mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Merge pull request #702 from AutoMaker-Org/chore/fix-ui-typescript-errors
chore: Fix all 246 TypeScript errors in UI
This commit is contained in:
@@ -191,7 +191,7 @@ export function NewProjectModal({
|
|||||||
|
|
||||||
// Use platform-specific path separator
|
// Use platform-specific path separator
|
||||||
const pathSep =
|
const pathSep =
|
||||||
typeof window !== 'undefined' && (window as any).electronAPI
|
typeof window !== 'undefined' && window.electronAPI
|
||||||
? navigator.platform.indexOf('Win') !== -1
|
? navigator.platform.indexOf('Win') !== -1
|
||||||
? '\\'
|
? '\\'
|
||||||
: '/'
|
: '/'
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ interface EditProjectDialogProps {
|
|||||||
export function EditProjectDialog({ project, open, onOpenChange }: EditProjectDialogProps) {
|
export function EditProjectDialog({ project, open, onOpenChange }: EditProjectDialogProps) {
|
||||||
const { setProjectName, setProjectIcon, setProjectCustomIcon } = useAppStore();
|
const { setProjectName, setProjectIcon, setProjectCustomIcon } = useAppStore();
|
||||||
const [name, setName] = useState(project.name);
|
const [name, setName] = useState(project.name);
|
||||||
const [icon, setIcon] = useState<string | null>((project as any).icon || null);
|
const [icon, setIcon] = useState<string | null>(project.icon || null);
|
||||||
const [customIconPath, setCustomIconPath] = useState<string | null>(
|
const [customIconPath, setCustomIconPath] = useState<string | null>(
|
||||||
(project as any).customIconPath || null
|
project.customIconPath || null
|
||||||
);
|
);
|
||||||
const [isUploadingIcon, setIsUploadingIcon] = useState(false);
|
const [isUploadingIcon, setIsUploadingIcon] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -36,10 +36,10 @@ export function EditProjectDialog({ project, open, onOpenChange }: EditProjectDi
|
|||||||
if (name.trim() !== project.name) {
|
if (name.trim() !== project.name) {
|
||||||
setProjectName(project.id, name.trim());
|
setProjectName(project.id, name.trim());
|
||||||
}
|
}
|
||||||
if (icon !== (project as any).icon) {
|
if (icon !== project.icon) {
|
||||||
setProjectIcon(project.id, icon);
|
setProjectIcon(project.id, icon);
|
||||||
}
|
}
|
||||||
if (customIconPath !== (project as any).customIconPath) {
|
if (customIconPath !== project.customIconPath) {
|
||||||
setProjectCustomIcon(project.id, customIconPath);
|
setProjectCustomIcon(project.id, customIconPath);
|
||||||
}
|
}
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ export function GitDiffPanel({
|
|||||||
<div className="flex flex-col items-center justify-center gap-2 py-8 text-muted-foreground">
|
<div className="flex flex-col items-center justify-center gap-2 py-8 text-muted-foreground">
|
||||||
<AlertCircle className="w-5 h-5 text-amber-500" />
|
<AlertCircle className="w-5 h-5 text-amber-500" />
|
||||||
<span className="text-sm">{error}</span>
|
<span className="text-sm">{error}</span>
|
||||||
<Button variant="ghost" size="sm" onClick={loadDiffs} className="mt-2">
|
<Button variant="ghost" size="sm" onClick={() => void loadDiffs()} className="mt-2">
|
||||||
<RefreshCw className="w-4 h-4 mr-2" />
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
Retry
|
Retry
|
||||||
</Button>
|
</Button>
|
||||||
@@ -550,7 +550,12 @@ export function GitDiffPanel({
|
|||||||
>
|
>
|
||||||
Collapse All
|
Collapse All
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" onClick={loadDiffs} className="text-xs h-7">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => void loadDiffs()}
|
||||||
|
className="text-xs h-7"
|
||||||
|
>
|
||||||
<RefreshCw className="w-3 h-3 mr-1" />
|
<RefreshCw className="w-3 h-3 mr-1" />
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Check, Circle, ChevronDown, ChevronRight, FileCode } from 'lucide-react
|
|||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import type { AutoModeEvent } from '@/types/electron';
|
import type { AutoModeEvent } from '@/types/electron';
|
||||||
|
import type { Feature, ParsedTask } from '@automaker/types';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
interface TaskInfo {
|
interface TaskInfo {
|
||||||
@@ -53,26 +54,29 @@ export function TaskProgressPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await api.features.get(projectPath, featureId);
|
const result = await api.features.get(projectPath, featureId);
|
||||||
const feature: any = (result as any).feature;
|
const feature = (result as { success: boolean; feature?: Feature }).feature;
|
||||||
if (result.success && feature?.planSpec?.tasks) {
|
if (result.success && feature?.planSpec?.tasks) {
|
||||||
const planSpec = feature.planSpec as any;
|
const planSpec = feature.planSpec;
|
||||||
const planTasks = planSpec.tasks;
|
const planTasks = planSpec.tasks; // Already guarded by the if condition above
|
||||||
const currentId = planSpec.currentTaskId;
|
const currentId = planSpec.currentTaskId;
|
||||||
const completedCount = planSpec.tasksCompleted || 0;
|
const completedCount = planSpec.tasksCompleted || 0;
|
||||||
|
|
||||||
// Convert planSpec tasks to TaskInfo with proper status
|
// Convert planSpec tasks to TaskInfo with proper status
|
||||||
const initialTasks: TaskInfo[] = planTasks.map((t: any, index: number) => ({
|
// planTasks is guaranteed to be defined due to the if condition check
|
||||||
id: t.id,
|
const initialTasks: TaskInfo[] = (planTasks as ParsedTask[]).map(
|
||||||
description: t.description,
|
(t: ParsedTask, index: number) => ({
|
||||||
filePath: t.filePath,
|
id: t.id,
|
||||||
phase: t.phase,
|
description: t.description,
|
||||||
status:
|
filePath: t.filePath,
|
||||||
index < completedCount
|
phase: t.phase,
|
||||||
? ('completed' as const)
|
status:
|
||||||
: t.id === currentId
|
index < completedCount
|
||||||
? ('in_progress' as const)
|
? ('completed' as const)
|
||||||
: ('pending' as const),
|
: t.id === currentId
|
||||||
}));
|
? ('in_progress' as const)
|
||||||
|
: ('pending' as const),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
setTasks(initialTasks);
|
setTasks(initialTasks);
|
||||||
setCurrentTaskId(currentId || null);
|
setCurrentTaskId(currentId || null);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
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 { queryKeys } from '@/lib/query-keys';
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -640,14 +640,14 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const detectedFeature of detectedFeatures) {
|
for (const detectedFeature of detectedFeatures) {
|
||||||
await api.features.create(currentProject.path, {
|
const newFeature: Feature = {
|
||||||
id: generateUUID(),
|
id: generateUUID(),
|
||||||
category: detectedFeature.category,
|
category: detectedFeature.category,
|
||||||
description: detectedFeature.description,
|
description: detectedFeature.description,
|
||||||
status: 'backlog',
|
status: 'backlog',
|
||||||
// Initialize with empty steps so the object satisfies the Feature type
|
|
||||||
steps: [],
|
steps: [],
|
||||||
} as any);
|
};
|
||||||
|
await api.features.create(currentProject.path, newFeature);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate React Query cache to sync UI
|
// Invalidate React Query cache to sync UI
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck - dnd-kit type incompatibilities with collision detection and complex state management
|
|
||||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
|
import type { PointerEvent as ReactPointerEvent } from 'react';
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
PointerSensor,
|
PointerSensor,
|
||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
useSensors,
|
useSensors,
|
||||||
rectIntersection,
|
rectIntersection,
|
||||||
pointerWithin,
|
pointerWithin,
|
||||||
type PointerEvent as DndPointerEvent,
|
type CollisionDetection,
|
||||||
|
type Collision,
|
||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
|
|
||||||
// Custom pointer sensor that ignores drag events from within dialogs
|
// Custom pointer sensor that ignores drag events from within dialogs
|
||||||
@@ -16,7 +17,7 @@ class DialogAwarePointerSensor extends PointerSensor {
|
|||||||
static activators = [
|
static activators = [
|
||||||
{
|
{
|
||||||
eventName: 'onPointerDown' as const,
|
eventName: 'onPointerDown' as const,
|
||||||
handler: ({ nativeEvent: event }: { nativeEvent: DndPointerEvent }) => {
|
handler: ({ nativeEvent: event }: ReactPointerEvent) => {
|
||||||
// Don't start drag if the event originated from inside a dialog
|
// Don't start drag if the event originated from inside a dialog
|
||||||
if ((event.target as Element)?.closest?.('[role="dialog"]')) {
|
if ((event.target as Element)?.closest?.('[role="dialog"]')) {
|
||||||
return false;
|
return false;
|
||||||
@@ -29,7 +30,7 @@ class DialogAwarePointerSensor extends PointerSensor {
|
|||||||
import { useAppStore, Feature } from '@/store/app-store';
|
import { useAppStore, Feature } from '@/store/app-store';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
import type { BacklogPlanResult } from '@automaker/types';
|
import type { BacklogPlanResult, FeatureStatusWithPipeline } from '@automaker/types';
|
||||||
import { pathsEqual } from '@/lib/utils';
|
import { pathsEqual } from '@/lib/utils';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { BoardBackgroundModal } from '@/components/dialogs/board-background-modal';
|
import { BoardBackgroundModal } from '@/components/dialogs/board-background-modal';
|
||||||
@@ -171,13 +172,9 @@ export function BoardView() {
|
|||||||
const [showCreatePRDialog, setShowCreatePRDialog] = useState(false);
|
const [showCreatePRDialog, setShowCreatePRDialog] = useState(false);
|
||||||
const [showCreateBranchDialog, setShowCreateBranchDialog] = useState(false);
|
const [showCreateBranchDialog, setShowCreateBranchDialog] = useState(false);
|
||||||
const [showPullResolveConflictsDialog, setShowPullResolveConflictsDialog] = useState(false);
|
const [showPullResolveConflictsDialog, setShowPullResolveConflictsDialog] = useState(false);
|
||||||
const [selectedWorktreeForAction, setSelectedWorktreeForAction] = useState<{
|
const [selectedWorktreeForAction, setSelectedWorktreeForAction] = useState<WorktreeInfo | null>(
|
||||||
path: string;
|
null
|
||||||
branch: string;
|
);
|
||||||
isMain: boolean;
|
|
||||||
hasChanges?: boolean;
|
|
||||||
changedFilesCount?: number;
|
|
||||||
} | null>(null);
|
|
||||||
const [worktreeRefreshKey, setWorktreeRefreshKey] = useState(0);
|
const [worktreeRefreshKey, setWorktreeRefreshKey] = useState(0);
|
||||||
|
|
||||||
// Backlog plan dialog state
|
// Backlog plan dialog state
|
||||||
@@ -348,12 +345,12 @@ export function BoardView() {
|
|||||||
}, [currentProject, worktreeRefreshKey]);
|
}, [currentProject, worktreeRefreshKey]);
|
||||||
|
|
||||||
// Custom collision detection that prioritizes specific drop targets (cards, worktrees) over columns
|
// Custom collision detection that prioritizes specific drop targets (cards, worktrees) over columns
|
||||||
const collisionDetectionStrategy = useCallback((args: any) => {
|
const collisionDetectionStrategy = useCallback((args: Parameters<CollisionDetection>[0]) => {
|
||||||
const pointerCollisions = pointerWithin(args);
|
const pointerCollisions = pointerWithin(args);
|
||||||
|
|
||||||
// Priority 1: Specific drop targets (cards for dependency links, worktrees)
|
// Priority 1: Specific drop targets (cards for dependency links, worktrees)
|
||||||
// These need to be detected even if they are inside a column
|
// These need to be detected even if they are inside a column
|
||||||
const specificTargetCollisions = pointerCollisions.filter((collision: any) => {
|
const specificTargetCollisions = pointerCollisions.filter((collision: Collision) => {
|
||||||
const id = String(collision.id);
|
const id = String(collision.id);
|
||||||
return id.startsWith('card-drop-') || id.startsWith('worktree-drop-');
|
return id.startsWith('card-drop-') || id.startsWith('worktree-drop-');
|
||||||
});
|
});
|
||||||
@@ -363,7 +360,7 @@ export function BoardView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Priority 2: Columns
|
// Priority 2: Columns
|
||||||
const columnCollisions = pointerCollisions.filter((collision: any) =>
|
const columnCollisions = pointerCollisions.filter((collision: Collision) =>
|
||||||
COLUMNS.some((col) => col.id === collision.id)
|
COLUMNS.some((col) => col.id === collision.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -417,19 +414,29 @@ export function BoardView() {
|
|||||||
|
|
||||||
// Get the branch for the currently selected worktree
|
// Get the branch for the currently selected worktree
|
||||||
// Find the worktree that matches the current selection, or use main worktree
|
// Find the worktree that matches the current selection, or use main worktree
|
||||||
const selectedWorktree = useMemo(() => {
|
const selectedWorktree = useMemo((): WorktreeInfo | undefined => {
|
||||||
|
let found;
|
||||||
if (currentWorktreePath === null) {
|
if (currentWorktreePath === null) {
|
||||||
// Primary worktree selected - find the main worktree
|
// Primary worktree selected - find the main worktree
|
||||||
return worktrees.find((w) => w.isMain);
|
found = worktrees.find((w) => w.isMain);
|
||||||
} else {
|
} else {
|
||||||
// Specific worktree selected - find it by path
|
// Specific worktree selected - find it by path
|
||||||
return worktrees.find((w) => !w.isMain && pathsEqual(w.path, currentWorktreePath));
|
found = worktrees.find((w) => !w.isMain && pathsEqual(w.path, currentWorktreePath));
|
||||||
}
|
}
|
||||||
|
if (!found) return undefined;
|
||||||
|
// Ensure all required WorktreeInfo fields are present
|
||||||
|
return {
|
||||||
|
...found,
|
||||||
|
isCurrent:
|
||||||
|
found.isCurrent ??
|
||||||
|
(currentWorktreePath !== null ? pathsEqual(found.path, currentWorktreePath) : found.isMain),
|
||||||
|
hasWorktree: found.hasWorktree ?? true,
|
||||||
|
};
|
||||||
}, [worktrees, currentWorktreePath]);
|
}, [worktrees, currentWorktreePath]);
|
||||||
|
|
||||||
// Auto mode hook - pass current worktree to get worktree-specific state
|
// Auto mode hook - pass current worktree to get worktree-specific state
|
||||||
// Must be after selectedWorktree is defined
|
// Must be after selectedWorktree is defined
|
||||||
const autoMode = useAutoMode(selectedWorktree ?? undefined);
|
const autoMode = useAutoMode(selectedWorktree);
|
||||||
// Get runningTasks from the hook (scoped to current project/worktree)
|
// Get runningTasks from the hook (scoped to current project/worktree)
|
||||||
const runningAutoTasks = autoMode.runningTasks;
|
const runningAutoTasks = autoMode.runningTasks;
|
||||||
// Get worktree-specific maxConcurrency from the hook
|
// Get worktree-specific maxConcurrency from the hook
|
||||||
@@ -958,28 +965,27 @@ export function BoardView() {
|
|||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api?.backlogPlan) return;
|
if (!api?.backlogPlan) return;
|
||||||
|
|
||||||
const unsubscribe = api.backlogPlan.onEvent(
|
const unsubscribe = api.backlogPlan.onEvent((data: unknown) => {
|
||||||
(event: { type: string; result?: BacklogPlanResult; error?: string }) => {
|
const event = data as { type: string; result?: BacklogPlanResult; error?: string };
|
||||||
if (event.type === 'backlog_plan_complete') {
|
if (event.type === 'backlog_plan_complete') {
|
||||||
setIsGeneratingPlan(false);
|
setIsGeneratingPlan(false);
|
||||||
if (event.result && event.result.changes?.length > 0) {
|
if (event.result && event.result.changes?.length > 0) {
|
||||||
setPendingBacklogPlan(event.result);
|
setPendingBacklogPlan(event.result);
|
||||||
toast.success('Plan ready! Click to review.', {
|
toast.success('Plan ready! Click to review.', {
|
||||||
duration: 10000,
|
duration: 10000,
|
||||||
action: {
|
action: {
|
||||||
label: 'Review',
|
label: 'Review',
|
||||||
onClick: () => setShowPlanDialog(true),
|
onClick: () => setShowPlanDialog(true),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.info('No changes generated. Try again with a different prompt.');
|
toast.info('No changes generated. Try again with a different prompt.');
|
||||||
}
|
|
||||||
} else if (event.type === 'backlog_plan_error') {
|
|
||||||
setIsGeneratingPlan(false);
|
|
||||||
toast.error(`Plan generation failed: ${event.error}`);
|
|
||||||
}
|
}
|
||||||
|
} else if (event.type === 'backlog_plan_error') {
|
||||||
|
setIsGeneratingPlan(false);
|
||||||
|
toast.error(`Plan generation failed: ${event.error}`);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -1091,10 +1097,10 @@ export function BoardView() {
|
|||||||
// Build columnFeaturesMap for ListView
|
// Build columnFeaturesMap for ListView
|
||||||
// pipelineConfig is now from usePipelineConfig React Query hook at the top
|
// pipelineConfig is now from usePipelineConfig React Query hook at the top
|
||||||
const columnFeaturesMap = useMemo(() => {
|
const columnFeaturesMap = useMemo(() => {
|
||||||
const columns = getColumnsWithPipeline(pipelineConfig);
|
const columns = getColumnsWithPipeline(pipelineConfig ?? null);
|
||||||
const map: Record<string, typeof hookFeatures> = {};
|
const map: Record<string, typeof hookFeatures> = {};
|
||||||
for (const column of columns) {
|
for (const column of columns) {
|
||||||
map[column.id] = getColumnFeatures(column.id as any);
|
map[column.id] = getColumnFeatures(column.id as FeatureStatusWithPipeline);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}, [pipelineConfig, getColumnFeatures]);
|
}, [pipelineConfig, getColumnFeatures]);
|
||||||
@@ -1444,14 +1450,13 @@ export function BoardView() {
|
|||||||
onAddFeature={() => setShowAddDialog(true)}
|
onAddFeature={() => setShowAddDialog(true)}
|
||||||
onShowCompletedModal={() => setShowCompletedModal(true)}
|
onShowCompletedModal={() => setShowCompletedModal(true)}
|
||||||
completedCount={completedFeatures.length}
|
completedCount={completedFeatures.length}
|
||||||
pipelineConfig={pipelineConfig}
|
pipelineConfig={pipelineConfig ?? null}
|
||||||
onOpenPipelineSettings={() => setShowPipelineSettings(true)}
|
onOpenPipelineSettings={() => setShowPipelineSettings(true)}
|
||||||
isSelectionMode={isSelectionMode}
|
isSelectionMode={isSelectionMode}
|
||||||
selectionTarget={selectionTarget}
|
selectionTarget={selectionTarget}
|
||||||
selectedFeatureIds={selectedFeatureIds}
|
selectedFeatureIds={selectedFeatureIds}
|
||||||
onToggleFeatureSelection={toggleFeatureSelection}
|
onToggleFeatureSelection={toggleFeatureSelection}
|
||||||
onToggleSelectionMode={toggleSelectionMode}
|
onToggleSelectionMode={toggleSelectionMode}
|
||||||
viewMode={viewMode}
|
|
||||||
isDragging={activeFeature !== null}
|
isDragging={activeFeature !== null}
|
||||||
onAiSuggest={() => setShowPlanDialog(true)}
|
onAiSuggest={() => setShowPlanDialog(true)}
|
||||||
className="transition-opacity duration-200"
|
className="transition-opacity duration-200"
|
||||||
@@ -1604,7 +1609,7 @@ export function BoardView() {
|
|||||||
open={showPipelineSettings}
|
open={showPipelineSettings}
|
||||||
onClose={() => setShowPipelineSettings(false)}
|
onClose={() => setShowPipelineSettings(false)}
|
||||||
projectPath={currentProject.path}
|
projectPath={currentProject.path}
|
||||||
pipelineConfig={pipelineConfig}
|
pipelineConfig={pipelineConfig ?? null}
|
||||||
onSave={async (config) => {
|
onSave={async (config) => {
|
||||||
const api = getHttpApiClient();
|
const api = getHttpApiClient();
|
||||||
const result = await api.pipeline.saveConfig(currentProject.path, config);
|
const result = await api.pipeline.saveConfig(currentProject.path, config);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { memo, useEffect, useState, useMemo, useRef } from 'react';
|
import { memo, useEffect, useState, useMemo, useRef } from 'react';
|
||||||
import { Feature, ThinkingLevel, ParsedTask } from '@/store/app-store';
|
import { Feature, ThinkingLevel, ReasoningEffort, ParsedTask } from '@/store/app-store';
|
||||||
import type { ReasoningEffort } from '@automaker/types';
|
|
||||||
import { getProviderFromModel } from '@/lib/utils';
|
import { getProviderFromModel } from '@/lib/utils';
|
||||||
import { parseAgentContext, formatModelName, DEFAULT_MODEL } from '@/lib/agent-context-parser';
|
import { parseAgentContext, formatModelName, DEFAULT_MODEL } from '@/lib/agent-context-parser';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -290,7 +289,8 @@ export const AgentInfoPanel = memo(function AgentInfoPanel({
|
|||||||
// Agent Info Panel for non-backlog cards
|
// Agent Info Panel for non-backlog cards
|
||||||
// Show panel if we have agentInfo OR planSpec.tasks (for spec/full mode)
|
// Show panel if we have agentInfo OR planSpec.tasks (for spec/full mode)
|
||||||
// Note: hasPlanSpecTasks is already defined above and includes freshPlanSpec
|
// Note: hasPlanSpecTasks is already defined above and includes freshPlanSpec
|
||||||
if (feature.status !== 'backlog' && (agentInfo || hasPlanSpecTasks)) {
|
// (The backlog case was already handled above and returned early)
|
||||||
|
if (agentInfo || hasPlanSpecTasks) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 space-y-2 overflow-hidden">
|
<div className="mb-3 space-y-2 overflow-hidden">
|
||||||
|
|||||||
@@ -98,13 +98,11 @@ export const PriorityBadges = memo(function PriorityBadges({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setCurrentTime(Date.now());
|
setCurrentTime(Date.now());
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, [feature.justFinishedAt, feature.status, currentTime]);
|
}, [feature.justFinishedAt, feature.status, currentTime]);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { useAppStore } from '@/store/app-store';
|
|||||||
import { extractSummary } from '@/lib/log-parser';
|
import { extractSummary } from '@/lib/log-parser';
|
||||||
import { useAgentOutput } from '@/hooks/queries';
|
import { useAgentOutput } from '@/hooks/queries';
|
||||||
import type { AutoModeEvent } from '@/types/electron';
|
import type { AutoModeEvent } from '@/types/electron';
|
||||||
|
import type { BacklogPlanEvent } from '@automaker/types';
|
||||||
|
|
||||||
interface AgentOutputModalProps {
|
interface AgentOutputModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -48,18 +49,16 @@ export function AgentOutputModal({
|
|||||||
const isBacklogPlan = featureId.startsWith('backlog-plan:');
|
const isBacklogPlan = featureId.startsWith('backlog-plan:');
|
||||||
|
|
||||||
// Resolve project path - prefer prop, fallback to window.__currentProject
|
// Resolve project path - prefer prop, fallback to window.__currentProject
|
||||||
const resolvedProjectPath = projectPathProp || (window as any).__currentProject?.path || '';
|
const resolvedProjectPath = projectPathProp || window.__currentProject?.path || '';
|
||||||
|
|
||||||
// Track additional content from WebSocket events (appended to query data)
|
// Track additional content from WebSocket events (appended to query data)
|
||||||
const [streamedContent, setStreamedContent] = useState<string>('');
|
const [streamedContent, setStreamedContent] = useState<string>('');
|
||||||
const [viewMode, setViewMode] = useState<ViewMode | null>(null);
|
const [viewMode, setViewMode] = useState<ViewMode | null>(null);
|
||||||
|
|
||||||
// Use React Query for initial output loading
|
// Use React Query for initial output loading
|
||||||
const { data: initialOutput = '', isLoading } = useAgentOutput(
|
const { data: initialOutput = '', isLoading } = useAgentOutput(resolvedProjectPath, featureId, {
|
||||||
resolvedProjectPath,
|
enabled: open && !!resolvedProjectPath,
|
||||||
featureId,
|
});
|
||||||
open && !!resolvedProjectPath
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset streamed content when modal opens or featureId changes
|
// Reset streamed content when modal opens or featureId changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -262,7 +261,8 @@ export function AgentOutputModal({
|
|||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api?.backlogPlan) return;
|
if (!api?.backlogPlan) return;
|
||||||
|
|
||||||
const unsubscribe = api.backlogPlan.onEvent((event: any) => {
|
const unsubscribe = api.backlogPlan.onEvent((data: unknown) => {
|
||||||
|
const event = data as BacklogPlanEvent;
|
||||||
if (!event?.type) return;
|
if (!event?.type) return;
|
||||||
|
|
||||||
let newContent = '';
|
let newContent = '';
|
||||||
|
|||||||
@@ -23,14 +23,7 @@ import { getHttpApiClient } from '@/lib/http-api-client';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { GitMerge, RefreshCw, AlertTriangle } from 'lucide-react';
|
import { GitMerge, RefreshCw, AlertTriangle } from 'lucide-react';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
|
import type { WorktreeInfo } from '../worktree-panel/types';
|
||||||
interface WorktreeInfo {
|
|
||||||
path: string;
|
|
||||||
branch: string;
|
|
||||||
isMain: boolean;
|
|
||||||
hasChanges?: boolean;
|
|
||||||
changedFilesCount?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RemoteBranch {
|
interface RemoteBranch {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -49,7 +42,7 @@ interface PullResolveConflictsDialogProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
worktree: WorktreeInfo | null;
|
worktree: WorktreeInfo | null;
|
||||||
onConfirm: (worktree: WorktreeInfo, remoteBranch: string) => void;
|
onConfirm: (worktree: WorktreeInfo, remoteBranch: string) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PullResolveConflictsDialog({
|
export function PullResolveConflictsDialog({
|
||||||
|
|||||||
@@ -128,10 +128,9 @@ export function useBoardDragDrop({
|
|||||||
const targetBranch = worktreeData.branch;
|
const targetBranch = worktreeData.branch;
|
||||||
const currentBranch = draggedFeature.branchName;
|
const currentBranch = draggedFeature.branchName;
|
||||||
|
|
||||||
// For main worktree, set branchName to null to indicate it should use main
|
// For main worktree, set branchName to undefined to indicate it should use main
|
||||||
// (must use null not undefined so it serializes to JSON for the API call)
|
|
||||||
// For other worktrees, set branchName to the target branch
|
// For other worktrees, set branchName to the target branch
|
||||||
const newBranchName = worktreeData.isMain ? null : targetBranch;
|
const newBranchName: string | undefined = worktreeData.isMain ? undefined : targetBranch;
|
||||||
|
|
||||||
// If already on the same branch, nothing to do
|
// If already on the same branch, nothing to do
|
||||||
// For main worktree: feature with null/undefined branchName is already on main
|
// For main worktree: feature with null/undefined branchName is already on main
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
|
import type { Feature } from '@/store/app-store';
|
||||||
|
|
||||||
const logger = createLogger('BoardEffects');
|
const logger = createLogger('BoardEffects');
|
||||||
|
|
||||||
interface UseBoardEffectsProps {
|
interface UseBoardEffectsProps {
|
||||||
currentProject: { path: string; id: string } | null;
|
currentProject: { path: string; id: string; name?: string } | null;
|
||||||
specCreatingForProject: string | null;
|
specCreatingForProject: string | null;
|
||||||
setSpecCreatingForProject: (path: string | null) => void;
|
setSpecCreatingForProject: (path: string | null) => void;
|
||||||
checkContextExists: (featureId: string) => Promise<boolean>;
|
checkContextExists: (featureId: string) => Promise<boolean>;
|
||||||
features: any[];
|
features: Feature[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
featuresWithContext: Set<string>;
|
featuresWithContext: Set<string>;
|
||||||
setFeaturesWithContext: (set: Set<string>) => void;
|
setFeaturesWithContext: (set: Set<string>) => void;
|
||||||
@@ -33,10 +34,10 @@ export function useBoardEffects({
|
|||||||
// Make current project available globally for modal
|
// Make current project available globally for modal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentProject) {
|
if (currentProject) {
|
||||||
(window as any).__currentProject = currentProject;
|
window.__currentProject = currentProject;
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
(window as any).__currentProject = null;
|
window.__currentProject = null;
|
||||||
};
|
};
|
||||||
}, [currentProject]);
|
}, [currentProject]);
|
||||||
|
|
||||||
|
|||||||
@@ -185,8 +185,8 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) {
|
|||||||
features,
|
features,
|
||||||
isLoading,
|
isLoading,
|
||||||
persistedCategories,
|
persistedCategories,
|
||||||
loadFeatures: () => {
|
loadFeatures: async () => {
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.features.all(currentProject?.path ?? ''),
|
queryKey: queryKeys.features.all(currentProject?.path ?? ''),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import type { Feature as ApiFeature } from '@automaker/types';
|
||||||
import { Feature } from '@/store/app-store';
|
import { Feature } from '@/store/app-store';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
@@ -48,14 +49,14 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
feature: result.feature,
|
feature: result.feature,
|
||||||
});
|
});
|
||||||
if (result.success && result.feature) {
|
if (result.success && result.feature) {
|
||||||
const updatedFeature = result.feature;
|
const updatedFeature = result.feature as Feature;
|
||||||
updateFeature(updatedFeature.id, updatedFeature);
|
updateFeature(updatedFeature.id, updatedFeature as Partial<Feature>);
|
||||||
queryClient.setQueryData<Feature[]>(
|
queryClient.setQueryData<Feature[]>(
|
||||||
queryKeys.features.all(currentProject.path),
|
queryKeys.features.all(currentProject.path),
|
||||||
(features) => {
|
(features) => {
|
||||||
if (!features) return features;
|
if (!features) return features;
|
||||||
return features.map((feature) =>
|
return features.map((feature) =>
|
||||||
feature.id === updatedFeature.id ? updatedFeature : feature
|
feature.id === updatedFeature.id ? { ...feature, ...updatedFeature } : feature
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -85,9 +86,9 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await api.features.create(currentProject.path, feature);
|
const result = await api.features.create(currentProject.path, feature as ApiFeature);
|
||||||
if (result.success && result.feature) {
|
if (result.success && result.feature) {
|
||||||
updateFeature(result.feature.id, result.feature);
|
updateFeature(result.feature.id, result.feature as Partial<Feature>);
|
||||||
// Invalidate React Query cache to sync UI
|
// Invalidate React Query cache to sync UI
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.features.all(currentProject.path),
|
queryKey: queryKeys.features.all(currentProject.path),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
type RefObject,
|
type RefObject,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
|
type UIEvent,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { DragOverlay } from '@dnd-kit/core';
|
import { DragOverlay } from '@dnd-kit/core';
|
||||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
@@ -79,7 +80,7 @@ const REDUCED_CARD_OPACITY_PERCENT = 85;
|
|||||||
type VirtualListItem = { id: string };
|
type VirtualListItem = { id: string };
|
||||||
|
|
||||||
interface VirtualListState<Item extends VirtualListItem> {
|
interface VirtualListState<Item extends VirtualListItem> {
|
||||||
contentRef: RefObject<HTMLDivElement>;
|
contentRef: RefObject<HTMLDivElement | null>;
|
||||||
onScroll: (event: UIEvent<HTMLDivElement>) => void;
|
onScroll: (event: UIEvent<HTMLDivElement>) => void;
|
||||||
itemIds: string[];
|
itemIds: string[];
|
||||||
visibleItems: Item[];
|
visibleItems: Item[];
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck - model selector with provider-specific model options and validation
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Brain, AlertTriangle } from 'lucide-react';
|
import { Brain, AlertTriangle } from 'lucide-react';
|
||||||
@@ -7,7 +6,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { useSetupStore } from '@/store/setup-store';
|
import { useSetupStore } from '@/store/setup-store';
|
||||||
import { getModelProvider } from '@automaker/types';
|
import { getModelProvider } from '@automaker/types';
|
||||||
import type { ModelProvider } from '@automaker/types';
|
import type { ModelProvider, CursorModelId } from '@automaker/types';
|
||||||
import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants';
|
import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
@@ -40,6 +39,7 @@ export function ModelSelector({
|
|||||||
const isCursorAvailable = cursorCliStatus?.installed && cursorCliStatus?.auth?.authenticated;
|
const isCursorAvailable = cursorCliStatus?.installed && cursorCliStatus?.auth?.authenticated;
|
||||||
|
|
||||||
// Check if Codex CLI is available
|
// Check if Codex CLI is available
|
||||||
|
// @ts-expect-error - codexCliStatus uses CliStatus type but should use CodexCliStatus which has auth
|
||||||
const isCodexAvailable = codexCliStatus?.installed && codexCliStatus?.auth?.authenticated;
|
const isCodexAvailable = codexCliStatus?.installed && codexCliStatus?.auth?.authenticated;
|
||||||
|
|
||||||
// Fetch Codex models on mount
|
// Fetch Codex models on mount
|
||||||
@@ -75,8 +75,8 @@ export function ModelSelector({
|
|||||||
// Check both the full ID (for GPT models) and the unprefixed version (for non-GPT models)
|
// Check both the full ID (for GPT models) and the unprefixed version (for non-GPT models)
|
||||||
const unprefixedId = model.id.startsWith('cursor-') ? model.id.slice(7) : model.id;
|
const unprefixedId = model.id.startsWith('cursor-') ? model.id.slice(7) : model.id;
|
||||||
return (
|
return (
|
||||||
enabledCursorModels.includes(model.id as any) ||
|
enabledCursorModels.includes(model.id as CursorModelId) ||
|
||||||
enabledCursorModels.includes(unprefixedId as any)
|
enabledCursorModels.includes(unprefixedId as CursorModelId)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export function useAvailableEditors() {
|
|||||||
const { mutate: refreshMutate, isPending: isRefreshing } = useMutation({
|
const { mutate: refreshMutate, isPending: isRefreshing } = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.refreshEditors();
|
const result = await api.worktree.refreshEditors();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to refresh editors');
|
throw new Error(result.error || 'Failed to refresh editors');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { UIEvent } from 'react';
|
import type { UIEvent } from 'react';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore, ChatSession } from '@/store/app-store';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -156,7 +156,7 @@ export function ChatHistory() {
|
|||||||
createChatSession();
|
createChatSession();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectSession = (session: any) => {
|
const handleSelectSession = (session: ChatSession) => {
|
||||||
setCurrentChatSession(session);
|
setCurrentChatSession(session);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// @ts-nocheck - graph view page with feature filtering and visualization state
|
|
||||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||||
import { useAppStore, Feature } from '@/store/app-store';
|
import { useAppStore, Feature, FeatureImagePath } from '@/store/app-store';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { GraphView } from './graph-view';
|
import { GraphView } from './graph-view';
|
||||||
import {
|
import {
|
||||||
@@ -150,33 +149,32 @@ export function GraphViewPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribe = api.backlogPlan.onEvent(
|
const unsubscribe = api.backlogPlan.onEvent((data: unknown) => {
|
||||||
(event: { type: string; result?: BacklogPlanResult; error?: string }) => {
|
const event = data as { type: string; result?: BacklogPlanResult; error?: string };
|
||||||
logger.debug('Backlog plan event received', {
|
logger.debug('Backlog plan event received', {
|
||||||
type: event.type,
|
type: event.type,
|
||||||
hasResult: Boolean(event.result),
|
hasResult: Boolean(event.result),
|
||||||
hasError: Boolean(event.error),
|
hasError: Boolean(event.error),
|
||||||
});
|
});
|
||||||
if (event.type === 'backlog_plan_complete') {
|
if (event.type === 'backlog_plan_complete') {
|
||||||
setIsGeneratingPlan(false);
|
setIsGeneratingPlan(false);
|
||||||
if (event.result && event.result.changes?.length > 0) {
|
if (event.result && event.result.changes?.length > 0) {
|
||||||
setPendingBacklogPlan(event.result);
|
setPendingBacklogPlan(event.result);
|
||||||
toast.success('Plan ready! Click to review.', {
|
toast.success('Plan ready! Click to review.', {
|
||||||
duration: 10000,
|
duration: 10000,
|
||||||
action: {
|
action: {
|
||||||
label: 'Review',
|
label: 'Review',
|
||||||
onClick: () => setShowPlanDialog(true),
|
onClick: () => setShowPlanDialog(true),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.info('No changes generated. Try again with a different prompt.');
|
toast.info('No changes generated. Try again with a different prompt.');
|
||||||
}
|
|
||||||
} else if (event.type === 'backlog_plan_error') {
|
|
||||||
setIsGeneratingPlan(false);
|
|
||||||
toast.error(`Plan generation failed: ${event.error}`);
|
|
||||||
}
|
}
|
||||||
|
} else if (event.type === 'backlog_plan_error') {
|
||||||
|
setIsGeneratingPlan(false);
|
||||||
|
toast.error(`Plan generation failed: ${event.error}`);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -212,7 +210,7 @@ export function GraphViewPage() {
|
|||||||
return hookFeatures.reduce(
|
return hookFeatures.reduce(
|
||||||
(counts, feature) => {
|
(counts, feature) => {
|
||||||
if (feature.status !== 'completed') {
|
if (feature.status !== 'completed') {
|
||||||
const branch = feature.branchName ?? 'main';
|
const branch = (feature.branchName as string | undefined) ?? 'main';
|
||||||
counts[branch] = (counts[branch] || 0) + 1;
|
counts[branch] = (counts[branch] || 0) + 1;
|
||||||
}
|
}
|
||||||
return counts;
|
return counts;
|
||||||
@@ -236,7 +234,7 @@ export function GraphViewPage() {
|
|||||||
// Follow-up state (simplified for graph view)
|
// Follow-up state (simplified for graph view)
|
||||||
const [followUpFeature, setFollowUpFeature] = useState<Feature | null>(null);
|
const [followUpFeature, setFollowUpFeature] = useState<Feature | null>(null);
|
||||||
const [followUpPrompt, setFollowUpPrompt] = useState('');
|
const [followUpPrompt, setFollowUpPrompt] = useState('');
|
||||||
const [followUpImagePaths, setFollowUpImagePaths] = useState<any[]>([]);
|
const [followUpImagePaths, setFollowUpImagePaths] = useState<FeatureImagePath[]>([]);
|
||||||
const [, setFollowUpPreviewMap] = useState<Map<string, string>>(new Map());
|
const [, setFollowUpPreviewMap] = useState<Map<string, string>>(new Map());
|
||||||
|
|
||||||
// In-progress features for shortcuts
|
// In-progress features for shortcuts
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export function useGraphNodes({
|
|||||||
type: 'dependency',
|
type: 'dependency',
|
||||||
animated: enableEdgeAnimations && (isRunning || runningTaskIds.has(depId)),
|
animated: enableEdgeAnimations && (isRunning || runningTaskIds.has(depId)),
|
||||||
data: {
|
data: {
|
||||||
sourceStatus: sourceFeature.status,
|
sourceStatus: sourceFeature.status as Feature['status'],
|
||||||
targetStatus: feature.status,
|
targetStatus: feature.status,
|
||||||
isHighlighted: edgeIsHighlighted,
|
isHighlighted: edgeIsHighlighted,
|
||||||
isDimmed: edgeIsDimmed,
|
isDimmed: edgeIsDimmed,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck - interview flow state machine with dynamic question handling
|
|
||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
import { useAppStore, Feature } from '@/store/app-store';
|
import { useAppStore, Feature } from '@/store/app-store';
|
||||||
@@ -324,7 +323,7 @@ export function InterviewView() {
|
|||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
// Use platform-specific path separator
|
// Use platform-specific path separator
|
||||||
const pathSep =
|
const pathSep =
|
||||||
typeof window !== 'undefined' && (window as any).electronAPI
|
typeof window !== 'undefined' && window.electronAPI
|
||||||
? navigator.platform.indexOf('Win') !== -1
|
? navigator.platform.indexOf('Win') !== -1
|
||||||
? '\\'
|
? '\\'
|
||||||
: '/'
|
: '/'
|
||||||
@@ -349,8 +348,9 @@ export function InterviewView() {
|
|||||||
id: generateUUID(),
|
id: generateUUID(),
|
||||||
category: 'Core',
|
category: 'Core',
|
||||||
description: 'Initial project setup',
|
description: 'Initial project setup',
|
||||||
status: 'backlog' as const,
|
status: 'backlog',
|
||||||
skipTests: true,
|
skipTests: true,
|
||||||
|
steps: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!api.features) {
|
if (!api.features) {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function RecentActivityFeed({ activities, maxItems = 10 }: RecentActivity
|
|||||||
async (activity: RecentActivity) => {
|
async (activity: RecentActivity) => {
|
||||||
try {
|
try {
|
||||||
// Get project path from the activity (projectId is actually the path in our data model)
|
// Get project path from the activity (projectId is actually the path in our data model)
|
||||||
const projectPath = activity.projectPath || activity.projectId;
|
const projectPath = (activity.projectPath as string | undefined) || activity.projectId;
|
||||||
const projectName = activity.projectName;
|
const projectName = activity.projectName;
|
||||||
|
|
||||||
const initResult = await initializeProject(projectPath);
|
const initResult = await initializeProject(projectPath);
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ export function ProjectBulkReplaceDialog({
|
|||||||
currentEntry: PhaseModelEntry
|
currentEntry: PhaseModelEntry
|
||||||
) => {
|
) => {
|
||||||
const claudeAlias = getClaudeModelAlias(currentEntry);
|
const claudeAlias = getClaudeModelAlias(currentEntry);
|
||||||
const newEntry = findModelForClaudeAlias(selectedProviderConfig, claudeAlias, key);
|
const providerConfig: ClaudeCompatibleProvider | null = selectedProviderConfig ?? null;
|
||||||
|
const newEntry = findModelForClaudeAlias(providerConfig, claudeAlias, key);
|
||||||
|
|
||||||
// Get display names
|
// Get display names
|
||||||
const getCurrentDisplay = (): string => {
|
const getCurrentDisplay = (): string => {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { useAppStore } from '@/store/app-store';
|
|||||||
import {
|
import {
|
||||||
useAvailableEditors,
|
useAvailableEditors,
|
||||||
useEffectiveDefaultEditor,
|
useEffectiveDefaultEditor,
|
||||||
|
type EditorInfo,
|
||||||
} from '@/components/views/board-view/worktree-panel/hooks/use-available-editors';
|
} from '@/components/views/board-view/worktree-panel/hooks/use-available-editors';
|
||||||
import { getEditorIcon } from '@/components/icons/editor-icons';
|
import { getEditorIcon } from '@/components/icons/editor-icons';
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ export function AccountSection() {
|
|||||||
|
|
||||||
// Normalize Select value: if saved editor isn't found, show 'auto'
|
// Normalize Select value: if saved editor isn't found, show 'auto'
|
||||||
const hasSavedEditor =
|
const hasSavedEditor =
|
||||||
!!defaultEditorCommand && editors.some((e) => e.command === defaultEditorCommand);
|
!!defaultEditorCommand && editors.some((e: EditorInfo) => e.command === defaultEditorCommand);
|
||||||
const selectValue = hasSavedEditor ? defaultEditorCommand : 'auto';
|
const selectValue = hasSavedEditor ? defaultEditorCommand : 'auto';
|
||||||
|
|
||||||
// Get icon component for the effective editor
|
// Get icon component for the effective editor
|
||||||
@@ -121,7 +122,7 @@ export function AccountSection() {
|
|||||||
Auto-detect
|
Auto-detect
|
||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{editors.map((editor) => {
|
{editors.map((editor: EditorInfo) => {
|
||||||
const Icon = getEditorIcon(editor.command);
|
const Icon = getEditorIcon(editor.command);
|
||||||
return (
|
return (
|
||||||
<SelectItem key={editor.command} value={editor.command}>
|
<SelectItem key={editor.command} value={editor.command}>
|
||||||
|
|||||||
@@ -89,7 +89,17 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
|
|||||||
setIsAuthenticating(true);
|
setIsAuthenticating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.setup.authClaude();
|
// Check if authClaude method exists on the API
|
||||||
|
const authClaude = (api.setup as Record<string, unknown> | undefined)?.authClaude as
|
||||||
|
| (() => Promise<{ success: boolean; error?: string }>)
|
||||||
|
| undefined;
|
||||||
|
if (!authClaude) {
|
||||||
|
toast.error('Authentication Failed', {
|
||||||
|
description: 'Claude authentication is not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await authClaude();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success('Signed In', {
|
toast.success('Signed In', {
|
||||||
@@ -114,7 +124,17 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
|
|||||||
setIsDeauthenticating(true);
|
setIsDeauthenticating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.setup.deauthClaude();
|
// Check if deauthClaude method exists on the API
|
||||||
|
const deauthClaude = (api.setup as Record<string, unknown> | undefined)?.deauthClaude as
|
||||||
|
| (() => Promise<{ success: boolean; error?: string }>)
|
||||||
|
| undefined;
|
||||||
|
if (!deauthClaude) {
|
||||||
|
toast.error('Sign Out Failed', {
|
||||||
|
description: 'Claude sign out is not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await deauthClaude();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success('Signed Out', {
|
toast.success('Signed Out', {
|
||||||
|
|||||||
@@ -84,7 +84,17 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
|
|||||||
setIsAuthenticating(true);
|
setIsAuthenticating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.setup.authCodex();
|
// Check if authCodex method exists on the API
|
||||||
|
const authCodex = (api.setup as Record<string, unknown> | undefined)?.authCodex as
|
||||||
|
| (() => Promise<{ success: boolean; error?: string }>)
|
||||||
|
| undefined;
|
||||||
|
if (!authCodex) {
|
||||||
|
toast.error('Authentication Failed', {
|
||||||
|
description: 'Codex authentication is not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await authCodex();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success('Signed In', {
|
toast.success('Signed In', {
|
||||||
@@ -109,7 +119,17 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
|
|||||||
setIsDeauthenticating(true);
|
setIsDeauthenticating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.setup.deauthCodex();
|
// Check if deauthCodex method exists on the API
|
||||||
|
const deauthCodex = (api.setup as Record<string, unknown> | undefined)?.deauthCodex as
|
||||||
|
| (() => Promise<{ success: boolean; error?: string }>)
|
||||||
|
| undefined;
|
||||||
|
if (!deauthCodex) {
|
||||||
|
toast.error('Sign Out Failed', {
|
||||||
|
description: 'Codex sign out is not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await deauthCodex();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success('Signed Out', {
|
toast.success('Signed Out', {
|
||||||
|
|||||||
@@ -209,7 +209,17 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
|
|||||||
setIsAuthenticating(true);
|
setIsAuthenticating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.setup.authCursor();
|
// Check if authCursor method exists on the API
|
||||||
|
const authCursor = (api?.setup as Record<string, unknown> | undefined)?.authCursor as
|
||||||
|
| (() => Promise<{ success: boolean; error?: string }>)
|
||||||
|
| undefined;
|
||||||
|
if (!authCursor) {
|
||||||
|
toast.error('Authentication Failed', {
|
||||||
|
description: 'Cursor authentication is not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await authCursor();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success('Signed In', {
|
toast.success('Signed In', {
|
||||||
@@ -234,7 +244,17 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
|
|||||||
setIsDeauthenticating(true);
|
setIsDeauthenticating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.setup.deauthCursor();
|
// Check if deauthCursor method exists on the API
|
||||||
|
const deauthCursor = (api?.setup as Record<string, unknown> | undefined)?.deauthCursor as
|
||||||
|
| (() => Promise<{ success: boolean; error?: string }>)
|
||||||
|
| undefined;
|
||||||
|
if (!deauthCursor) {
|
||||||
|
toast.error('Sign Out Failed', {
|
||||||
|
description: 'Cursor sign out is not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await deauthCursor();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success('Signed Out', {
|
toast.success('Signed Out', {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function SecurityWarningDialog({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
serverType,
|
serverType,
|
||||||
_serverName,
|
serverName: _serverName,
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function BulkReplaceDialog({ open, onOpenChange }: BulkReplaceDialogProps
|
|||||||
currentEntry: PhaseModelEntry
|
currentEntry: PhaseModelEntry
|
||||||
) => {
|
) => {
|
||||||
const claudeAlias = getClaudeModelAlias(currentEntry);
|
const claudeAlias = getClaudeModelAlias(currentEntry);
|
||||||
const newEntry = findModelForClaudeAlias(selectedProviderConfig, claudeAlias, key);
|
const newEntry = findModelForClaudeAlias(selectedProviderConfig ?? null, claudeAlias, key);
|
||||||
|
|
||||||
// Get display names
|
// Get display names
|
||||||
const getCurrentDisplay = (): string => {
|
const getCurrentDisplay = (): string => {
|
||||||
|
|||||||
@@ -54,9 +54,25 @@ export function CodexSettingsTab() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkCodexStatus = async () => {
|
const checkCodexStatus = async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (api?.setup?.getCodexStatus) {
|
// Check if getCodexStatus method exists on the API (may not be implemented yet)
|
||||||
|
const getCodexStatus = (api?.setup as Record<string, unknown> | undefined)?.getCodexStatus as
|
||||||
|
| (() => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
installed: boolean;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
recommendation?: string;
|
||||||
|
installCommands?: { npm?: string; macos?: string; windows?: string };
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
};
|
||||||
|
}>)
|
||||||
|
| undefined;
|
||||||
|
if (getCodexStatus) {
|
||||||
try {
|
try {
|
||||||
const result = await api.setup.getCodexStatus();
|
const result = await getCodexStatus();
|
||||||
setDisplayCliStatus({
|
setDisplayCliStatus({
|
||||||
success: result.success,
|
success: result.success,
|
||||||
status: result.installed ? 'installed' : 'not_installed',
|
status: result.installed ? 'installed' : 'not_installed',
|
||||||
@@ -68,8 +84,8 @@ export function CodexSettingsTab() {
|
|||||||
});
|
});
|
||||||
setCodexCliStatus({
|
setCodexCliStatus({
|
||||||
installed: result.installed,
|
installed: result.installed,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
method: result.auth?.method || 'none',
|
method: result.auth?.method || 'none',
|
||||||
});
|
});
|
||||||
if (result.auth) {
|
if (result.auth) {
|
||||||
@@ -96,8 +112,24 @@ export function CodexSettingsTab() {
|
|||||||
setIsCheckingCodexCli(true);
|
setIsCheckingCodexCli(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (api?.setup?.getCodexStatus) {
|
// Check if getCodexStatus method exists on the API (may not be implemented yet)
|
||||||
const result = await api.setup.getCodexStatus();
|
const getCodexStatus = (api?.setup as Record<string, unknown> | undefined)?.getCodexStatus as
|
||||||
|
| (() => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
installed: boolean;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
recommendation?: string;
|
||||||
|
installCommands?: { npm?: string; macos?: string; windows?: string };
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
};
|
||||||
|
}>)
|
||||||
|
| undefined;
|
||||||
|
if (getCodexStatus) {
|
||||||
|
const result = await getCodexStatus();
|
||||||
setDisplayCliStatus({
|
setDisplayCliStatus({
|
||||||
success: result.success,
|
success: result.success,
|
||||||
status: result.installed ? 'installed' : 'not_installed',
|
status: result.installed ? 'installed' : 'not_installed',
|
||||||
@@ -109,8 +141,8 @@ export function CodexSettingsTab() {
|
|||||||
});
|
});
|
||||||
setCodexCliStatus({
|
setCodexCliStatus({
|
||||||
installed: result.installed,
|
installed: result.installed,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
method: result.auth?.method || 'none',
|
method: result.auth?.method || 'none',
|
||||||
});
|
});
|
||||||
if (result.auth) {
|
if (result.auth) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function CopilotSettingsTab() {
|
|||||||
// Server sends installCommand (singular), transform to expected format
|
// Server sends installCommand (singular), transform to expected format
|
||||||
installCommands: cliStatusData.installCommand
|
installCommands: cliStatusData.installCommand
|
||||||
? { npm: cliStatusData.installCommand }
|
? { npm: cliStatusData.installCommand }
|
||||||
: cliStatusData.installCommands,
|
: undefined,
|
||||||
};
|
};
|
||||||
}, [cliStatusData]);
|
}, [cliStatusData]);
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,9 @@ interface CursorPermissionsSectionProps {
|
|||||||
isSavingPermissions: boolean;
|
isSavingPermissions: boolean;
|
||||||
copiedConfig: boolean;
|
copiedConfig: boolean;
|
||||||
currentProject?: { path: string } | null;
|
currentProject?: { path: string } | null;
|
||||||
onApplyProfile: (
|
onApplyProfile: (profileId: 'strict' | 'development', scope: 'global' | 'project') => void;
|
||||||
profileId: 'strict' | 'development',
|
onCopyConfig: (profileId: 'strict' | 'development') => void;
|
||||||
scope: 'global' | 'project'
|
onLoadPermissions: () => void;
|
||||||
) => Promise<void>;
|
|
||||||
onCopyConfig: (profileId: 'strict' | 'development') => Promise<void>;
|
|
||||||
onLoadPermissions: () => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CursorPermissionsSection({
|
export function CursorPermissionsSection({
|
||||||
|
|||||||
@@ -54,13 +54,15 @@ export function OpencodeSettingsTab() {
|
|||||||
// Transform auth status to the expected format
|
// Transform auth status to the expected format
|
||||||
const authStatus = useMemo((): OpencodeAuthStatus | null => {
|
const authStatus = useMemo((): OpencodeAuthStatus | null => {
|
||||||
if (!cliStatusData?.auth) return null;
|
if (!cliStatusData?.auth) return null;
|
||||||
|
// Cast auth to include optional error field for type compatibility
|
||||||
|
const auth = cliStatusData.auth as typeof cliStatusData.auth & { error?: string };
|
||||||
return {
|
return {
|
||||||
authenticated: cliStatusData.auth.authenticated,
|
authenticated: auth.authenticated,
|
||||||
method: (cliStatusData.auth.method as OpencodeAuthStatus['method']) || 'none',
|
method: (auth.method as OpencodeAuthStatus['method']) || 'none',
|
||||||
hasApiKey: cliStatusData.auth.hasApiKey,
|
hasApiKey: auth.hasApiKey,
|
||||||
hasEnvApiKey: cliStatusData.auth.hasEnvApiKey,
|
hasEnvApiKey: auth.hasEnvApiKey,
|
||||||
hasOAuthToken: cliStatusData.auth.hasOAuthToken,
|
hasOAuthToken: auth.hasOAuthToken,
|
||||||
error: cliStatusData.auth.error,
|
error: auth.error,
|
||||||
};
|
};
|
||||||
}, [cliStatusData]);
|
}, [cliStatusData]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
|
import type { ModelProvider } from '@automaker/types';
|
||||||
|
import type { CliStatus } from '@/store/setup-store';
|
||||||
|
|
||||||
const logger = createLogger('CliInstallation');
|
const logger = createLogger('CliInstallation');
|
||||||
|
|
||||||
|
interface InstallApiResult {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InstallProgressEvent {
|
||||||
|
cli?: string;
|
||||||
|
data?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface UseCliInstallationOptions {
|
interface UseCliInstallationOptions {
|
||||||
cliType: 'claude';
|
cliType: ModelProvider;
|
||||||
installApi: () => Promise<any>;
|
installApi: () => Promise<InstallApiResult>;
|
||||||
onProgressEvent?: (callback: (progress: any) => void) => (() => void) | undefined;
|
onProgressEvent?: (
|
||||||
|
callback: (progress: InstallProgressEvent) => void
|
||||||
|
) => (() => void) | undefined;
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
getStoreState?: () => any;
|
getStoreState?: () => CliStatus | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCliInstallation({
|
export function useCliInstallation({
|
||||||
@@ -32,15 +48,13 @@ export function useCliInstallation({
|
|||||||
let unsubscribe: (() => void) | undefined;
|
let unsubscribe: (() => void) | undefined;
|
||||||
|
|
||||||
if (onProgressEvent) {
|
if (onProgressEvent) {
|
||||||
unsubscribe = onProgressEvent(
|
unsubscribe = onProgressEvent((progress: InstallProgressEvent) => {
|
||||||
(progress: { cli?: string; data?: string; type?: string }) => {
|
if (progress.cli === cliType) {
|
||||||
if (progress.cli === cliType) {
|
setInstallProgress((prev) => ({
|
||||||
setInstallProgress((prev) => ({
|
output: [...prev.output, progress.data || progress.type || ''],
|
||||||
output: [...prev.output, progress.data || progress.type || ''],
|
}));
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await installApi();
|
const result = await installApi();
|
||||||
|
|||||||
@@ -1,11 +1,38 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
|
import type { ModelProvider } from '@automaker/types';
|
||||||
|
import type { CliStatus, ClaudeAuthStatus, CodexAuthStatus } from '@/store/setup-store';
|
||||||
|
|
||||||
|
interface CliStatusApiResponse {
|
||||||
|
success: boolean;
|
||||||
|
status?: string;
|
||||||
|
installed?: boolean;
|
||||||
|
method?: string;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasCredentialsFile?: boolean;
|
||||||
|
hasToken?: boolean;
|
||||||
|
hasStoredOAuthToken?: boolean;
|
||||||
|
hasStoredApiKey?: boolean;
|
||||||
|
hasEnvApiKey?: boolean;
|
||||||
|
hasEnvOAuthToken?: boolean;
|
||||||
|
hasCliAuth?: boolean;
|
||||||
|
hasRecentActivity?: boolean;
|
||||||
|
hasAuthFile?: boolean;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
hasOAuthToken?: boolean;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface UseCliStatusOptions {
|
interface UseCliStatusOptions {
|
||||||
cliType: 'claude' | 'codex';
|
cliType: ModelProvider;
|
||||||
statusApi: () => Promise<any>;
|
statusApi: () => Promise<CliStatusApiResponse>;
|
||||||
setCliStatus: (status: any) => void;
|
setCliStatus: (status: CliStatus | null) => void;
|
||||||
setAuthStatus: (status: any) => void;
|
setAuthStatus: (status: ClaudeAuthStatus | CodexAuthStatus | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VALID_AUTH_METHODS = {
|
const VALID_AUTH_METHODS = {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck - CLI setup wizard with step validation and setup store state
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -45,6 +44,33 @@ type VerificationStatus = 'idle' | 'verifying' | 'verified' | 'error';
|
|||||||
|
|
||||||
type CliSetupAuthStatus = ClaudeAuthStatus | CodexAuthStatus;
|
type CliSetupAuthStatus = ClaudeAuthStatus | CodexAuthStatus;
|
||||||
|
|
||||||
|
interface CliStatusApiResponse {
|
||||||
|
success: boolean;
|
||||||
|
status?: 'installed' | 'not_installed';
|
||||||
|
installed?: boolean;
|
||||||
|
method?: string;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasCredentialsFile?: boolean;
|
||||||
|
hasStoredOAuthToken?: boolean;
|
||||||
|
hasStoredApiKey?: boolean;
|
||||||
|
hasEnvApiKey?: boolean;
|
||||||
|
hasEnvOAuthToken?: boolean;
|
||||||
|
hasAuthFile?: boolean;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InstallApiResponse {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface CliSetupConfig {
|
interface CliSetupConfig {
|
||||||
cliType: ModelProvider;
|
cliType: ModelProvider;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -73,8 +99,8 @@ interface CliSetupConfig {
|
|||||||
buildCliAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
buildCliAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
||||||
buildApiKeyAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
buildApiKeyAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
||||||
buildClearedAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
buildClearedAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus;
|
||||||
statusApi: () => Promise<any>;
|
statusApi: () => Promise<CliStatusApiResponse>;
|
||||||
installApi: () => Promise<any>;
|
installApi: () => Promise<InstallApiResponse>;
|
||||||
verifyAuthApi: (
|
verifyAuthApi: (
|
||||||
method: 'cli' | 'api_key',
|
method: 'cli' | 'api_key',
|
||||||
apiKey?: string
|
apiKey?: string
|
||||||
|
|||||||
@@ -55,13 +55,18 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
|
|||||||
}
|
}
|
||||||
const result = await api.setup.getOpencodeStatus();
|
const result = await api.setup.getOpencodeStatus();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
// Derive install command from platform-specific options or use npm fallback
|
||||||
|
const installCommand =
|
||||||
|
result.installCommands?.npm ||
|
||||||
|
result.installCommands?.macos ||
|
||||||
|
result.installCommands?.linux;
|
||||||
const status: OpencodeCliStatus = {
|
const status: OpencodeCliStatus = {
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
auth: result.auth,
|
auth: result.auth,
|
||||||
installCommand: result.installCommand,
|
installCommand,
|
||||||
loginCommand: result.loginCommand,
|
loginCommand: 'opencode auth login',
|
||||||
};
|
};
|
||||||
setOpencodeCliStatus(status);
|
setOpencodeCliStatus(status);
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ function ClaudeContent() {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setClaudeCliStatus({
|
setClaudeCliStatus({
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
method: 'none',
|
method: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -707,14 +707,21 @@ function CodexContent() {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setCodexCliStatus({
|
setCodexCliStatus({
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
method: 'none',
|
method: 'none',
|
||||||
});
|
});
|
||||||
if (result.auth?.authenticated) {
|
if (result.auth?.authenticated) {
|
||||||
|
const validMethods = ['api_key_env', 'api_key', 'cli_authenticated', 'none'] as const;
|
||||||
|
type CodexAuthMethod = (typeof validMethods)[number];
|
||||||
|
const method: CodexAuthMethod = validMethods.includes(
|
||||||
|
result.auth.method as CodexAuthMethod
|
||||||
|
)
|
||||||
|
? (result.auth.method as CodexAuthMethod)
|
||||||
|
: 'cli_authenticated';
|
||||||
setCodexAuthStatus({
|
setCodexAuthStatus({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
method: result.auth.method || 'cli_authenticated',
|
method,
|
||||||
});
|
});
|
||||||
toast.success('Codex CLI is ready!');
|
toast.success('Codex CLI is ready!');
|
||||||
}
|
}
|
||||||
@@ -997,13 +1004,18 @@ function OpencodeContent() {
|
|||||||
if (!api.setup?.getOpencodeStatus) return;
|
if (!api.setup?.getOpencodeStatus) return;
|
||||||
const result = await api.setup.getOpencodeStatus();
|
const result = await api.setup.getOpencodeStatus();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
// Derive install command from platform-specific options or use npm fallback
|
||||||
|
const installCommand =
|
||||||
|
result.installCommands?.npm ||
|
||||||
|
result.installCommands?.macos ||
|
||||||
|
result.installCommands?.linux;
|
||||||
setOpencodeCliStatus({
|
setOpencodeCliStatus({
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
auth: result.auth,
|
auth: result.auth,
|
||||||
installCommand: result.installCommand,
|
installCommand,
|
||||||
loginCommand: result.loginCommand,
|
loginCommand: 'opencode auth login',
|
||||||
});
|
});
|
||||||
if (result.auth?.authenticated) {
|
if (result.auth?.authenticated) {
|
||||||
toast.success('OpenCode CLI is ready!');
|
toast.success('OpenCode CLI is ready!');
|
||||||
@@ -1807,8 +1819,8 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setClaudeCliStatus({
|
setClaudeCliStatus({
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
method: 'none',
|
method: 'none',
|
||||||
});
|
});
|
||||||
// Note: Auth verification is handled by ClaudeContent component to avoid duplicate calls
|
// Note: Auth verification is handled by ClaudeContent component to avoid duplicate calls
|
||||||
@@ -1846,14 +1858,21 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setCodexCliStatus({
|
setCodexCliStatus({
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
method: 'none',
|
method: 'none',
|
||||||
});
|
});
|
||||||
if (result.auth?.authenticated) {
|
if (result.auth?.authenticated) {
|
||||||
|
const validMethods = ['api_key_env', 'api_key', 'cli_authenticated', 'none'] as const;
|
||||||
|
type CodexAuthMethodType = (typeof validMethods)[number];
|
||||||
|
const method: CodexAuthMethodType = validMethods.includes(
|
||||||
|
result.auth.method as CodexAuthMethodType
|
||||||
|
)
|
||||||
|
? (result.auth.method as CodexAuthMethodType)
|
||||||
|
: 'cli_authenticated';
|
||||||
setCodexAuthStatus({
|
setCodexAuthStatus({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
method: result.auth.method || 'cli_authenticated',
|
method,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1868,13 +1887,18 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
|
|||||||
if (!api.setup?.getOpencodeStatus) return;
|
if (!api.setup?.getOpencodeStatus) return;
|
||||||
const result = await api.setup.getOpencodeStatus();
|
const result = await api.setup.getOpencodeStatus();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
// Derive install command from platform-specific options or use npm fallback
|
||||||
|
const installCommand =
|
||||||
|
result.installCommands?.npm ||
|
||||||
|
result.installCommands?.macos ||
|
||||||
|
result.installCommands?.linux;
|
||||||
setOpencodeCliStatus({
|
setOpencodeCliStatus({
|
||||||
installed: result.installed ?? false,
|
installed: result.installed ?? false,
|
||||||
version: result.version,
|
version: result.version ?? null,
|
||||||
path: result.path,
|
path: result.path ?? null,
|
||||||
auth: result.auth,
|
auth: result.auth,
|
||||||
installCommand: result.installCommand,
|
installCommand,
|
||||||
loginCommand: result.loginCommand,
|
loginCommand: 'opencode auth login',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -310,9 +310,10 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
if (!node) return;
|
if (!node) return;
|
||||||
if (node.type === 'terminal') {
|
if (node.type === 'terminal') {
|
||||||
sessionIds.push(node.sessionId);
|
sessionIds.push(node.sessionId);
|
||||||
} else {
|
} else if (node.type === 'split') {
|
||||||
node.panels.forEach(collectFromLayout);
|
node.panels.forEach(collectFromLayout);
|
||||||
}
|
}
|
||||||
|
// testRunner type has sessionId but we only collect terminal sessions
|
||||||
};
|
};
|
||||||
terminalState.tabs.forEach((tab) => collectFromLayout(tab.layout));
|
terminalState.tabs.forEach((tab) => collectFromLayout(tab.layout));
|
||||||
return sessionIds;
|
return sessionIds;
|
||||||
@@ -620,7 +621,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
description: data.error || 'Unknown error',
|
description: data.error || 'Unknown error',
|
||||||
});
|
});
|
||||||
// Reset the handled ref so the same cwd can be retried
|
// Reset the handled ref so the same cwd can be retried
|
||||||
initialCwdHandledRef.current = undefined;
|
initialCwdHandledRef.current = null;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Create terminal with cwd error:', err);
|
logger.error('Create terminal with cwd error:', err);
|
||||||
@@ -628,7 +629,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
description: 'Could not connect to server',
|
description: 'Could not connect to server',
|
||||||
});
|
});
|
||||||
// Reset the handled ref so the same cwd can be retried
|
// Reset the handled ref so the same cwd can be retried
|
||||||
initialCwdHandledRef.current = undefined;
|
initialCwdHandledRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -791,6 +792,11 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle testRunner type - skip for now as we don't persist test runner sessions
|
||||||
|
if (persisted.type === 'testRunner') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// It's a split - rebuild all child panels
|
// It's a split - rebuild all child panels
|
||||||
const childPanels: TerminalPanelContent[] = [];
|
const childPanels: TerminalPanelContent[] = [];
|
||||||
for (const childPersisted of persisted.panels) {
|
for (const childPersisted of persisted.panels) {
|
||||||
@@ -1094,7 +1100,8 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
const collectSessionIds = (node: TerminalPanelContent | null): string[] => {
|
const collectSessionIds = (node: TerminalPanelContent | null): string[] => {
|
||||||
if (!node) return [];
|
if (!node) return [];
|
||||||
if (node.type === 'terminal') return [node.sessionId];
|
if (node.type === 'terminal') return [node.sessionId];
|
||||||
return node.panels.flatMap(collectSessionIds);
|
if (node.type === 'split') return node.panels.flatMap(collectSessionIds);
|
||||||
|
return []; // testRunner type
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessionIds = collectSessionIds(tab.layout);
|
const sessionIds = collectSessionIds(tab.layout);
|
||||||
@@ -1132,7 +1139,10 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
if (panel.type === 'terminal') {
|
if (panel.type === 'terminal') {
|
||||||
return [panel.sessionId];
|
return [panel.sessionId];
|
||||||
}
|
}
|
||||||
return panel.panels.flatMap(getTerminalIds);
|
if (panel.type === 'split') {
|
||||||
|
return panel.panels.flatMap(getTerminalIds);
|
||||||
|
}
|
||||||
|
return []; // testRunner type
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get a STABLE key for a panel - uses the stable id for splits
|
// Get a STABLE key for a panel - uses the stable id for splits
|
||||||
@@ -1141,8 +1151,12 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
if (panel.type === 'terminal') {
|
if (panel.type === 'terminal') {
|
||||||
return panel.sessionId;
|
return panel.sessionId;
|
||||||
}
|
}
|
||||||
// Use the stable id for split nodes
|
if (panel.type === 'split') {
|
||||||
return panel.id;
|
// Use the stable id for split nodes
|
||||||
|
return panel.id;
|
||||||
|
}
|
||||||
|
// testRunner - use sessionId
|
||||||
|
return panel.sessionId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const findTerminalFontSize = useCallback(
|
const findTerminalFontSize = useCallback(
|
||||||
@@ -1154,6 +1168,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (panel.type !== 'split') return null; // testRunner type
|
||||||
for (const child of panel.panels) {
|
for (const child of panel.panels) {
|
||||||
const found = findInPanel(child);
|
const found = findInPanel(child);
|
||||||
if (found !== null) return found;
|
if (found !== null) return found;
|
||||||
@@ -1208,7 +1223,8 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
// Helper to get all terminal IDs from a layout subtree
|
// Helper to get all terminal IDs from a layout subtree
|
||||||
const getAllTerminals = (node: TerminalPanelContent): string[] => {
|
const getAllTerminals = (node: TerminalPanelContent): string[] => {
|
||||||
if (node.type === 'terminal') return [node.sessionId];
|
if (node.type === 'terminal') return [node.sessionId];
|
||||||
return node.panels.flatMap(getAllTerminals);
|
if (node.type === 'split') return node.panels.flatMap(getAllTerminals);
|
||||||
|
return []; // testRunner type
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to find terminal and its path in the tree
|
// Helper to find terminal and its path in the tree
|
||||||
@@ -1225,6 +1241,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
if (node.type === 'terminal') {
|
if (node.type === 'terminal') {
|
||||||
return node.sessionId === target ? path : null;
|
return node.sessionId === target ? path : null;
|
||||||
}
|
}
|
||||||
|
if (node.type !== 'split') return null; // testRunner type
|
||||||
for (let i = 0; i < node.panels.length; i++) {
|
for (let i = 0; i < node.panels.length; i++) {
|
||||||
const result = findPath(node.panels[i], target, [
|
const result = findPath(node.panels[i], target, [
|
||||||
...path,
|
...path,
|
||||||
@@ -1354,6 +1371,11 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle testRunner type - return null for now
|
||||||
|
if (content.type === 'testRunner') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const isHorizontal = content.direction === 'horizontal';
|
const isHorizontal = content.direction === 'horizontal';
|
||||||
const defaultSizePerPanel = 100 / content.panels.length;
|
const defaultSizePerPanel = 100 / content.panels.length;
|
||||||
|
|
||||||
@@ -1365,7 +1387,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelGroup direction={content.direction} onLayout={handleLayoutChange}>
|
<PanelGroup direction={content.direction} onLayout={handleLayoutChange}>
|
||||||
{content.panels.map((panel, index) => {
|
{content.panels.map((panel: TerminalPanelContent, index: number) => {
|
||||||
const panelSize =
|
const panelSize =
|
||||||
panel.type === 'terminal' && panel.size ? panel.size : defaultSizePerPanel;
|
panel.type === 'terminal' && panel.size ? panel.size : defaultSizePerPanel;
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export function useStartFeature(projectPath: string) {
|
|||||||
worktreePath?: string;
|
worktreePath?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.runFeature(
|
const result = await api.autoMode.runFeature(
|
||||||
projectPath,
|
projectPath,
|
||||||
featureId,
|
featureId,
|
||||||
@@ -77,6 +78,7 @@ export function useResumeFeature(projectPath: string) {
|
|||||||
useWorktrees?: boolean;
|
useWorktrees?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.resumeFeature(projectPath, featureId, useWorktrees);
|
const result = await api.autoMode.resumeFeature(projectPath, featureId, useWorktrees);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to resume feature');
|
throw new Error(result.error || 'Failed to resume feature');
|
||||||
@@ -116,6 +118,7 @@ export function useStopFeature() {
|
|||||||
mutationFn: async (input: string | { featureId: string; projectPath?: string }) => {
|
mutationFn: async (input: string | { featureId: string; projectPath?: string }) => {
|
||||||
const featureId = typeof input === 'string' ? input : input.featureId;
|
const featureId = typeof input === 'string' ? input : input.featureId;
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.stopFeature(featureId);
|
const result = await api.autoMode.stopFeature(featureId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to stop feature');
|
throw new Error(result.error || 'Failed to stop feature');
|
||||||
@@ -151,6 +154,7 @@ export function useVerifyFeature(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (featureId: string) => {
|
mutationFn: async (featureId: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.verifyFeature(projectPath, featureId);
|
const result = await api.autoMode.verifyFeature(projectPath, featureId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to verify feature');
|
throw new Error(result.error || 'Failed to verify feature');
|
||||||
@@ -196,6 +200,7 @@ export function useApprovePlan(projectPath: string) {
|
|||||||
feedback?: string;
|
feedback?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.approvePlan(
|
const result = await api.autoMode.approvePlan(
|
||||||
projectPath,
|
projectPath,
|
||||||
featureId,
|
featureId,
|
||||||
@@ -246,6 +251,7 @@ export function useFollowUpFeature(projectPath: string) {
|
|||||||
useWorktrees?: boolean;
|
useWorktrees?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.followUpFeature(
|
const result = await api.autoMode.followUpFeature(
|
||||||
projectPath,
|
projectPath,
|
||||||
featureId,
|
featureId,
|
||||||
@@ -282,6 +288,7 @@ export function useCommitFeature(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (featureId: string) => {
|
mutationFn: async (featureId: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.commitFeature(projectPath, featureId);
|
const result = await api.autoMode.commitFeature(projectPath, featureId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to commit changes');
|
throw new Error(result.error || 'Failed to commit changes');
|
||||||
@@ -310,6 +317,7 @@ export function useAnalyzeProject() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (projectPath: string) => {
|
mutationFn: async (projectPath: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.analyzeProject(projectPath);
|
const result = await api.autoMode.analyzeProject(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to analyze project');
|
throw new Error(result.error || 'Failed to analyze project');
|
||||||
@@ -339,7 +347,8 @@ export function useStartAutoMode(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (maxConcurrency?: number) => {
|
mutationFn: async (maxConcurrency?: number) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.autoMode.start(projectPath, maxConcurrency);
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
|
const result = await api.autoMode.start(projectPath, undefined, maxConcurrency);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to start auto mode');
|
throw new Error(result.error || 'Failed to start auto mode');
|
||||||
}
|
}
|
||||||
@@ -369,6 +378,7 @@ export function useStopAutoMode(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
||||||
const result = await api.autoMode.stop(projectPath);
|
const result = await api.autoMode.stop(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to stop auto mode');
|
throw new Error(result.error || 'Failed to stop auto mode');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { getElectronAPI, GitHubIssue, GitHubComment } from '@/lib/electron';
|
import { getElectronAPI, GitHubIssue, GitHubComment } from '@/lib/electron';
|
||||||
import { queryKeys } from '@/lib/query-keys';
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { LinkedPRInfo, ModelId } from '@automaker/types';
|
import type { LinkedPRInfo, ModelId, ThinkingLevel, ReasoningEffort } from '@automaker/types';
|
||||||
import { resolveModelString } from '@automaker/model-resolver';
|
import { resolveModelString } from '@automaker/model-resolver';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,8 +17,8 @@ import { resolveModelString } from '@automaker/model-resolver';
|
|||||||
interface ValidateIssueInput {
|
interface ValidateIssueInput {
|
||||||
issue: GitHubIssue;
|
issue: GitHubIssue;
|
||||||
model?: ModelId;
|
model?: ModelId;
|
||||||
thinkingLevel?: number;
|
thinkingLevel?: ThinkingLevel;
|
||||||
reasoningEffort?: string;
|
reasoningEffort?: ReasoningEffort;
|
||||||
comments?: GitHubComment[];
|
comments?: GitHubComment[];
|
||||||
linkedPRs?: LinkedPRInfo[];
|
linkedPRs?: LinkedPRInfo[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export function useCreateWorktree(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ branchName, baseBranch }: { branchName: string; baseBranch?: string }) => {
|
mutationFn: async ({ branchName, baseBranch }: { branchName: string; baseBranch?: string }) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.create(projectPath, branchName, baseBranch);
|
const result = await api.worktree.create(projectPath, branchName, baseBranch);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to create worktree');
|
throw new Error(result.error || 'Failed to create worktree');
|
||||||
@@ -58,6 +59,7 @@ export function useDeleteWorktree(projectPath: string) {
|
|||||||
deleteBranch?: boolean;
|
deleteBranch?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.delete(projectPath, worktreePath, deleteBranch);
|
const result = await api.worktree.delete(projectPath, worktreePath, deleteBranch);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to delete worktree');
|
throw new Error(result.error || 'Failed to delete worktree');
|
||||||
@@ -87,6 +89,7 @@ export function useCommitWorktree() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ worktreePath, message }: { worktreePath: string; message: string }) => {
|
mutationFn: async ({ worktreePath, message }: { worktreePath: string; message: string }) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.commit(worktreePath, message);
|
const result = await api.worktree.commit(worktreePath, message);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to commit changes');
|
throw new Error(result.error || 'Failed to commit changes');
|
||||||
@@ -117,6 +120,7 @@ export function usePushWorktree() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ worktreePath, force }: { worktreePath: string; force?: boolean }) => {
|
mutationFn: async ({ worktreePath, force }: { worktreePath: string; force?: boolean }) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.push(worktreePath, force);
|
const result = await api.worktree.push(worktreePath, force);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to push changes');
|
throw new Error(result.error || 'Failed to push changes');
|
||||||
@@ -146,6 +150,7 @@ export function usePullWorktree() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (worktreePath: string) => {
|
mutationFn: async (worktreePath: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.pull(worktreePath);
|
const result = await api.worktree.pull(worktreePath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to pull changes');
|
throw new Error(result.error || 'Failed to pull changes');
|
||||||
@@ -188,6 +193,7 @@ export function useCreatePullRequest() {
|
|||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.createPR(worktreePath, options);
|
const result = await api.worktree.createPR(worktreePath, options);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to create pull request');
|
throw new Error(result.error || 'Failed to create pull request');
|
||||||
@@ -243,10 +249,12 @@ export function useMergeWorktree(projectPath: string) {
|
|||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.mergeFeature(
|
const result = await api.worktree.mergeFeature(
|
||||||
projectPath,
|
projectPath,
|
||||||
branchName,
|
branchName,
|
||||||
worktreePath,
|
worktreePath,
|
||||||
|
undefined, // targetBranch - use default (main)
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -284,6 +292,7 @@ export function useSwitchBranch() {
|
|||||||
branchName: string;
|
branchName: string;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.switchBranch(worktreePath, branchName);
|
const result = await api.worktree.switchBranch(worktreePath, branchName);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to switch branch');
|
throw new Error(result.error || 'Failed to switch branch');
|
||||||
@@ -319,6 +328,7 @@ export function useCheckoutBranch() {
|
|||||||
branchName: string;
|
branchName: string;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.checkoutBranch(worktreePath, branchName);
|
const result = await api.worktree.checkoutBranch(worktreePath, branchName);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to checkout branch');
|
throw new Error(result.error || 'Failed to checkout branch');
|
||||||
@@ -346,6 +356,7 @@ export function useGenerateCommitMessage() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (worktreePath: string) => {
|
mutationFn: async (worktreePath: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.generateCommitMessage(worktreePath);
|
const result = await api.worktree.generateCommitMessage(worktreePath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to generate commit message');
|
throw new Error(result.error || 'Failed to generate commit message');
|
||||||
@@ -375,6 +386,7 @@ export function useOpenInEditor() {
|
|||||||
editorCommand?: string;
|
editorCommand?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.openInEditor(worktreePath, editorCommand);
|
const result = await api.worktree.openInEditor(worktreePath, editorCommand);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to open in editor');
|
throw new Error(result.error || 'Failed to open in editor');
|
||||||
@@ -400,6 +412,7 @@ export function useInitGit() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (projectPath: string) => {
|
mutationFn: async (projectPath: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.initGit(projectPath);
|
const result = await api.worktree.initGit(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to initialize git');
|
throw new Error(result.error || 'Failed to initialize git');
|
||||||
@@ -431,6 +444,7 @@ export function useSetInitScript(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (content: string) => {
|
mutationFn: async (content: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.setInitScript(projectPath, content);
|
const result = await api.worktree.setInitScript(projectPath, content);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to save init script');
|
throw new Error(result.error || 'Failed to save init script');
|
||||||
@@ -461,6 +475,7 @@ export function useDeleteInitScript(projectPath: string) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) throw new Error('Worktree API not available');
|
||||||
const result = await api.worktree.deleteInitScript(projectPath);
|
const result = await api.worktree.deleteInitScript(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to delete init script');
|
throw new Error(result.error || 'Failed to delete init script');
|
||||||
|
|||||||
@@ -60,14 +60,13 @@ export {
|
|||||||
// CLI Status
|
// CLI Status
|
||||||
export {
|
export {
|
||||||
useClaudeCliStatus,
|
useClaudeCliStatus,
|
||||||
useCursorCliStatus,
|
|
||||||
useCodexCliStatus,
|
|
||||||
useOpencodeCliStatus,
|
|
||||||
useGeminiCliStatus,
|
|
||||||
useCopilotCliStatus,
|
|
||||||
useGitHubCliStatus,
|
useGitHubCliStatus,
|
||||||
useApiKeysStatus,
|
useApiKeysStatus,
|
||||||
usePlatformInfo,
|
usePlatformInfo,
|
||||||
|
useCursorCliStatus,
|
||||||
|
useCopilotCliStatus,
|
||||||
|
useGeminiCliStatus,
|
||||||
|
useOpencodeCliStatus,
|
||||||
} from './use-cli-status';
|
} from './use-cli-status';
|
||||||
|
|
||||||
// Ideation
|
// Ideation
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* CLI Status Query Hooks
|
* CLI Status Query Hooks
|
||||||
*
|
*
|
||||||
* React Query hooks for fetching CLI tool status (Claude, Cursor, Codex, etc.)
|
* React Query hooks for fetching CLI tool status (Claude, GitHub CLI, etc.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
@@ -19,6 +19,9 @@ export function useClaudeCliStatus() {
|
|||||||
queryKey: queryKeys.cli.claude(),
|
queryKey: queryKeys.cli.claude(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup) {
|
||||||
|
throw new Error('Setup API not available');
|
||||||
|
}
|
||||||
const result = await api.setup.getClaudeStatus();
|
const result = await api.setup.getClaudeStatus();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch Claude status');
|
throw new Error(result.error || 'Failed to fetch Claude status');
|
||||||
@@ -29,106 +32,6 @@ export function useClaudeCliStatus() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch Cursor CLI status
|
|
||||||
*
|
|
||||||
* @returns Query result with Cursor CLI status
|
|
||||||
*/
|
|
||||||
export function useCursorCliStatus() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: queryKeys.cli.cursor(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.setup.getCursorStatus();
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || 'Failed to fetch Cursor status');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch Codex CLI status
|
|
||||||
*
|
|
||||||
* @returns Query result with Codex CLI status
|
|
||||||
*/
|
|
||||||
export function useCodexCliStatus() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: queryKeys.cli.codex(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.setup.getCodexStatus();
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || 'Failed to fetch Codex status');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch OpenCode CLI status
|
|
||||||
*
|
|
||||||
* @returns Query result with OpenCode CLI status
|
|
||||||
*/
|
|
||||||
export function useOpencodeCliStatus() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: queryKeys.cli.opencode(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.setup.getOpencodeStatus();
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || 'Failed to fetch OpenCode status');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch Gemini CLI status
|
|
||||||
*
|
|
||||||
* @returns Query result with Gemini CLI status
|
|
||||||
*/
|
|
||||||
export function useGeminiCliStatus() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: queryKeys.cli.gemini(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.setup.getGeminiStatus();
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || 'Failed to fetch Gemini status');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch Copilot SDK status
|
|
||||||
*
|
|
||||||
* @returns Query result with Copilot SDK status
|
|
||||||
*/
|
|
||||||
export function useCopilotCliStatus() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: queryKeys.cli.copilot(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.setup.getCopilotStatus();
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || 'Failed to fetch Copilot status');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch GitHub CLI status
|
* Fetch GitHub CLI status
|
||||||
*
|
*
|
||||||
@@ -139,6 +42,9 @@ export function useGitHubCliStatus() {
|
|||||||
queryKey: queryKeys.cli.github(),
|
queryKey: queryKeys.cli.github(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getGhStatus) {
|
||||||
|
throw new Error('GitHub CLI status API not available');
|
||||||
|
}
|
||||||
const result = await api.setup.getGhStatus();
|
const result = await api.setup.getGhStatus();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch GitHub CLI status');
|
throw new Error(result.error || 'Failed to fetch GitHub CLI status');
|
||||||
@@ -159,7 +65,13 @@ export function useApiKeysStatus() {
|
|||||||
queryKey: queryKeys.cli.apiKeys(),
|
queryKey: queryKeys.cli.apiKeys(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup) {
|
||||||
|
throw new Error('Setup API not available');
|
||||||
|
}
|
||||||
const result = await api.setup.getApiKeys();
|
const result = await api.setup.getApiKeys();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error('Failed to fetch API keys');
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
@@ -176,6 +88,9 @@ export function usePlatformInfo() {
|
|||||||
queryKey: queryKeys.cli.platform(),
|
queryKey: queryKeys.cli.platform(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup) {
|
||||||
|
throw new Error('Setup API not available');
|
||||||
|
}
|
||||||
const result = await api.setup.getPlatform();
|
const result = await api.setup.getPlatform();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Failed to fetch platform info');
|
throw new Error('Failed to fetch platform info');
|
||||||
@@ -185,3 +100,95 @@ export function usePlatformInfo() {
|
|||||||
staleTime: Infinity, // Platform info never changes
|
staleTime: Infinity, // Platform info never changes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Cursor CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with Cursor CLI status
|
||||||
|
*/
|
||||||
|
export function useCursorCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.cursor(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getCursorStatus) {
|
||||||
|
throw new Error('Cursor CLI status API not available');
|
||||||
|
}
|
||||||
|
const result = await api.setup.getCursorStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Cursor CLI status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Copilot CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with Copilot CLI status
|
||||||
|
*/
|
||||||
|
export function useCopilotCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.copilot(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getCopilotStatus) {
|
||||||
|
throw new Error('Copilot CLI status API not available');
|
||||||
|
}
|
||||||
|
const result = await api.setup.getCopilotStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Copilot CLI status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Gemini CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with Gemini CLI status
|
||||||
|
*/
|
||||||
|
export function useGeminiCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.gemini(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getGeminiStatus) {
|
||||||
|
throw new Error('Gemini CLI status API not available');
|
||||||
|
}
|
||||||
|
const result = await api.setup.getGeminiStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Gemini CLI status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch OpenCode CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with OpenCode CLI status
|
||||||
|
*/
|
||||||
|
export function useOpencodeCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.opencode(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getOpencodeStatus) {
|
||||||
|
throw new Error('OpenCode CLI status API not available');
|
||||||
|
}
|
||||||
|
const result = await api.setup.getOpencodeStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch OpenCode CLI status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export function useGitDiffs(projectPath: string | undefined, enabled = true) {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.git) {
|
||||||
|
throw new Error('Git API not available');
|
||||||
|
}
|
||||||
const result = await api.git.getDiffs(projectPath);
|
const result = await api.git.getDiffs(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch diffs');
|
throw new Error(result.error || 'Failed to fetch diffs');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
|||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { queryKeys } from '@/lib/query-keys';
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
import { STALE_TIMES } from '@/lib/query-client';
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
import type { GitHubIssue, GitHubPR, GitHubComment, IssueValidation } from '@/lib/electron';
|
import type { GitHubIssue, GitHubPR, GitHubComment, StoredValidation } from '@/lib/electron';
|
||||||
|
|
||||||
interface GitHubIssuesResult {
|
interface GitHubIssuesResult {
|
||||||
openIssues: GitHubIssue[];
|
openIssues: GitHubIssue[];
|
||||||
@@ -38,6 +38,9 @@ export function useGitHubIssues(projectPath: string | undefined) {
|
|||||||
queryFn: async (): Promise<GitHubIssuesResult> => {
|
queryFn: async (): Promise<GitHubIssuesResult> => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.github) {
|
||||||
|
throw new Error('GitHub API not available');
|
||||||
|
}
|
||||||
const result = await api.github.listIssues(projectPath);
|
const result = await api.github.listIssues(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch issues');
|
throw new Error(result.error || 'Failed to fetch issues');
|
||||||
@@ -64,6 +67,9 @@ export function useGitHubPRs(projectPath: string | undefined) {
|
|||||||
queryFn: async (): Promise<GitHubPRsResult> => {
|
queryFn: async (): Promise<GitHubPRsResult> => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.github) {
|
||||||
|
throw new Error('GitHub API not available');
|
||||||
|
}
|
||||||
const result = await api.github.listPRs(projectPath);
|
const result = await api.github.listPRs(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch PRs');
|
throw new Error(result.error || 'Failed to fetch PRs');
|
||||||
@@ -90,9 +96,12 @@ export function useGitHubValidations(projectPath: string | undefined, issueNumbe
|
|||||||
queryKey: issueNumber
|
queryKey: issueNumber
|
||||||
? queryKeys.github.validation(projectPath ?? '', issueNumber)
|
? queryKeys.github.validation(projectPath ?? '', issueNumber)
|
||||||
: queryKeys.github.validations(projectPath ?? ''),
|
: queryKeys.github.validations(projectPath ?? ''),
|
||||||
queryFn: async (): Promise<IssueValidation[]> => {
|
queryFn: async (): Promise<StoredValidation[]> => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.github) {
|
||||||
|
throw new Error('GitHub API not available');
|
||||||
|
}
|
||||||
const result = await api.github.getValidations(projectPath, issueNumber);
|
const result = await api.github.getValidations(projectPath, issueNumber);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch validations');
|
throw new Error(result.error || 'Failed to fetch validations');
|
||||||
@@ -116,15 +125,18 @@ export function useGitHubRemote(projectPath: string | undefined) {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.github) {
|
||||||
|
throw new Error('GitHub API not available');
|
||||||
|
}
|
||||||
const result = await api.github.checkRemote(projectPath);
|
const result = await api.github.checkRemote(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to check remote');
|
throw new Error(result.error || 'Failed to check remote');
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
hasRemote: result.hasRemote ?? false,
|
hasRemote: result.hasGitHubRemote ?? false,
|
||||||
owner: result.owner,
|
owner: result.owner,
|
||||||
repo: result.repo,
|
repo: result.repo,
|
||||||
url: result.url,
|
url: result.remoteUrl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
enabled: !!projectPath,
|
enabled: !!projectPath,
|
||||||
@@ -165,6 +177,9 @@ export function useGitHubIssueComments(
|
|||||||
queryFn: async ({ pageParam }: { pageParam: string | undefined }) => {
|
queryFn: async ({ pageParam }: { pageParam: string | undefined }) => {
|
||||||
if (!projectPath || !issueNumber) throw new Error('Missing project path or issue number');
|
if (!projectPath || !issueNumber) throw new Error('Missing project path or issue number');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.github) {
|
||||||
|
throw new Error('GitHub API not available');
|
||||||
|
}
|
||||||
const result = await api.github.getIssueComments(projectPath, issueNumber, pageParam);
|
const result = await api.github.getIssueComments(projectPath, issueNumber, pageParam);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch comments');
|
throw new Error(result.error || 'Failed to fetch comments');
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { queryKeys } from '@/lib/query-keys';
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
import { STALE_TIMES } from '@/lib/query-client';
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { ModelDefinition } from '@automaker/types';
|
||||||
|
|
||||||
interface CodexModel {
|
interface CodexModel {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -19,18 +20,6 @@ interface CodexModel {
|
|||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OpencodeModel {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
modelString: string;
|
|
||||||
provider: string;
|
|
||||||
description: string;
|
|
||||||
supportsTools: boolean;
|
|
||||||
supportsVision: boolean;
|
|
||||||
tier: string;
|
|
||||||
default?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch available models
|
* Fetch available models
|
||||||
*
|
*
|
||||||
@@ -41,6 +30,9 @@ export function useAvailableModels() {
|
|||||||
queryKey: queryKeys.models.available(),
|
queryKey: queryKeys.models.available(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.model) {
|
||||||
|
throw new Error('Model API not available');
|
||||||
|
}
|
||||||
const result = await api.model.getAvailable();
|
const result = await api.model.getAvailable();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch available models');
|
throw new Error(result.error || 'Failed to fetch available models');
|
||||||
@@ -62,6 +54,9 @@ export function useCodexModels(refresh = false) {
|
|||||||
queryKey: queryKeys.models.codex(),
|
queryKey: queryKeys.models.codex(),
|
||||||
queryFn: async (): Promise<CodexModel[]> => {
|
queryFn: async (): Promise<CodexModel[]> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.codex) {
|
||||||
|
throw new Error('Codex API not available');
|
||||||
|
}
|
||||||
const result = await api.codex.getModels(refresh);
|
const result = await api.codex.getModels(refresh);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch Codex models');
|
throw new Error(result.error || 'Failed to fetch Codex models');
|
||||||
@@ -81,13 +76,16 @@ export function useCodexModels(refresh = false) {
|
|||||||
export function useOpencodeModels(refresh = false) {
|
export function useOpencodeModels(refresh = false) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: queryKeys.models.opencode(),
|
queryKey: queryKeys.models.opencode(),
|
||||||
queryFn: async (): Promise<OpencodeModel[]> => {
|
queryFn: async (): Promise<ModelDefinition[]> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getOpencodeModels) {
|
||||||
|
throw new Error('OpenCode models API not available');
|
||||||
|
}
|
||||||
const result = await api.setup.getOpencodeModels(refresh);
|
const result = await api.setup.getOpencodeModels(refresh);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch OpenCode models');
|
throw new Error(result.error || 'Failed to fetch OpenCode models');
|
||||||
}
|
}
|
||||||
return (result.models ?? []) as OpencodeModel[];
|
return (result.models ?? []) as ModelDefinition[];
|
||||||
},
|
},
|
||||||
staleTime: STALE_TIMES.MODELS,
|
staleTime: STALE_TIMES.MODELS,
|
||||||
});
|
});
|
||||||
@@ -103,6 +101,9 @@ export function useOpencodeProviders() {
|
|||||||
queryKey: queryKeys.models.opencodeProviders(),
|
queryKey: queryKeys.models.opencodeProviders(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.setup?.getOpencodeProviders) {
|
||||||
|
throw new Error('OpenCode providers API not available');
|
||||||
|
}
|
||||||
const result = await api.setup.getOpencodeProviders();
|
const result = await api.setup.getOpencodeProviders();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch OpenCode providers');
|
throw new Error(result.error || 'Failed to fetch OpenCode providers');
|
||||||
@@ -123,6 +124,9 @@ export function useModelProviders() {
|
|||||||
queryKey: queryKeys.models.providers(),
|
queryKey: queryKeys.models.providers(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.model) {
|
||||||
|
throw new Error('Model API not available');
|
||||||
|
}
|
||||||
const result = await api.model.checkProviders();
|
const result = await api.model.checkProviders();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch providers');
|
throw new Error(result.error || 'Failed to fetch providers');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
import { queryKeys } from '@/lib/query-keys';
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
import { STALE_TIMES } from '@/lib/query-client';
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
import type { PipelineConfig } from '@/store/app-store';
|
import type { PipelineConfig } from '@automaker/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch pipeline config for a project
|
* Fetch pipeline config for a project
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export function useRunningAgents() {
|
|||||||
queryKey: queryKeys.runningAgents.all(),
|
queryKey: queryKeys.runningAgents.all(),
|
||||||
queryFn: async (): Promise<RunningAgentsResult> => {
|
queryFn: async (): Promise<RunningAgentsResult> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.runningAgents) {
|
||||||
|
throw new Error('Running agents API not available');
|
||||||
|
}
|
||||||
const result = await api.runningAgents.getAll();
|
const result = await api.runningAgents.getAll();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch running agents');
|
throw new Error(result.error || 'Failed to fetch running agents');
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export function useSessions(includeArchived = false) {
|
|||||||
queryKey: queryKeys.sessions.all(includeArchived),
|
queryKey: queryKeys.sessions.all(includeArchived),
|
||||||
queryFn: async (): Promise<SessionListItem[]> => {
|
queryFn: async (): Promise<SessionListItem[]> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.sessions) {
|
||||||
|
throw new Error('Sessions API not available');
|
||||||
|
}
|
||||||
const result = await api.sessions.list(includeArchived);
|
const result = await api.sessions.list(includeArchived);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch sessions');
|
throw new Error(result.error || 'Failed to fetch sessions');
|
||||||
@@ -48,6 +51,9 @@ export function useSessionHistory(sessionId: string | undefined) {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!sessionId) throw new Error('No session ID');
|
if (!sessionId) throw new Error('No session ID');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.agent) {
|
||||||
|
throw new Error('Agent API not available');
|
||||||
|
}
|
||||||
const result = await api.agent.getHistory(sessionId);
|
const result = await api.agent.getHistory(sessionId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch session history');
|
throw new Error(result.error || 'Failed to fetch session history');
|
||||||
@@ -74,6 +80,9 @@ export function useSessionQueue(sessionId: string | undefined) {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!sessionId) throw new Error('No session ID');
|
if (!sessionId) throw new Error('No session ID');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.agent) {
|
||||||
|
throw new Error('Agent API not available');
|
||||||
|
}
|
||||||
const result = await api.agent.queueList(sessionId);
|
const result = await api.agent.queueList(sessionId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch queue');
|
throw new Error(result.error || 'Failed to fetch queue');
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ export function useGlobalSettings() {
|
|||||||
queryKey: queryKeys.settings.global(),
|
queryKey: queryKeys.settings.global(),
|
||||||
queryFn: async (): Promise<GlobalSettings> => {
|
queryFn: async (): Promise<GlobalSettings> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
const result = await api.settings.getGlobal();
|
const result = await api.settings.getGlobal();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch global settings');
|
throw new Error(result.error || 'Failed to fetch global settings');
|
||||||
}
|
}
|
||||||
return result.settings as GlobalSettings;
|
return result.settings as unknown as GlobalSettings;
|
||||||
},
|
},
|
||||||
staleTime: STALE_TIMES.SETTINGS,
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
});
|
});
|
||||||
@@ -47,11 +50,14 @@ export function useProjectSettings(projectPath: string | undefined) {
|
|||||||
queryFn: async (): Promise<ProjectSettings> => {
|
queryFn: async (): Promise<ProjectSettings> => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
const result = await api.settings.getProject(projectPath);
|
const result = await api.settings.getProject(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch project settings');
|
throw new Error(result.error || 'Failed to fetch project settings');
|
||||||
}
|
}
|
||||||
return result.settings as ProjectSettings;
|
return result.settings as unknown as ProjectSettings;
|
||||||
},
|
},
|
||||||
enabled: !!projectPath,
|
enabled: !!projectPath,
|
||||||
staleTime: STALE_TIMES.SETTINGS,
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
@@ -68,6 +74,9 @@ export function useSettingsStatus() {
|
|||||||
queryKey: queryKeys.settings.status(),
|
queryKey: queryKeys.settings.status(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
const result = await api.settings.getStatus();
|
const result = await api.settings.getStatus();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
@@ -85,6 +94,9 @@ export function useCredentials() {
|
|||||||
queryKey: queryKeys.settings.credentials(),
|
queryKey: queryKeys.settings.credentials(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
const result = await api.settings.getCredentials();
|
const result = await api.settings.getCredentials();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch credentials');
|
throw new Error(result.error || 'Failed to fetch credentials');
|
||||||
@@ -111,6 +123,9 @@ export function useDiscoveredAgents(
|
|||||||
queryKey: queryKeys.settings.agents(projectPath ?? '', sources),
|
queryKey: queryKeys.settings.agents(projectPath ?? '', sources),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
const result = await api.settings.discoverAgents(projectPath, sources);
|
const result = await api.settings.discoverAgents(projectPath, sources);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to discover agents');
|
throw new Error(result.error || 'Failed to discover agents');
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ export function useClaudeUsage(enabled = true) {
|
|||||||
queryKey: queryKeys.usage.claude(),
|
queryKey: queryKeys.usage.claude(),
|
||||||
queryFn: async (): Promise<ClaudeUsage> => {
|
queryFn: async (): Promise<ClaudeUsage> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.claude) {
|
||||||
|
throw new Error('Claude API not available');
|
||||||
|
}
|
||||||
const result = await api.claude.getUsage();
|
const result = await api.claude.getUsage();
|
||||||
// Check if result is an error response
|
// Check if result is an error response
|
||||||
if ('error' in result) {
|
if ('error' in result) {
|
||||||
@@ -65,6 +68,9 @@ export function useCodexUsage(enabled = true) {
|
|||||||
queryKey: queryKeys.usage.codex(),
|
queryKey: queryKeys.usage.codex(),
|
||||||
queryFn: async (): Promise<CodexUsage> => {
|
queryFn: async (): Promise<CodexUsage> => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.codex) {
|
||||||
|
throw new Error('Codex API not available');
|
||||||
|
}
|
||||||
const result = await api.codex.getUsage();
|
const result = await api.codex.getUsage();
|
||||||
// Check if result is an error response
|
// Check if result is an error response
|
||||||
if ('error' in result) {
|
if ('error' in result) {
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ export function useWorktrees(projectPath: string | undefined, includeDetails = t
|
|||||||
queryFn: async (): Promise<WorktreesResult> => {
|
queryFn: async (): Promise<WorktreesResult> => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.listAll(projectPath, includeDetails);
|
const result = await api.worktree.listAll(projectPath, includeDetails);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch worktrees');
|
throw new Error(result.error || 'Failed to fetch worktrees');
|
||||||
@@ -80,6 +83,9 @@ export function useWorktreeInfo(projectPath: string | undefined, featureId: stri
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.getInfo(projectPath, featureId);
|
const result = await api.worktree.getInfo(projectPath, featureId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch worktree info');
|
throw new Error(result.error || 'Failed to fetch worktree info');
|
||||||
@@ -106,6 +112,9 @@ export function useWorktreeStatus(projectPath: string | undefined, featureId: st
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.getStatus(projectPath, featureId);
|
const result = await api.worktree.getStatus(projectPath, featureId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch worktree status');
|
throw new Error(result.error || 'Failed to fetch worktree status');
|
||||||
@@ -132,6 +141,9 @@ export function useWorktreeDiffs(projectPath: string | undefined, featureId: str
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.getDiffs(projectPath, featureId);
|
const result = await api.worktree.getDiffs(projectPath, featureId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch diffs');
|
throw new Error(result.error || 'Failed to fetch diffs');
|
||||||
@@ -180,6 +192,9 @@ export function useWorktreeBranches(worktreePath: string | undefined, includeRem
|
|||||||
queryFn: async (): Promise<BranchesResult> => {
|
queryFn: async (): Promise<BranchesResult> => {
|
||||||
if (!worktreePath) throw new Error('No worktree path');
|
if (!worktreePath) throw new Error('No worktree path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.listBranches(worktreePath, includeRemote);
|
const result = await api.worktree.listBranches(worktreePath, includeRemote);
|
||||||
|
|
||||||
// Handle special git status codes
|
// Handle special git status codes
|
||||||
@@ -239,6 +254,9 @@ export function useWorktreeInitScript(projectPath: string | undefined) {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!projectPath) throw new Error('No project path');
|
if (!projectPath) throw new Error('No project path');
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.getInitScript(projectPath);
|
const result = await api.worktree.getInitScript(projectPath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch init script');
|
throw new Error(result.error || 'Failed to fetch init script');
|
||||||
@@ -265,11 +283,14 @@ export function useAvailableEditors() {
|
|||||||
queryKey: queryKeys.worktrees.editors(),
|
queryKey: queryKeys.worktrees.editors(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.worktree) {
|
||||||
|
throw new Error('Worktree API not available');
|
||||||
|
}
|
||||||
const result = await api.worktree.getAvailableEditors();
|
const result = await api.worktree.getAvailableEditors();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to fetch editors');
|
throw new Error(result.error || 'Failed to fetch editors');
|
||||||
}
|
}
|
||||||
return result.editors ?? [];
|
return result.result?.editors ?? [];
|
||||||
},
|
},
|
||||||
staleTime: STALE_TIMES.CLI_STATUS,
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
refetchOnWindowFocus: WORKTREE_REFETCH_ON_FOCUS,
|
refetchOnWindowFocus: WORKTREE_REFETCH_ON_FOCUS,
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function useProjectSettingsLoader() {
|
|||||||
// These are stored directly on the project, so we need to update both
|
// These are stored directly on the project, so we need to update both
|
||||||
// currentProject AND the projects array to keep them in sync
|
// currentProject AND the projects array to keep them in sync
|
||||||
// Type assertion needed because API returns Record<string, unknown>
|
// Type assertion needed because API returns Record<string, unknown>
|
||||||
const settingsWithExtras = settings as Record<string, unknown>;
|
const settingsWithExtras = settings as unknown as Record<string, unknown>;
|
||||||
const activeClaudeApiProfileId = settingsWithExtras.activeClaudeApiProfileId as
|
const activeClaudeApiProfileId = settingsWithExtras.activeClaudeApiProfileId as
|
||||||
| string
|
| string
|
||||||
| null
|
| null
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useEffect, useRef } from 'react';
|
|||||||
import { useQueryClient, QueryClient } from '@tanstack/react-query';
|
import { useQueryClient, QueryClient } from '@tanstack/react-query';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { queryKeys } from '@/lib/query-keys';
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
import type { AutoModeEvent, SpecRegenerationEvent } from '@/types/electron';
|
import type { AutoModeEvent, SpecRegenerationEvent, StreamEvent } from '@/types/electron';
|
||||||
import type { IssueValidationEvent } from '@automaker/types';
|
import type { IssueValidationEvent } from '@automaker/types';
|
||||||
import { debounce, type DebouncedFunction } from '@automaker/utils/debounce';
|
import { debounce, type DebouncedFunction } from '@automaker/utils/debounce';
|
||||||
import { useEventRecencyStore } from './use-event-recency';
|
import { useEventRecencyStore } from './use-event-recency';
|
||||||
@@ -165,6 +165,7 @@ export function useAutoModeQueryInvalidation(projectPath: string | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.autoMode) return;
|
||||||
const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => {
|
const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => {
|
||||||
// Record that we received a WebSocket event (for event recency tracking)
|
// Record that we received a WebSocket event (for event recency tracking)
|
||||||
// This allows polling to be disabled when WebSocket events are flowing
|
// This allows polling to be disabled when WebSocket events are flowing
|
||||||
@@ -241,6 +242,7 @@ export function useSpecRegenerationQueryInvalidation(projectPath: string | undef
|
|||||||
if (!projectPath) return;
|
if (!projectPath) return;
|
||||||
|
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.specRegeneration) return;
|
||||||
const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => {
|
const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => {
|
||||||
// Only handle events for the current project
|
// Only handle events for the current project
|
||||||
if (event.projectPath !== projectPath) return;
|
if (event.projectPath !== projectPath) return;
|
||||||
@@ -288,14 +290,14 @@ export function useGitHubValidationQueryInvalidation(projectPath: string | undef
|
|||||||
// Record that we received a WebSocket event
|
// Record that we received a WebSocket event
|
||||||
recordGlobalEvent();
|
recordGlobalEvent();
|
||||||
|
|
||||||
if (event.type === 'validation_complete' || event.type === 'validation_error') {
|
if (event.type === 'issue_validation_complete' || event.type === 'issue_validation_error') {
|
||||||
// Invalidate all validations for this project
|
// Invalidate all validations for this project
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.github.validations(projectPath),
|
queryKey: queryKeys.github.validations(projectPath),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also invalidate specific issue validation if we have the issue number
|
// Also invalidate specific issue validation if we have the issue number
|
||||||
if ('issueNumber' in event && event.issueNumber) {
|
if (event.issueNumber) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.github.validation(projectPath, event.issueNumber),
|
queryKey: queryKeys.github.validation(projectPath, event.issueNumber),
|
||||||
});
|
});
|
||||||
@@ -320,7 +322,9 @@ export function useSessionQueryInvalidation(sessionId: string | undefined) {
|
|||||||
if (!sessionId) return;
|
if (!sessionId) return;
|
||||||
|
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const unsubscribe = api.agent.onStream((event) => {
|
if (!api.agent) return;
|
||||||
|
const unsubscribe = api.agent.onStream((data: unknown) => {
|
||||||
|
const event = data as StreamEvent;
|
||||||
// Only handle events for the current session
|
// Only handle events for the current session
|
||||||
if ('sessionId' in event && event.sessionId !== sessionId) return;
|
if ('sessionId' in event && event.sessionId !== sessionId) return;
|
||||||
|
|
||||||
|
|||||||
@@ -668,8 +668,9 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
maxConcurrency: number;
|
maxConcurrency: number;
|
||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
if ((settings as Record<string, unknown>).autoModeByWorktree) {
|
if ((settings as unknown as Record<string, unknown>).autoModeByWorktree) {
|
||||||
const persistedSettings = (settings as Record<string, unknown>).autoModeByWorktree as Record<
|
const persistedSettings = (settings as unknown as Record<string, unknown>)
|
||||||
|
.autoModeByWorktree as Record<
|
||||||
string,
|
string,
|
||||||
{ maxConcurrency?: number; branchName?: string | null }
|
{ maxConcurrency?: number; branchName?: string | null }
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
DEFAULT_MAX_CONCURRENCY,
|
DEFAULT_MAX_CONCURRENCY,
|
||||||
getAllOpencodeModelIds,
|
getAllOpencodeModelIds,
|
||||||
getAllCursorModelIds,
|
getAllCursorModelIds,
|
||||||
getAllCodexModelIds,
|
|
||||||
getAllGeminiModelIds,
|
getAllGeminiModelIds,
|
||||||
getAllCopilotModelIds,
|
getAllCopilotModelIds,
|
||||||
migrateCursorModelIds,
|
migrateCursorModelIds,
|
||||||
@@ -34,7 +33,6 @@ import {
|
|||||||
migratePhaseModelEntry,
|
migratePhaseModelEntry,
|
||||||
type GlobalSettings,
|
type GlobalSettings,
|
||||||
type CursorModelId,
|
type CursorModelId,
|
||||||
type CodexModelId,
|
|
||||||
type GeminiModelId,
|
type GeminiModelId,
|
||||||
type CopilotModelId,
|
type CopilotModelId,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
@@ -76,8 +74,6 @@ const SETTINGS_FIELDS_TO_SYNC = [
|
|||||||
'cursorDefaultModel',
|
'cursorDefaultModel',
|
||||||
'enabledOpencodeModels',
|
'enabledOpencodeModels',
|
||||||
'opencodeDefaultModel',
|
'opencodeDefaultModel',
|
||||||
'enabledCodexModels',
|
|
||||||
'codexDefaultModel',
|
|
||||||
'enabledGeminiModels',
|
'enabledGeminiModels',
|
||||||
'geminiDefaultModel',
|
'geminiDefaultModel',
|
||||||
'enabledCopilotModels',
|
'enabledCopilotModels',
|
||||||
@@ -282,8 +278,10 @@ export function useSettingsSync(): SettingsSyncState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info('[SYNC_SEND] Sending settings update to server:', {
|
logger.info('[SYNC_SEND] Sending settings update to server:', {
|
||||||
projects: (updates.projects as any)?.length ?? 0,
|
projects: Array.isArray(updates.projects) ? updates.projects.length : 0,
|
||||||
trashedProjects: (updates.trashedProjects as any)?.length ?? 0,
|
trashedProjects: Array.isArray(updates.trashedProjects)
|
||||||
|
? updates.trashedProjects.length
|
||||||
|
: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await api.settings.updateGlobal(updates);
|
const result = await api.settings.updateGlobal(updates);
|
||||||
@@ -583,22 +581,6 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
|||||||
sanitizedEnabledOpencodeModels.push(sanitizedOpencodeDefaultModel);
|
sanitizedEnabledOpencodeModels.push(sanitizedOpencodeDefaultModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize Codex models
|
|
||||||
const validCodexModelIds = new Set(getAllCodexModelIds());
|
|
||||||
const DEFAULT_CODEX_MODEL: CodexModelId = 'codex-gpt-5.2-codex';
|
|
||||||
const sanitizedEnabledCodexModels = (serverSettings.enabledCodexModels ?? []).filter(
|
|
||||||
(id): id is CodexModelId => validCodexModelIds.has(id as CodexModelId)
|
|
||||||
);
|
|
||||||
const sanitizedCodexDefaultModel = validCodexModelIds.has(
|
|
||||||
serverSettings.codexDefaultModel as CodexModelId
|
|
||||||
)
|
|
||||||
? (serverSettings.codexDefaultModel as CodexModelId)
|
|
||||||
: DEFAULT_CODEX_MODEL;
|
|
||||||
|
|
||||||
if (!sanitizedEnabledCodexModels.includes(sanitizedCodexDefaultModel)) {
|
|
||||||
sanitizedEnabledCodexModels.push(sanitizedCodexDefaultModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize Gemini models
|
// Sanitize Gemini models
|
||||||
const validGeminiModelIds = new Set(getAllGeminiModelIds());
|
const validGeminiModelIds = new Set(getAllGeminiModelIds());
|
||||||
const sanitizedEnabledGeminiModels = (serverSettings.enabledGeminiModels ?? []).filter(
|
const sanitizedEnabledGeminiModels = (serverSettings.enabledGeminiModels ?? []).filter(
|
||||||
@@ -724,8 +706,6 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
|||||||
cursorDefaultModel: sanitizedCursorDefault,
|
cursorDefaultModel: sanitizedCursorDefault,
|
||||||
enabledOpencodeModels: sanitizedEnabledOpencodeModels,
|
enabledOpencodeModels: sanitizedEnabledOpencodeModels,
|
||||||
opencodeDefaultModel: sanitizedOpencodeDefaultModel,
|
opencodeDefaultModel: sanitizedOpencodeDefaultModel,
|
||||||
enabledCodexModels: sanitizedEnabledCodexModels,
|
|
||||||
codexDefaultModel: sanitizedCodexDefaultModel,
|
|
||||||
enabledGeminiModels: sanitizedEnabledGeminiModels,
|
enabledGeminiModels: sanitizedEnabledGeminiModels,
|
||||||
geminiDefaultModel: sanitizedGeminiDefaultModel,
|
geminiDefaultModel: sanitizedGeminiDefaultModel,
|
||||||
enabledCopilotModels: sanitizedEnabledCopilotModels,
|
enabledCopilotModels: sanitizedEnabledCopilotModels,
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import type {
|
|||||||
UpdateIdeaInput,
|
UpdateIdeaInput,
|
||||||
ConvertToFeatureOptions,
|
ConvertToFeatureOptions,
|
||||||
IdeationContextSources,
|
IdeationContextSources,
|
||||||
|
Feature,
|
||||||
|
IdeationStreamEvent,
|
||||||
|
IdeationAnalysisEvent,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import { DEFAULT_MAX_CONCURRENCY } from '@automaker/types';
|
import { DEFAULT_MAX_CONCURRENCY } from '@automaker/types';
|
||||||
import { getJSON, setJSON, removeItem } from './storage';
|
import { getJSON, setJSON, removeItem } from './storage';
|
||||||
@@ -124,7 +127,7 @@ export interface IdeationAPI {
|
|||||||
projectPath: string,
|
projectPath: string,
|
||||||
ideaId: string,
|
ideaId: string,
|
||||||
options?: ConvertToFeatureOptions
|
options?: ConvertToFeatureOptions
|
||||||
) => Promise<{ success: boolean; feature?: any; featureId?: string; error?: string }>;
|
) => Promise<{ success: boolean; feature?: Feature; featureId?: string; error?: string }>;
|
||||||
|
|
||||||
// Add suggestion directly to board as feature
|
// Add suggestion directly to board as feature
|
||||||
addSuggestionToBoard: (
|
addSuggestionToBoard: (
|
||||||
@@ -141,8 +144,8 @@ export interface IdeationAPI {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Event subscriptions
|
// Event subscriptions
|
||||||
onStream: (callback: (event: any) => void) => () => void;
|
onStream: (callback: (event: IdeationStreamEvent) => void) => () => void;
|
||||||
onAnalysisEvent: (callback: (event: any) => void) => () => void;
|
onAnalysisEvent: (callback: (event: IdeationAnalysisEvent) => void) => () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileEntry {
|
export interface FileEntry {
|
||||||
@@ -186,6 +189,16 @@ export interface StatResult {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Options for creating a pull request
|
||||||
|
export interface CreatePROptions {
|
||||||
|
projectPath?: string;
|
||||||
|
commitMessage?: string;
|
||||||
|
prTitle?: string;
|
||||||
|
prBody?: string;
|
||||||
|
baseBranch?: string;
|
||||||
|
draft?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// Re-export types from electron.d.ts for external use
|
// Re-export types from electron.d.ts for external use
|
||||||
export type {
|
export type {
|
||||||
AutoModeEvent,
|
AutoModeEvent,
|
||||||
@@ -212,9 +225,6 @@ import type {
|
|||||||
// Import HTTP API client (ES module)
|
// Import HTTP API client (ES module)
|
||||||
import { getHttpApiClient, getServerUrlSync } from './http-api-client';
|
import { getHttpApiClient, getServerUrlSync } from './http-api-client';
|
||||||
|
|
||||||
// Feature type - Import from app-store
|
|
||||||
import type { Feature } from '@/store/app-store';
|
|
||||||
|
|
||||||
// Running Agent type
|
// Running Agent type
|
||||||
export interface RunningAgent {
|
export interface RunningAgent {
|
||||||
featureId: string;
|
featureId: string;
|
||||||
@@ -223,6 +233,7 @@ export interface RunningAgent {
|
|||||||
isAutoMode: boolean;
|
isAutoMode: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
branchName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RunningAgentsResult {
|
export interface RunningAgentsResult {
|
||||||
@@ -749,7 +760,7 @@ export interface ElectronAPI {
|
|||||||
};
|
};
|
||||||
// Setup API surface is implemented by the main process and mirrored by HttpApiClient.
|
// Setup API surface is implemented by the main process and mirrored by HttpApiClient.
|
||||||
// Keep this intentionally loose to avoid tight coupling between front-end and server types.
|
// Keep this intentionally loose to avoid tight coupling between front-end and server types.
|
||||||
setup?: any;
|
setup?: SetupAPI;
|
||||||
agent?: {
|
agent?: {
|
||||||
start: (
|
start: (
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
@@ -774,6 +785,18 @@ export interface ElectronAPI {
|
|||||||
}>;
|
}>;
|
||||||
stop: (sessionId: string) => Promise<{ success: boolean; error?: string }>;
|
stop: (sessionId: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
clear: (sessionId: string) => Promise<{ success: boolean; error?: string }>;
|
clear: (sessionId: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
queueList: (sessionId: string) => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
queue?: Array<{
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
imagePaths?: string[];
|
||||||
|
model?: string;
|
||||||
|
thinkingLevel?: string;
|
||||||
|
addedAt: string;
|
||||||
|
}>;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
onStream: (callback: (data: unknown) => void) => () => void;
|
onStream: (callback: (data: unknown) => void) => () => void;
|
||||||
};
|
};
|
||||||
sessions?: {
|
sessions?: {
|
||||||
@@ -925,12 +948,16 @@ export interface ElectronAPI {
|
|||||||
// Do not redeclare here to avoid type conflicts
|
// Do not redeclare here to avoid type conflicts
|
||||||
|
|
||||||
// Mock data for web development
|
// Mock data for web development
|
||||||
const mockFeatures = [
|
const mockFeatures: Feature[] = [
|
||||||
{
|
{
|
||||||
|
id: 'mock-feature-1',
|
||||||
|
title: 'Sample Feature',
|
||||||
category: 'Core',
|
category: 'Core',
|
||||||
description: 'Sample Feature',
|
description: 'Sample Feature',
|
||||||
|
status: 'backlog',
|
||||||
steps: ['Step 1', 'Step 2'],
|
steps: ['Step 1', 'Step 2'],
|
||||||
passes: false,
|
passes: false,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -950,13 +977,11 @@ export const isElectron = (): boolean => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = window as any;
|
if (window.isElectron === true) {
|
||||||
|
|
||||||
if (w.isElectron === true) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!w.electronAPI?.isElectron;
|
return !!window.electronAPI?.isElectron;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if backend server is available
|
// Check if backend server is available
|
||||||
@@ -1030,7 +1055,7 @@ export const getCurrentApiMode = (): 'http' => {
|
|||||||
|
|
||||||
// Debug helpers
|
// Debug helpers
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
(window as any).__checkApiMode = () => {
|
window.__checkApiMode = () => {
|
||||||
console.log('Current API mode:', getCurrentApiMode());
|
console.log('Current API mode:', getCurrentApiMode());
|
||||||
console.log('isElectron():', isElectron());
|
console.log('isElectron():', isElectron());
|
||||||
};
|
};
|
||||||
@@ -1342,6 +1367,13 @@ const _getMockElectronAPI = (): ElectronAPI => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Install progress event type used by useCliInstallation hook
|
||||||
|
interface InstallProgressEvent {
|
||||||
|
cli?: string;
|
||||||
|
data?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup API interface
|
// Setup API interface
|
||||||
interface SetupAPI {
|
interface SetupAPI {
|
||||||
getClaudeStatus: () => Promise<{
|
getClaudeStatus: () => Promise<{
|
||||||
@@ -1380,7 +1412,15 @@ interface SetupAPI {
|
|||||||
message?: string;
|
message?: string;
|
||||||
output?: string;
|
output?: string;
|
||||||
}>;
|
}>;
|
||||||
|
deauthClaude?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
requiresManualDeauth?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
storeApiKey: (provider: string, apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
storeApiKey: (provider: string, apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
saveApiKey?: (provider: string, apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
getApiKeys: () => Promise<{
|
getApiKeys: () => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
hasAnthropicKey: boolean;
|
hasAnthropicKey: boolean;
|
||||||
@@ -1413,12 +1453,252 @@ interface SetupAPI {
|
|||||||
user: string | null;
|
user: string | null;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
onInstallProgress?: (callback: (progress: any) => void) => () => void;
|
// Cursor CLI methods
|
||||||
onAuthProgress?: (callback: (progress: any) => void) => () => void;
|
getCursorStatus?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
installed?: boolean;
|
||||||
|
version?: string | null;
|
||||||
|
path?: string | null;
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
};
|
||||||
|
installCommand?: string;
|
||||||
|
loginCommand?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
authCursor?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
token?: string;
|
||||||
|
requiresManualAuth?: boolean;
|
||||||
|
terminalOpened?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
output?: string;
|
||||||
|
}>;
|
||||||
|
deauthCursor?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
requiresManualDeauth?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
// Codex CLI methods
|
||||||
|
getCodexStatus?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
status?: string;
|
||||||
|
installed?: boolean;
|
||||||
|
method?: string;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasAuthFile?: boolean;
|
||||||
|
hasOAuthToken?: boolean;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
hasStoredApiKey?: boolean;
|
||||||
|
hasEnvApiKey?: boolean;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
installCodex?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
authCodex?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
token?: string;
|
||||||
|
requiresManualAuth?: boolean;
|
||||||
|
terminalOpened?: boolean;
|
||||||
|
command?: string;
|
||||||
|
error?: string;
|
||||||
|
message?: string;
|
||||||
|
output?: string;
|
||||||
|
}>;
|
||||||
|
deauthCodex?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
requiresManualDeauth?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
verifyCodexAuth?: (
|
||||||
|
authMethod: 'cli' | 'api_key',
|
||||||
|
apiKey?: string
|
||||||
|
) => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
authenticated: boolean;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
// OpenCode CLI methods
|
||||||
|
getOpencodeStatus?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
status?: string;
|
||||||
|
installed?: boolean;
|
||||||
|
method?: string;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
recommendation?: string;
|
||||||
|
installCommands?: {
|
||||||
|
macos?: string;
|
||||||
|
linux?: string;
|
||||||
|
npm?: string;
|
||||||
|
};
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasAuthFile?: boolean;
|
||||||
|
hasOAuthToken?: boolean;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
hasStoredApiKey?: boolean;
|
||||||
|
hasEnvApiKey?: boolean;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
authOpencode?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
token?: string;
|
||||||
|
requiresManualAuth?: boolean;
|
||||||
|
terminalOpened?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
output?: string;
|
||||||
|
}>;
|
||||||
|
deauthOpencode?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
requiresManualDeauth?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
getOpencodeModels?: (refresh?: boolean) => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
models?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
modelString: string;
|
||||||
|
provider: string;
|
||||||
|
description: string;
|
||||||
|
supportsTools: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
tier: string;
|
||||||
|
default?: boolean;
|
||||||
|
}>;
|
||||||
|
count?: number;
|
||||||
|
cached?: boolean;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
refreshOpencodeModels?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
models?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
modelString: string;
|
||||||
|
provider: string;
|
||||||
|
description: string;
|
||||||
|
supportsTools: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
tier: string;
|
||||||
|
default?: boolean;
|
||||||
|
}>;
|
||||||
|
count?: number;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
getOpencodeProviders?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
providers?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
authenticated: boolean;
|
||||||
|
authMethod?: 'oauth' | 'api_key';
|
||||||
|
}>;
|
||||||
|
authenticated?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
authenticated: boolean;
|
||||||
|
authMethod?: 'oauth' | 'api_key';
|
||||||
|
}>;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
clearOpencodeCache?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
// Gemini CLI methods
|
||||||
|
getGeminiStatus?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
status?: string;
|
||||||
|
installed?: boolean;
|
||||||
|
method?: string;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
recommendation?: string;
|
||||||
|
installCommands?: {
|
||||||
|
macos?: string;
|
||||||
|
linux?: string;
|
||||||
|
npm?: string;
|
||||||
|
};
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
hasApiKey?: boolean;
|
||||||
|
hasEnvApiKey?: boolean;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
loginCommand?: string;
|
||||||
|
installCommand?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
authGemini?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
requiresManualAuth?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
deauthGemini?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
requiresManualDeauth?: boolean;
|
||||||
|
command?: string;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
// Copilot SDK methods
|
||||||
|
getCopilotStatus?: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
status?: string;
|
||||||
|
installed?: boolean;
|
||||||
|
method?: string;
|
||||||
|
version?: string;
|
||||||
|
path?: string;
|
||||||
|
recommendation?: string;
|
||||||
|
auth?: {
|
||||||
|
authenticated: boolean;
|
||||||
|
method: string;
|
||||||
|
login?: string;
|
||||||
|
host?: string;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
loginCommand?: string;
|
||||||
|
installCommand?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
onInstallProgress?: (
|
||||||
|
callback: (progress: InstallProgressEvent) => void
|
||||||
|
) => (() => void) | undefined;
|
||||||
|
onAuthProgress?: (callback: (progress: InstallProgressEvent) => void) => (() => void) | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock Setup API implementation
|
// Mock Setup API implementation
|
||||||
function createMockSetupAPI(): SetupAPI {
|
function createMockSetupAPI(): SetupAPI {
|
||||||
|
const mockStoreApiKey = async (provider: string, _apiKey: string) => {
|
||||||
|
console.log('[Mock] Storing API key for:', provider);
|
||||||
|
return { success: true };
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getClaudeStatus: async () => {
|
getClaudeStatus: async () => {
|
||||||
console.log('[Mock] Getting Claude status');
|
console.log('[Mock] Getting Claude status');
|
||||||
@@ -1457,12 +1737,18 @@ function createMockSetupAPI(): SetupAPI {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
storeApiKey: async (provider: string, _apiKey: string) => {
|
deauthClaude: async () => {
|
||||||
console.log('[Mock] Storing API key for:', provider);
|
console.log('[Mock] Deauth Claude CLI');
|
||||||
// In mock mode, we just pretend to store it (it's already in the app store)
|
return {
|
||||||
return { success: true };
|
success: true,
|
||||||
|
requiresManualDeauth: true,
|
||||||
|
command: 'claude logout',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
storeApiKey: mockStoreApiKey,
|
||||||
|
saveApiKey: mockStoreApiKey,
|
||||||
|
|
||||||
getApiKeys: async () => {
|
getApiKeys: async () => {
|
||||||
console.log('[Mock] Getting API keys');
|
console.log('[Mock] Getting API keys');
|
||||||
return {
|
return {
|
||||||
@@ -1512,6 +1798,187 @@ function createMockSetupAPI(): SetupAPI {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Cursor CLI mock methods
|
||||||
|
getCursorStatus: async () => {
|
||||||
|
console.log('[Mock] Getting Cursor status');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
installed: false,
|
||||||
|
version: null,
|
||||||
|
path: null,
|
||||||
|
auth: { authenticated: false, method: 'none' },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
authCursor: async () => {
|
||||||
|
console.log('[Mock] Auth Cursor CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualAuth: true,
|
||||||
|
command: 'cursor --login',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
deauthCursor: async () => {
|
||||||
|
console.log('[Mock] Deauth Cursor CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualDeauth: true,
|
||||||
|
command: 'cursor --logout',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Codex CLI mock methods
|
||||||
|
getCodexStatus: async () => {
|
||||||
|
console.log('[Mock] Getting Codex status');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: 'not_installed',
|
||||||
|
installed: false,
|
||||||
|
auth: { authenticated: false, method: 'none' },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
installCodex: async () => {
|
||||||
|
console.log('[Mock] Installing Codex CLI');
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'CLI installation is only available in the Electron app.',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
authCodex: async () => {
|
||||||
|
console.log('[Mock] Auth Codex CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualAuth: true,
|
||||||
|
command: 'codex login',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
deauthCodex: async () => {
|
||||||
|
console.log('[Mock] Deauth Codex CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualDeauth: true,
|
||||||
|
command: 'codex logout',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
verifyCodexAuth: async (authMethod: 'cli' | 'api_key') => {
|
||||||
|
console.log('[Mock] Verifying Codex auth with method:', authMethod);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
authenticated: false,
|
||||||
|
error: 'Mock environment - authentication not available',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// OpenCode CLI mock methods
|
||||||
|
getOpencodeStatus: async () => {
|
||||||
|
console.log('[Mock] Getting OpenCode status');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: 'not_installed',
|
||||||
|
installed: false,
|
||||||
|
auth: { authenticated: false, method: 'none' },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
authOpencode: async () => {
|
||||||
|
console.log('[Mock] Auth OpenCode CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualAuth: true,
|
||||||
|
command: 'opencode auth login',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
deauthOpencode: async () => {
|
||||||
|
console.log('[Mock] Deauth OpenCode CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualDeauth: true,
|
||||||
|
command: 'opencode auth logout',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getOpencodeModels: async () => {
|
||||||
|
console.log('[Mock] Getting OpenCode models');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
models: [],
|
||||||
|
count: 0,
|
||||||
|
cached: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshOpencodeModels: async () => {
|
||||||
|
console.log('[Mock] Refreshing OpenCode models');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
models: [],
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getOpencodeProviders: async () => {
|
||||||
|
console.log('[Mock] Getting OpenCode providers');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
providers: [],
|
||||||
|
authenticated: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
clearOpencodeCache: async () => {
|
||||||
|
console.log('[Mock] Clearing OpenCode cache');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Cache cleared',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Gemini CLI mock methods
|
||||||
|
getGeminiStatus: async () => {
|
||||||
|
console.log('[Mock] Getting Gemini status');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: 'not_installed',
|
||||||
|
installed: false,
|
||||||
|
auth: { authenticated: false, method: 'none' },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
authGemini: async () => {
|
||||||
|
console.log('[Mock] Auth Gemini CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualAuth: true,
|
||||||
|
command: 'gemini auth login',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
deauthGemini: async () => {
|
||||||
|
console.log('[Mock] Deauth Gemini CLI');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
requiresManualDeauth: true,
|
||||||
|
command: 'gemini auth logout',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Copilot SDK mock methods
|
||||||
|
getCopilotStatus: async () => {
|
||||||
|
console.log('[Mock] Getting Copilot status');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: 'not_installed',
|
||||||
|
installed: false,
|
||||||
|
auth: { authenticated: false, method: 'none' },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
onInstallProgress: (_callback) => {
|
onInstallProgress: (_callback) => {
|
||||||
// Mock progress events
|
// Mock progress events
|
||||||
return () => {};
|
return () => {};
|
||||||
@@ -1665,7 +2132,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
createPR: async (worktreePath: string, options?: any) => {
|
createPR: async (worktreePath: string, options?: CreatePROptions) => {
|
||||||
console.log('[Mock] Creating PR:', { worktreePath, options });
|
console.log('[Mock] Creating PR:', { worktreePath, options });
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -1784,6 +2251,19 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addRemote: async (worktreePath: string, remoteName: string, remoteUrl: string) => {
|
||||||
|
console.log('[Mock] Adding remote:', { worktreePath, remoteName, remoteUrl });
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
result: {
|
||||||
|
remoteName,
|
||||||
|
remoteUrl,
|
||||||
|
fetched: true,
|
||||||
|
message: `Added remote '${remoteName}' (${remoteUrl})`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
openInEditor: async (worktreePath: string, editorCommand?: string) => {
|
openInEditor: async (worktreePath: string, editorCommand?: string) => {
|
||||||
const ANTIGRAVITY_EDITOR_COMMAND = 'antigravity';
|
const ANTIGRAVITY_EDITOR_COMMAND = 'antigravity';
|
||||||
const ANTIGRAVITY_LEGACY_COMMAND = 'agy';
|
const ANTIGRAVITY_LEGACY_COMMAND = 'agy';
|
||||||
@@ -2113,14 +2593,14 @@ let mockAutoModeTimeouts = new Map<string, NodeJS.Timeout>(); // Track timeouts
|
|||||||
|
|
||||||
function createMockAutoModeAPI(): AutoModeAPI {
|
function createMockAutoModeAPI(): AutoModeAPI {
|
||||||
return {
|
return {
|
||||||
start: async (projectPath: string, maxConcurrency?: number) => {
|
start: async (projectPath: string, branchName?: string | null, maxConcurrency?: number) => {
|
||||||
if (mockAutoModeRunning) {
|
if (mockAutoModeRunning) {
|
||||||
return { success: false, error: 'Auto mode is already running' };
|
return { success: false, error: 'Auto mode is already running' };
|
||||||
}
|
}
|
||||||
|
|
||||||
mockAutoModeRunning = true;
|
mockAutoModeRunning = true;
|
||||||
console.log(
|
console.log(
|
||||||
`[Mock] Auto mode started with maxConcurrency: ${maxConcurrency || DEFAULT_MAX_CONCURRENCY}`
|
`[Mock] Auto mode started with branchName: ${branchName}, maxConcurrency: ${maxConcurrency || DEFAULT_MAX_CONCURRENCY}`
|
||||||
);
|
);
|
||||||
const featureId = 'auto-mode-0';
|
const featureId = 'auto-mode-0';
|
||||||
mockRunningFeatures.add(featureId);
|
mockRunningFeatures.add(featureId);
|
||||||
@@ -2131,7 +2611,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: async (_projectPath: string) => {
|
stop: async (_projectPath: string, _branchName?: string | null) => {
|
||||||
mockAutoModeRunning = false;
|
mockAutoModeRunning = false;
|
||||||
const runningCount = mockRunningFeatures.size;
|
const runningCount = mockRunningFeatures.size;
|
||||||
mockRunningFeatures.clear();
|
mockRunningFeatures.clear();
|
||||||
@@ -2927,7 +3407,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
|||||||
console.log('[Mock] Getting all features for:', projectPath);
|
console.log('[Mock] Getting all features for:', projectPath);
|
||||||
|
|
||||||
// Check if test has set mock features via global variable
|
// Check if test has set mock features via global variable
|
||||||
const testFeatures = (window as any).__mockFeatures;
|
const testFeatures = window.__mockFeatures;
|
||||||
if (testFeatures !== undefined) {
|
if (testFeatures !== undefined) {
|
||||||
return { success: true, features: testFeatures };
|
return { success: true, features: testFeatures };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,9 +162,13 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
|
|||||||
logger.info('Opening directory picker...');
|
logger.info('Opening directory picker...');
|
||||||
|
|
||||||
// Try to show picker programmatically
|
// Try to show picker programmatically
|
||||||
if ('showPicker' in HTMLInputElement.prototype) {
|
// Note: showPicker() is available in modern browsers but not in standard TypeScript types
|
||||||
|
if (
|
||||||
|
'showPicker' in input &&
|
||||||
|
typeof (input as { showPicker?: () => void }).showPicker === 'function'
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
(input as any).showPicker();
|
(input as { showPicker: () => void }).showPicker();
|
||||||
logger.info('Using showPicker()');
|
logger.info('Using showPicker()');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.info('showPicker() failed, using click()', error);
|
logger.info('showPicker() failed, using click()', error);
|
||||||
@@ -263,11 +267,13 @@ export async function openFilePicker(options?: {
|
|||||||
document.body.appendChild(input);
|
document.body.appendChild(input);
|
||||||
|
|
||||||
// Try to show picker programmatically
|
// Try to show picker programmatically
|
||||||
// Note: showPicker() is available in modern browsers but TypeScript types it as void
|
// Note: showPicker() is available in modern browsers but not in standard TypeScript types
|
||||||
// In practice, it may return a Promise in some implementations, but we'll handle errors via try/catch
|
if (
|
||||||
if ('showPicker' in HTMLInputElement.prototype) {
|
'showPicker' in input &&
|
||||||
|
typeof (input as { showPicker?: () => void }).showPicker === 'function'
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
(input as any).showPicker();
|
(input as { showPicker: () => void }).showPicker();
|
||||||
} catch {
|
} catch {
|
||||||
// Fallback to click if showPicker fails
|
// Fallback to click if showPicker fails
|
||||||
input.click();
|
input.click();
|
||||||
|
|||||||
@@ -31,13 +31,19 @@ import type {
|
|||||||
ConvertToFeatureOptions,
|
ConvertToFeatureOptions,
|
||||||
NotificationsAPI,
|
NotificationsAPI,
|
||||||
EventHistoryAPI,
|
EventHistoryAPI,
|
||||||
|
CreatePROptions,
|
||||||
} from './electron';
|
} from './electron';
|
||||||
import type { IdeationContextSources } from '@automaker/types';
|
import type {
|
||||||
import type { EventHistoryFilter } from '@automaker/types';
|
IdeationContextSources,
|
||||||
|
EventHistoryFilter,
|
||||||
|
IdeationStreamEvent,
|
||||||
|
IdeationAnalysisEvent,
|
||||||
|
Notification,
|
||||||
|
} from '@automaker/types';
|
||||||
import type { Message, SessionListItem } from '@/types/electron';
|
import type { Message, SessionListItem } from '@/types/electron';
|
||||||
import type { Feature, ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
||||||
import type { WorktreeAPI, GitAPI, ModelDefinition, ProviderStatus } from '@/types/electron';
|
import type { WorktreeAPI, GitAPI, ModelDefinition, ProviderStatus } from '@/types/electron';
|
||||||
import type { ModelId, ThinkingLevel, ReasoningEffort } from '@automaker/types';
|
import type { ModelId, ThinkingLevel, ReasoningEffort, Feature } from '@automaker/types';
|
||||||
import { getGlobalFileBrowser } from '@/contexts/file-browser-context';
|
import { getGlobalFileBrowser } from '@/contexts/file-browser-context';
|
||||||
|
|
||||||
const logger = createLogger('HttpClient');
|
const logger = createLogger('HttpClient');
|
||||||
@@ -131,9 +137,7 @@ export const handleServerOffline = (): void => {
|
|||||||
* Must be called early in Electron mode before making API requests.
|
* Must be called early in Electron mode before making API requests.
|
||||||
*/
|
*/
|
||||||
export const initServerUrl = async (): Promise<void> => {
|
export const initServerUrl = async (): Promise<void> => {
|
||||||
// window.electronAPI is typed as ElectronAPI, but some Electron-only helpers
|
const electron = typeof window !== 'undefined' ? window.electronAPI : null;
|
||||||
// (like getServerUrl) are not part of the shared interface. Narrow via `any`.
|
|
||||||
const electron = typeof window !== 'undefined' ? (window.electronAPI as any) : null;
|
|
||||||
if (electron?.getServerUrl) {
|
if (electron?.getServerUrl) {
|
||||||
try {
|
try {
|
||||||
cachedServerUrl = await electron.getServerUrl();
|
cachedServerUrl = await electron.getServerUrl();
|
||||||
@@ -157,7 +161,7 @@ const getServerUrl = (): string => {
|
|||||||
|
|
||||||
// In web mode (not Electron), use relative URL to leverage Vite proxy
|
// In web mode (not Electron), use relative URL to leverage Vite proxy
|
||||||
// This avoids CORS issues since requests appear same-origin
|
// This avoids CORS issues since requests appear same-origin
|
||||||
if (!window.electron) {
|
if (!window.isElectron) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +253,7 @@ export const isElectronMode = (): boolean => {
|
|||||||
// Prefer a stable runtime marker from preload.
|
// Prefer a stable runtime marker from preload.
|
||||||
// In some dev/electron setups, method availability can be temporarily undefined
|
// In some dev/electron setups, method availability can be temporarily undefined
|
||||||
// during early startup, but `isElectron` remains reliable.
|
// during early startup, but `isElectron` remains reliable.
|
||||||
const api = window.electronAPI as any;
|
const api = window.electronAPI;
|
||||||
return api?.isElectron === true || !!api?.getApiKey;
|
return api?.isElectron === true || !!api?.getApiKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -266,7 +270,7 @@ export const checkExternalServerMode = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const api = window.electronAPI as any;
|
const api = window.electronAPI;
|
||||||
if (api?.isExternalServerMode) {
|
if (api?.isExternalServerMode) {
|
||||||
try {
|
try {
|
||||||
cachedExternalServerMode = Boolean(await api.isExternalServerMode());
|
cachedExternalServerMode = Boolean(await api.isExternalServerMode());
|
||||||
@@ -1719,12 +1723,16 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}> => this.get('/api/setup/copilot-status'),
|
}> => this.get('/api/setup/copilot-status'),
|
||||||
|
|
||||||
onInstallProgress: (callback: (progress: unknown) => void) => {
|
onInstallProgress: (
|
||||||
return this.subscribeToEvent('agent:stream', callback);
|
callback: (progress: { cli?: string; data?: string; type?: string }) => void
|
||||||
|
) => {
|
||||||
|
return this.subscribeToEvent('agent:stream', callback as EventCallback);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAuthProgress: (callback: (progress: unknown) => void) => {
|
onAuthProgress: (
|
||||||
return this.subscribeToEvent('agent:stream', callback);
|
callback: (progress: { cli?: string; data?: string; type?: string }) => void
|
||||||
|
) => {
|
||||||
|
return this.subscribeToEvent('agent:stream', callback as EventCallback);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2035,7 +2043,7 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
this.post('/api/worktree/generate-commit-message', { worktreePath }),
|
this.post('/api/worktree/generate-commit-message', { worktreePath }),
|
||||||
push: (worktreePath: string, force?: boolean, remote?: string) =>
|
push: (worktreePath: string, force?: boolean, remote?: string) =>
|
||||||
this.post('/api/worktree/push', { worktreePath, force, remote }),
|
this.post('/api/worktree/push', { worktreePath, force, remote }),
|
||||||
createPR: (worktreePath: string, options?: any) =>
|
createPR: (worktreePath: string, options?: CreatePROptions) =>
|
||||||
this.post('/api/worktree/create-pr', { worktreePath, ...options }),
|
this.post('/api/worktree/create-pr', { worktreePath, ...options }),
|
||||||
getDiffs: (projectPath: string, featureId: string) =>
|
getDiffs: (projectPath: string, featureId: string) =>
|
||||||
this.post('/api/worktree/diffs', { projectPath, featureId }),
|
this.post('/api/worktree/diffs', { projectPath, featureId }),
|
||||||
@@ -2762,18 +2770,18 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
|
|
||||||
getPrompts: () => this.get('/api/ideation/prompts'),
|
getPrompts: () => this.get('/api/ideation/prompts'),
|
||||||
|
|
||||||
onStream: (callback: (event: any) => void): (() => void) => {
|
onStream: (callback: (event: IdeationStreamEvent) => void): (() => void) => {
|
||||||
return this.subscribeToEvent('ideation:stream', callback as EventCallback);
|
return this.subscribeToEvent('ideation:stream', callback as EventCallback);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAnalysisEvent: (callback: (event: any) => void): (() => void) => {
|
onAnalysisEvent: (callback: (event: IdeationAnalysisEvent) => void): (() => void) => {
|
||||||
return this.subscribeToEvent('ideation:analysis', callback as EventCallback);
|
return this.subscribeToEvent('ideation:analysis', callback as EventCallback);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Notifications API - project-level notifications
|
// Notifications API - project-level notifications
|
||||||
notifications: NotificationsAPI & {
|
notifications: NotificationsAPI & {
|
||||||
onNotificationCreated: (callback: (notification: any) => void) => () => void;
|
onNotificationCreated: (callback: (notification: Notification) => void) => () => void;
|
||||||
} = {
|
} = {
|
||||||
list: (projectPath: string) => this.post('/api/notifications/list', { projectPath }),
|
list: (projectPath: string) => this.post('/api/notifications/list', { projectPath }),
|
||||||
|
|
||||||
@@ -2786,7 +2794,7 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
dismiss: (projectPath: string, notificationId?: string) =>
|
dismiss: (projectPath: string, notificationId?: string) =>
|
||||||
this.post('/api/notifications/dismiss', { projectPath, notificationId }),
|
this.post('/api/notifications/dismiss', { projectPath, notificationId }),
|
||||||
|
|
||||||
onNotificationCreated: (callback: (notification: any) => void): (() => void) => {
|
onNotificationCreated: (callback: (notification: Notification) => void): (() => void) => {
|
||||||
return this.subscribeToEvent('notification:created', callback as EventCallback);
|
return this.subscribeToEvent('notification:created', callback as EventCallback);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ async function getDefaultDocumentsPath(): Promise<string | null> {
|
|||||||
// In Electron mode, use the native getPath API directly from the preload script
|
// In Electron mode, use the native getPath API directly from the preload script
|
||||||
// This returns the actual system Documents folder (e.g., C:\Users\<user>\Documents on Windows)
|
// This returns the actual system Documents folder (e.g., C:\Users\<user>\Documents on Windows)
|
||||||
// Note: The HTTP client's getPath returns incorrect Unix-style paths for 'documents'
|
// Note: The HTTP client's getPath returns incorrect Unix-style paths for 'documents'
|
||||||
if (typeof window !== 'undefined' && (window as any).electronAPI?.getPath) {
|
if (typeof window !== 'undefined' && window.electronAPI?.getPath) {
|
||||||
const documentsPath = await (window as any).electronAPI.getPath('documents');
|
const documentsPath = await window.electronAPI.getPath('documents');
|
||||||
return joinPath(documentsPath, 'Automaker');
|
return joinPath(documentsPath, 'Automaker');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type {
|
|||||||
ModelAlias,
|
ModelAlias,
|
||||||
PlanningMode,
|
PlanningMode,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
|
ReasoningEffort,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
CursorModelId,
|
CursorModelId,
|
||||||
CodexModelId,
|
CodexModelId,
|
||||||
@@ -63,6 +64,7 @@ export type {
|
|||||||
ModelAlias,
|
ModelAlias,
|
||||||
PlanningMode,
|
PlanningMode,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
|
ReasoningEffort,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
ServerLogLevel,
|
ServerLogLevel,
|
||||||
FeatureTextFilePath,
|
FeatureTextFilePath,
|
||||||
@@ -153,8 +155,12 @@ export function getStoredTheme(): ThemeMode | null {
|
|||||||
try {
|
try {
|
||||||
const legacy = getItem('automaker-storage');
|
const legacy = getItem('automaker-storage');
|
||||||
if (!legacy) return null;
|
if (!legacy) return null;
|
||||||
const parsed = JSON.parse(legacy) as { state?: { theme?: unknown } } | { theme?: unknown };
|
interface LegacyStorageFormat {
|
||||||
const theme = (parsed as any)?.state?.theme ?? (parsed as any)?.theme;
|
state?: { theme?: string };
|
||||||
|
theme?: string;
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(legacy) as LegacyStorageFormat;
|
||||||
|
const theme = parsed.state?.theme ?? parsed.theme;
|
||||||
if (typeof theme === 'string' && theme.length > 0) {
|
if (typeof theme === 'string' && theme.length > 0) {
|
||||||
return theme as ThemeMode;
|
return theme as ThemeMode;
|
||||||
}
|
}
|
||||||
@@ -456,7 +462,17 @@ export type ClaudeModel = 'opus' | 'sonnet' | 'haiku';
|
|||||||
|
|
||||||
export interface Feature extends Omit<
|
export interface Feature extends Omit<
|
||||||
BaseFeature,
|
BaseFeature,
|
||||||
'steps' | 'imagePaths' | 'textFilePaths' | 'status' | 'planSpec'
|
| 'steps'
|
||||||
|
| 'imagePaths'
|
||||||
|
| 'textFilePaths'
|
||||||
|
| 'status'
|
||||||
|
| 'planSpec'
|
||||||
|
| 'dependencies'
|
||||||
|
| 'model'
|
||||||
|
| 'branchName'
|
||||||
|
| 'thinkingLevel'
|
||||||
|
| 'reasoningEffort'
|
||||||
|
| 'summary'
|
||||||
> {
|
> {
|
||||||
id: string;
|
id: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -471,6 +487,12 @@ export interface Feature extends Omit<
|
|||||||
justFinishedAt?: string; // UI-specific: ISO timestamp when agent just finished
|
justFinishedAt?: string; // UI-specific: ISO timestamp when agent just finished
|
||||||
prUrl?: string; // UI-specific: Pull request URL
|
prUrl?: string; // UI-specific: Pull request URL
|
||||||
planSpec?: PlanSpec; // Explicit planSpec type to override BaseFeature's index signature
|
planSpec?: PlanSpec; // Explicit planSpec type to override BaseFeature's index signature
|
||||||
|
dependencies?: string[]; // Explicit type to override BaseFeature's index signature
|
||||||
|
model?: string; // Explicit type to override BaseFeature's index signature
|
||||||
|
branchName?: string; // Explicit type to override BaseFeature's index signature
|
||||||
|
thinkingLevel?: ThinkingLevel; // Explicit type to override BaseFeature's index signature
|
||||||
|
reasoningEffort?: ReasoningEffort; // Explicit type to override BaseFeature's index signature
|
||||||
|
summary?: string; // Explicit type to override BaseFeature's index signature
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsedTask and PlanSpec types are now imported from @automaker/types
|
// ParsedTask and PlanSpec types are now imported from @automaker/types
|
||||||
@@ -661,6 +683,8 @@ export interface AppState {
|
|||||||
path: string;
|
path: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
isMain: boolean;
|
isMain: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
hasWorktree: boolean;
|
||||||
hasChanges?: boolean;
|
hasChanges?: boolean;
|
||||||
changedFilesCount?: number;
|
changedFilesCount?: number;
|
||||||
}>
|
}>
|
||||||
@@ -1152,6 +1176,8 @@ export interface AppActions {
|
|||||||
path: string;
|
path: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
isMain: boolean;
|
isMain: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
hasWorktree: boolean;
|
||||||
hasChanges?: boolean;
|
hasChanges?: boolean;
|
||||||
changedFilesCount?: number;
|
changedFilesCount?: number;
|
||||||
}>
|
}>
|
||||||
@@ -1161,6 +1187,8 @@ export interface AppActions {
|
|||||||
path: string;
|
path: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
isMain: boolean;
|
isMain: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
hasWorktree: boolean;
|
||||||
hasChanges?: boolean;
|
hasChanges?: boolean;
|
||||||
changedFilesCount?: number;
|
changedFilesCount?: number;
|
||||||
}>;
|
}>;
|
||||||
@@ -4105,7 +4133,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api.setup) {
|
if (!api.setup?.getOpencodeModels) {
|
||||||
throw new Error('Setup API not available');
|
throw new Error('Setup API not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4116,7 +4144,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
set({
|
set({
|
||||||
dynamicOpencodeModels: result.models || [],
|
dynamicOpencodeModels: (result.models || []) as ModelDefinition[],
|
||||||
opencodeModelsLastFetched: Date.now(),
|
opencodeModelsLastFetched: Date.now(),
|
||||||
opencodeModelsLoading: false,
|
opencodeModelsLoading: false,
|
||||||
opencodeModelsError: null,
|
opencodeModelsError: null,
|
||||||
|
|||||||
32
apps/ui/src/types/electron.d.ts
vendored
32
apps/ui/src/types/electron.d.ts
vendored
@@ -1416,10 +1416,15 @@ export interface ModelDefinition {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
modelString: string;
|
modelString: string;
|
||||||
provider: 'claude';
|
provider: string;
|
||||||
description?: string;
|
description: string;
|
||||||
tier?: 'basic' | 'standard' | 'premium';
|
contextWindow?: number;
|
||||||
|
maxOutputTokens?: number;
|
||||||
|
supportsVision?: boolean;
|
||||||
|
supportsTools?: boolean;
|
||||||
|
tier?: 'basic' | 'standard' | 'premium' | string;
|
||||||
default?: boolean;
|
default?: boolean;
|
||||||
|
hasReasoning?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider status type
|
// Provider status type
|
||||||
@@ -1437,10 +1442,27 @@ export interface ProviderStatus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended Electron API with additional Electron-specific methods
|
||||||
|
* that are exposed via the preload script but not part of the shared interface.
|
||||||
|
*/
|
||||||
|
export interface ExtendedElectronAPI extends ElectronAPI {
|
||||||
|
/** Runtime marker indicating Electron environment */
|
||||||
|
isElectron?: boolean;
|
||||||
|
/** Get the server URL (Electron-only) */
|
||||||
|
getServerUrl?: () => Promise<string>;
|
||||||
|
/** Get the API key (Electron-only) */
|
||||||
|
getApiKey?: () => Promise<string | null>;
|
||||||
|
/** Check if running in external server mode (Electron-only) */
|
||||||
|
isExternalServerMode?: () => Promise<boolean>;
|
||||||
|
/** Get system paths (Electron-only) */
|
||||||
|
getPath?: (name: 'documents' | 'home' | 'appData' | 'userData') => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electronAPI: ElectronAPI;
|
electronAPI?: ExtendedElectronAPI;
|
||||||
isElectron: boolean;
|
isElectron?: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
68
apps/ui/src/types/global.d.ts
vendored
Normal file
68
apps/ui/src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Global type augmentations for Window interface
|
||||||
|
*
|
||||||
|
* These augmentations extend the Window interface with properties
|
||||||
|
* used in testing and development contexts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Feature } from '@automaker/types';
|
||||||
|
import type { ElectronAPI } from '../lib/electron';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock context file data for testing
|
||||||
|
*/
|
||||||
|
interface MockContextFile {
|
||||||
|
featureId: string;
|
||||||
|
path: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock project data for testing
|
||||||
|
*/
|
||||||
|
export interface MockProject {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
path: string;
|
||||||
|
lastOpened?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
/**
|
||||||
|
* Mock features array used in E2E tests
|
||||||
|
* Set via page.addInitScript() to simulate features loaded from disk
|
||||||
|
*/
|
||||||
|
__mockFeatures?: Feature[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock current project used in E2E tests
|
||||||
|
* Set via page.addInitScript() to simulate the currently open project
|
||||||
|
*/
|
||||||
|
__currentProject?: MockProject | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock context file data used in E2E tests
|
||||||
|
* Set via page.addInitScript() to simulate agent output files
|
||||||
|
*/
|
||||||
|
__mockContextFile?: MockContextFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug helper to check API mode
|
||||||
|
*/
|
||||||
|
__checkApiMode?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Electron API exposed via preload script
|
||||||
|
*/
|
||||||
|
electronAPI?: ElectronAPI & {
|
||||||
|
isElectron?: boolean;
|
||||||
|
getServerUrl?: () => Promise<string>;
|
||||||
|
getApiKey?: () => Promise<string | null>;
|
||||||
|
isExternalServerMode?: () => Promise<boolean>;
|
||||||
|
getPath?: (name: 'documents' | 'home' | 'appData' | 'userData') => Promise<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
@@ -90,7 +90,9 @@ test.describe('Feature Manual Review Flow', () => {
|
|||||||
|
|
||||||
// Add to projects if not already there
|
// Add to projects if not already there
|
||||||
const existingProjects = json.settings.projects || [];
|
const existingProjects = json.settings.projects || [];
|
||||||
const hasProject = existingProjects.some((p: any) => p.path === projectPath);
|
const hasProject = existingProjects.some(
|
||||||
|
(p: { id: string; path: string }) => p.path === projectPath
|
||||||
|
);
|
||||||
if (!hasProject) {
|
if (!hasProject) {
|
||||||
json.settings.projects = [testProject, ...existingProjects];
|
json.settings.projects = [testProject, ...existingProjects];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ test.describe('Open Project', () => {
|
|||||||
|
|
||||||
// Add to existing projects (or create array)
|
// Add to existing projects (or create array)
|
||||||
const existingProjects = json.settings.projects || [];
|
const existingProjects = json.settings.projects || [];
|
||||||
const hasProject = existingProjects.some((p: any) => p.id === projectId);
|
const hasProject = existingProjects.some(
|
||||||
|
(p: { id: string; path: string }) => p.id === projectId
|
||||||
|
);
|
||||||
if (!hasProject) {
|
if (!hasProject) {
|
||||||
json.settings.projects = [testProject, ...existingProjects];
|
json.settings.projects = [testProject, ...existingProjects];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ export async function setupMockProjectWithFeatures(
|
|||||||
|
|
||||||
// Also store features in a global variable that the mock electron API can use
|
// Also store features in a global variable that the mock electron API can use
|
||||||
// This is needed because the board-view loads features from the file system
|
// This is needed because the board-view loads features from the file system
|
||||||
(window as any).__mockFeatures = mockFeatures;
|
(window as { __mockFeatures?: unknown[] }).__mockFeatures = mockFeatures;
|
||||||
|
|
||||||
// Disable splash screen in tests
|
// Disable splash screen in tests
|
||||||
sessionStorage.setItem('automaker-splash-shown', 'true');
|
sessionStorage.setItem('automaker-splash-shown', 'true');
|
||||||
@@ -395,7 +395,9 @@ export async function setupMockProjectWithContextFile(
|
|||||||
// Set up mock file system with a context file for the feature
|
// Set up mock file system with a context file for the feature
|
||||||
// This will be used by the mock electron API
|
// This will be used by the mock electron API
|
||||||
// Now uses features/{id}/agent-output.md path
|
// Now uses features/{id}/agent-output.md path
|
||||||
(window as any).__mockContextFile = {
|
(
|
||||||
|
window as { __mockContextFile?: { featureId: string; path: string; content: string } }
|
||||||
|
).__mockContextFile = {
|
||||||
featureId,
|
featureId,
|
||||||
path: `/mock/test-project/.automaker/features/${featureId}/agent-output.md`,
|
path: `/mock/test-project/.automaker/features/${featureId}/agent-output.md`,
|
||||||
content: contextContent,
|
content: contextContent,
|
||||||
@@ -455,7 +457,7 @@ export async function setupMockProjectWithInProgressFeatures(
|
|||||||
|
|
||||||
// Also store features in a global variable that the mock electron API can use
|
// Also store features in a global variable that the mock electron API can use
|
||||||
// This is needed because the board-view loads features from the file system
|
// This is needed because the board-view loads features from the file system
|
||||||
(window as any).__mockFeatures = mockFeatures;
|
(window as { __mockFeatures?: unknown[] }).__mockFeatures = mockFeatures;
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,7 +689,9 @@ export async function setupMockProjectWithAgentOutput(
|
|||||||
|
|
||||||
// Set up mock file system with output content for the feature
|
// Set up mock file system with output content for the feature
|
||||||
// Now uses features/{id}/agent-output.md path
|
// Now uses features/{id}/agent-output.md path
|
||||||
(window as any).__mockContextFile = {
|
(
|
||||||
|
window as { __mockContextFile?: { featureId: string; path: string; content: string } }
|
||||||
|
).__mockContextFile = {
|
||||||
featureId,
|
featureId,
|
||||||
path: `/mock/test-project/.automaker/features/${featureId}/agent-output.md`,
|
path: `/mock/test-project/.automaker/features/${featureId}/agent-output.md`,
|
||||||
content: outputContent,
|
content: outputContent,
|
||||||
@@ -747,7 +751,7 @@ export async function setupMockProjectWithWaitingApprovalFeatures(
|
|||||||
localStorage.setItem('automaker-storage', JSON.stringify(mockState));
|
localStorage.setItem('automaker-storage', JSON.stringify(mockState));
|
||||||
|
|
||||||
// Also store features in a global variable that the mock electron API can use
|
// Also store features in a global variable that the mock electron API can use
|
||||||
(window as any).__mockFeatures = mockFeatures;
|
(window as { __mockFeatures?: unknown[] }).__mockFeatures = mockFeatures;
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"lint": "npm run lint --workspace=apps/ui",
|
"lint": "npm run lint --workspace=apps/ui",
|
||||||
"lint:errors": "npm run lint --workspace=apps/ui -- --quiet",
|
"lint:errors": "npm run lint --workspace=apps/ui -- --quiet",
|
||||||
"lint:server:errors": "npm run lint --workspace=apps/server -- --quiet",
|
"lint:server:errors": "npm run lint --workspace=apps/server -- --quiet",
|
||||||
|
"typecheck": "npm run typecheck --workspace=apps/ui",
|
||||||
"test": "npm run test --workspace=apps/ui",
|
"test": "npm run test --workspace=apps/ui",
|
||||||
"test:headed": "npm run test:headed --workspace=apps/ui",
|
"test:headed": "npm run test:headed --workspace=apps/ui",
|
||||||
"test:ui": "npm run test --workspace=apps/ui -- --ui",
|
"test:ui": "npm run test --workspace=apps/ui -- --ui",
|
||||||
|
|||||||
Reference in New Issue
Block a user