72106109a2
* fix(windows): Claude CLI detection failing on Windows On Windows, npm installs CLI tools as .cmd batch wrappers alongside bash scripts for Git Bash/Cygwin. Two issues prevented detection: 1. findExecutable() checked for extensionless files first, finding the unusable bash script before the .cmd wrapper 2. execFileSync() cannot execute .cmd files without shell: true Changes: - Reorder extension search to prioritize .exe/.cmd over extensionless - Add shell: true when validating .cmd/.bat files on Windows Both changes are Windows-specific and don't affect macOS behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(windows): terminal shortcuts and invoke Claude not working - Fix Ctrl+T/W shortcuts not working inside terminals on Windows xterm.js was capturing Ctrl+T as ^T character instead of letting it bubble up to window handler. Added T and W to custom key handler bypass list in useXterm.ts - Fix "Invoke Claude" button failing on Windows with path error buildCdCommand() was using single quotes which cmd.exe doesn't recognize. Now uses platform-appropriate quoting (double quotes on Windows, single quotes on Unix) - Improve terminal auto-naming to skip common commands Expanded skip list to include claude, git, npm, node, python, and other shell/dev commands that don't represent meaningful work. Terminal naming should come from actual task descriptions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(windows): reduce installer size by 75% (~300MB savings) Strip unnecessary files from Python site-packages during bundling: - Remove googleapiclient/discovery_cache/documents (92MB cached API docs) - Remove claude_agent_sdk/_bundled (224MB bundled Claude CLI) - Remove pythonwin directory (9MB Windows IDE) - Remove .chm help files (2.6MB) The Claude Agent SDK will fall back to the system-installed Claude Code CLI, which is already a prerequisite for Auto-Claude (required for 'claude setup-token'). Site-packages reduced from 446MB to 111MB (75% reduction). Expected installer size: ~100MB instead of ~206MB. * fix(frontend): sync project tabs with settings by removing project on tab close Closing a project tab now removes the project from the app entirely, keeping tabs and settings dropdown in sync. Files remain on disk so users can re-add projects later. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(settings): remove redundant Claude Auth project settings tab Claude authentication is already available in the app-level Integrations section, making this project-level tab redundant. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(onboarding): add one-click Ollama installation for Windows/macOS/Linux When users select Ollama as their embedding provider during onboarding but don't have it installed, they now see an "Install Ollama" button that opens their preferred terminal with the official install command. Changes: - Add checkOllamaInstalled() to detect if Ollama binary exists on system - Add installOllama() to open terminal with platform-specific install: - Windows: winget install --id Ollama.Ollama - macOS/Linux: curl -fsSL https://ollama.com/install.sh | sh - Update OllamaModelSelector to show install UI when Ollama not found - Fix Windows terminal handling for commands with pipes (PowerShell) - Use fire-and-forget pattern so UI doesn't hang waiting for terminal - Add i18n translations (English & French) for install UI The install uses the user's preferred terminal from the DevTools onboarding step, respecting their configuration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(ipc-handlers): enhance task status persistence in implementation plan - Added functionality to persist task status updates to the implementation plan file, preventing inconsistencies between UI and file state during task refresh. - Implemented error handling for file read/write operations to ensure robustness in status updates. - Updated task execution handlers to reflect changes in task status, including transitions to 'human_review' and 'backlog'. - Introduced critical comments to clarify the importance of status persistence in maintaining accurate task states. This update improves the reliability of task status management across the application. * fix(security): address PR review findings for Windows issues - Fix command injection vulnerability in buildCdCommand by using escapeShellArgWindows() to properly escape cmd.exe metacharacters - Add confirmation dialog before removing project on tab close - Add documentation explaining why skipCommands list exists - Add security comment for shell: true usage in Claude CLI validation - Remove unused EnvironmentSettings component (dead code cleanup) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(security): address follow-up PR review findings - Fix command injection in Windows terminal by escaping PowerShell metacharacters (backticks, double quotes, dollar signs) - Fix Git Bash to use passed command parameter instead of hardcoded value - Add shared plan-file-utils with mutex locking for thread-safe updates - Refactor agent-events-handlers and execution-handlers to use shared persistence utility, eliminating code duplication 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(security): strengthen shell escaping and document sync limitations - Add escaping for parentheses, semicolons, ampersands in PowerShell - Add escaping for semicolons, pipes, exclamation marks in Git Bash - Add comprehensive documentation warning about persistPlanStatusSync bypassing the async locking mechanism Note: The CRITICAL finding about EnvironmentSettings in SectionRouter.tsx is a false positive - grep confirms no such import exists in the file. Line 5 imports SecuritySettings, not EnvironmentSettings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address code scanning and TypeScript errors - Fix TypeScript error in plan-file-utils.ts (generic type assertion) - Add security comment for ollamaPath explaining hardcoded paths - Remove useless isLoading conditional in OllamaModelSelector.tsx Note: The EnvironmentSettings import finding remains a false positive - grep confirms no such import exists in SectionRouter.tsx. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): restore Retry button disabled state for defensive programming Restored disabled={isLoading} and animate-spin on Retry buttons. FALSE POSITIVES in review (verified via grep/sed): - CRITICAL "EnvironmentSettings import at line 5": Line 5 is actually `import { SecuritySettings }` - no EnvironmentSettings import exists - LOW "Dead code escape functions": Functions ARE used at lines 268, 275 in openTerminalWithCommand for PowerShell and Git Bash escaping The review tool appears to be using cached/stale file data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(security): address CodeQL security alerts - Fix 6 HIGH TOCTOU race conditions by removing existsSync checks and using try/catch with ENOENT detection instead - Fix MEDIUM command injection by using execFileSync instead of execSync for Ollama path detection (avoids shell interpretation) - Fix unused imports in execution-handlers.ts - Remove useless isLoading conditional and add explanatory comment about React batching behavior preventing double-clicks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(build): remove TOCTOU race condition in download-python script Replace existsSync check with try/catch pattern to avoid race condition between existence check and file operations. Handle ENOENT as no-op. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review issues for error handling and i18n - Add error handling with toast notification for project removal in App.tsx - Replace hardcoded strings with i18n translation keys in ProjectSettingsContent.tsx - Add translation keys for projectSettings.noProjectSelected (en/fr) - Add removeProject.error translation key to dialogs.json (en/fr) - Add errors.unknownError translation key to common.json (en/fr) - Refactor terminal command escaping in claude-code-handlers.ts - Remove unused imports in execution-handlers.ts - Add explanatory comment for React batching in OllamaModelSelector.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(app): replace toast with inline error display in remove project dialog Toast function doesn't exist in this codebase. Use inline error display with AlertCircle icon to show removal errors in the dialog itself. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
299 lines
11 KiB
TypeScript
299 lines
11 KiB
TypeScript
import { ipcRenderer } from 'electron';
|
|
import { IPC_CHANNELS } from '../../shared/constants';
|
|
import type {
|
|
Project,
|
|
ProjectSettings,
|
|
IPCResult,
|
|
InitializationResult,
|
|
AutoBuildVersionInfo,
|
|
ProjectEnvConfig,
|
|
ClaudeAuthResult,
|
|
InfrastructureStatus,
|
|
GraphitiValidationResult,
|
|
GraphitiConnectionTestResult,
|
|
GitStatus
|
|
} from '../../shared/types';
|
|
|
|
// Tab state interface (persisted in main process)
|
|
export interface TabState {
|
|
openProjectIds: string[];
|
|
activeProjectId: string | null;
|
|
tabOrder: string[];
|
|
}
|
|
|
|
export interface ProjectAPI {
|
|
// Project Management
|
|
addProject: (projectPath: string) => Promise<IPCResult<Project>>;
|
|
removeProject: (projectId: string) => Promise<IPCResult>;
|
|
getProjects: () => Promise<IPCResult<Project[]>>;
|
|
updateProjectSettings: (
|
|
projectId: string,
|
|
settings: Partial<ProjectSettings>
|
|
) => Promise<IPCResult>;
|
|
initializeProject: (projectId: string) => Promise<IPCResult<InitializationResult>>;
|
|
checkProjectVersion: (projectId: string) => Promise<IPCResult<AutoBuildVersionInfo>>;
|
|
|
|
// Tab State (persisted in main process for reliability)
|
|
getTabState: () => Promise<IPCResult<TabState>>;
|
|
saveTabState: (tabState: TabState) => Promise<IPCResult>;
|
|
|
|
// Context Operations
|
|
getProjectContext: (projectId: string) => Promise<IPCResult<unknown>>;
|
|
refreshProjectIndex: (projectId: string) => Promise<IPCResult<unknown>>;
|
|
getMemoryStatus: (projectId: string) => Promise<IPCResult<unknown>>;
|
|
searchMemories: (projectId: string, query: string) => Promise<IPCResult<unknown>>;
|
|
getRecentMemories: (projectId: string, limit?: number) => Promise<IPCResult<unknown>>;
|
|
|
|
// Environment Configuration
|
|
getProjectEnv: (projectId: string) => Promise<IPCResult<ProjectEnvConfig>>;
|
|
updateProjectEnv: (projectId: string, config: Partial<ProjectEnvConfig>) => Promise<IPCResult>;
|
|
checkClaudeAuth: (projectId: string) => Promise<IPCResult<ClaudeAuthResult>>;
|
|
invokeClaudeSetup: (projectId: string) => Promise<IPCResult<ClaudeAuthResult>>;
|
|
|
|
// Dialog Operations
|
|
selectDirectory: () => Promise<string | null>;
|
|
createProjectFolder: (
|
|
location: string,
|
|
name: string,
|
|
initGit: boolean
|
|
) => Promise<IPCResult<import('../../shared/types').CreateProjectFolderResult>>;
|
|
getDefaultProjectLocation: () => Promise<string | null>;
|
|
|
|
// Memory Infrastructure Operations (LadybugDB - no Docker required)
|
|
getMemoryInfrastructureStatus: (dbPath?: string) => Promise<IPCResult<InfrastructureStatus>>;
|
|
listMemoryDatabases: (dbPath?: string) => Promise<IPCResult<string[]>>;
|
|
testMemoryConnection: (dbPath?: string, database?: string) => Promise<IPCResult<GraphitiValidationResult>>;
|
|
|
|
// Graphiti Validation Operations
|
|
validateLLMApiKey: (provider: string, apiKey: string) => Promise<IPCResult<GraphitiValidationResult>>;
|
|
testGraphitiConnection: (config: {
|
|
dbPath?: string;
|
|
database?: string;
|
|
llmProvider: string;
|
|
apiKey: string;
|
|
}) => Promise<IPCResult<GraphitiConnectionTestResult>>;
|
|
|
|
// Ollama Model Management
|
|
scanOllamaModels: (baseUrl: string) => Promise<IPCResult<{
|
|
models: Array<{
|
|
name: string;
|
|
size: number;
|
|
modified_at: string;
|
|
digest: string;
|
|
}>;
|
|
}>>;
|
|
downloadOllamaModel: (baseUrl: string, modelName: string) => Promise<IPCResult<{ message: string }>>;
|
|
onDownloadProgress: (callback: (data: {
|
|
modelName: string;
|
|
status: string;
|
|
completed: number;
|
|
total: number;
|
|
percentage: number;
|
|
}) => void) => () => void;
|
|
|
|
// Git Operations
|
|
getGitBranches: (projectPath: string) => Promise<IPCResult<string[]>>;
|
|
getCurrentGitBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
|
detectMainBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
|
checkGitStatus: (projectPath: string) => Promise<IPCResult<GitStatus>>;
|
|
initializeGit: (projectPath: string) => Promise<IPCResult<InitializationResult>>;
|
|
|
|
// Ollama Model Detection
|
|
checkOllamaStatus: (baseUrl?: string) => Promise<IPCResult<{
|
|
running: boolean;
|
|
url: string;
|
|
version?: string;
|
|
message?: string;
|
|
}>>;
|
|
checkOllamaInstalled: () => Promise<IPCResult<{
|
|
installed: boolean;
|
|
path?: string;
|
|
version?: string;
|
|
}>>;
|
|
installOllama: () => Promise<IPCResult<{ command: string }>>;
|
|
listOllamaModels: (baseUrl?: string) => Promise<IPCResult<{
|
|
models: Array<{
|
|
name: string;
|
|
size_bytes: number;
|
|
size_gb: number;
|
|
modified_at: string;
|
|
is_embedding: boolean;
|
|
embedding_dim?: number | null;
|
|
description?: string;
|
|
}>;
|
|
count: number;
|
|
}>>;
|
|
listOllamaEmbeddingModels: (baseUrl?: string) => Promise<IPCResult<{
|
|
embedding_models: Array<{
|
|
name: string;
|
|
embedding_dim: number | null;
|
|
description: string;
|
|
size_bytes: number;
|
|
size_gb: number;
|
|
}>;
|
|
count: number;
|
|
}>>;
|
|
pullOllamaModel: (modelName: string, baseUrl?: string) => Promise<IPCResult<{
|
|
model: string;
|
|
status: 'completed' | 'failed';
|
|
output: string[];
|
|
}>>;
|
|
}
|
|
|
|
export const createProjectAPI = (): ProjectAPI => ({
|
|
// Project Management
|
|
addProject: (projectPath: string): Promise<IPCResult<Project>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_ADD, projectPath),
|
|
|
|
removeProject: (projectId: string): Promise<IPCResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_REMOVE, projectId),
|
|
|
|
getProjects: (): Promise<IPCResult<Project[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_LIST),
|
|
|
|
updateProjectSettings: (
|
|
projectId: string,
|
|
settings: Partial<ProjectSettings>
|
|
): Promise<IPCResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_UPDATE_SETTINGS, projectId, settings),
|
|
|
|
initializeProject: (projectId: string): Promise<IPCResult<InitializationResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_INITIALIZE, projectId),
|
|
|
|
checkProjectVersion: (projectId: string): Promise<IPCResult<AutoBuildVersionInfo>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_CHECK_VERSION, projectId),
|
|
|
|
// Tab State (persisted in main process for reliability)
|
|
getTabState: (): Promise<IPCResult<TabState>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.TAB_STATE_GET),
|
|
|
|
saveTabState: (tabState: TabState): Promise<IPCResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.TAB_STATE_SAVE, tabState),
|
|
|
|
// Context Operations
|
|
getProjectContext: (projectId: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.CONTEXT_GET, projectId),
|
|
|
|
refreshProjectIndex: (projectId: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.CONTEXT_REFRESH_INDEX, projectId),
|
|
|
|
getMemoryStatus: (projectId: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.CONTEXT_MEMORY_STATUS, projectId),
|
|
|
|
searchMemories: (projectId: string, query: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.CONTEXT_SEARCH_MEMORIES, projectId, query),
|
|
|
|
getRecentMemories: (projectId: string, limit?: number) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.CONTEXT_GET_MEMORIES, projectId, limit),
|
|
|
|
// Environment Configuration
|
|
getProjectEnv: (projectId: string): Promise<IPCResult<ProjectEnvConfig>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.ENV_GET, projectId),
|
|
|
|
updateProjectEnv: (projectId: string, config: Partial<ProjectEnvConfig>): Promise<IPCResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.ENV_UPDATE, projectId, config),
|
|
|
|
checkClaudeAuth: (projectId: string): Promise<IPCResult<ClaudeAuthResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.ENV_CHECK_CLAUDE_AUTH, projectId),
|
|
|
|
invokeClaudeSetup: (projectId: string): Promise<IPCResult<ClaudeAuthResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.ENV_INVOKE_CLAUDE_SETUP, projectId),
|
|
|
|
// Dialog Operations
|
|
selectDirectory: (): Promise<string | null> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.DIALOG_SELECT_DIRECTORY),
|
|
|
|
createProjectFolder: (
|
|
location: string,
|
|
name: string,
|
|
initGit: boolean
|
|
): Promise<IPCResult<import('../../shared/types').CreateProjectFolderResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.DIALOG_CREATE_PROJECT_FOLDER, location, name, initGit),
|
|
|
|
getDefaultProjectLocation: (): Promise<string | null> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.DIALOG_GET_DEFAULT_PROJECT_LOCATION),
|
|
|
|
// Memory Infrastructure Operations (LadybugDB - no Docker required)
|
|
getMemoryInfrastructureStatus: (dbPath?: string): Promise<IPCResult<InfrastructureStatus>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.MEMORY_STATUS, dbPath),
|
|
|
|
listMemoryDatabases: (dbPath?: string): Promise<IPCResult<string[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.MEMORY_LIST_DATABASES, dbPath),
|
|
|
|
testMemoryConnection: (dbPath?: string, database?: string): Promise<IPCResult<GraphitiValidationResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.MEMORY_TEST_CONNECTION, dbPath, database),
|
|
|
|
// Graphiti Validation Operations
|
|
validateLLMApiKey: (provider: string, apiKey: string): Promise<IPCResult<GraphitiValidationResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GRAPHITI_VALIDATE_LLM, provider, apiKey),
|
|
|
|
testGraphitiConnection: (config: {
|
|
dbPath?: string;
|
|
database?: string;
|
|
llmProvider: string;
|
|
apiKey: string;
|
|
}): Promise<IPCResult<GraphitiConnectionTestResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GRAPHITI_TEST_CONNECTION, config),
|
|
|
|
// Ollama Model Management
|
|
scanOllamaModels: (baseUrl: string): Promise<IPCResult<{
|
|
models: Array<{
|
|
name: string;
|
|
size: number;
|
|
modified_at: string;
|
|
digest: string;
|
|
}>;
|
|
}>> =>
|
|
ipcRenderer.invoke('scan-ollama-models', baseUrl),
|
|
|
|
downloadOllamaModel: (baseUrl: string, modelName: string): Promise<IPCResult<{ message: string }>> =>
|
|
ipcRenderer.invoke('download-ollama-model', baseUrl, modelName),
|
|
|
|
onDownloadProgress: (callback: (data: {
|
|
modelName: string;
|
|
status: string;
|
|
completed: number;
|
|
total: number;
|
|
percentage: number;
|
|
}) => void) => {
|
|
const listener = (_: any, data: any) => callback(data);
|
|
ipcRenderer.on(IPC_CHANNELS.OLLAMA_PULL_PROGRESS, listener);
|
|
return () => ipcRenderer.off(IPC_CHANNELS.OLLAMA_PULL_PROGRESS, listener);
|
|
},
|
|
|
|
// Git Operations
|
|
getGitBranches: (projectPath: string): Promise<IPCResult<string[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_GET_BRANCHES, projectPath),
|
|
|
|
getCurrentGitBranch: (projectPath: string): Promise<IPCResult<string | null>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_GET_CURRENT_BRANCH, projectPath),
|
|
|
|
detectMainBranch: (projectPath: string): Promise<IPCResult<string | null>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_DETECT_MAIN_BRANCH, projectPath),
|
|
|
|
checkGitStatus: (projectPath: string): Promise<IPCResult<GitStatus>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_CHECK_STATUS, projectPath),
|
|
|
|
initializeGit: (projectPath: string): Promise<IPCResult<InitializationResult>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_INITIALIZE, projectPath),
|
|
|
|
// Ollama Model Detection
|
|
checkOllamaStatus: (baseUrl?: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.OLLAMA_CHECK_STATUS, baseUrl),
|
|
|
|
checkOllamaInstalled: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.OLLAMA_CHECK_INSTALLED),
|
|
|
|
installOllama: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.OLLAMA_INSTALL),
|
|
|
|
listOllamaModels: (baseUrl?: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.OLLAMA_LIST_MODELS, baseUrl),
|
|
|
|
listOllamaEmbeddingModels: (baseUrl?: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.OLLAMA_LIST_EMBEDDING_MODELS, baseUrl),
|
|
|
|
pullOllamaModel: (modelName: string, baseUrl?: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.OLLAMA_PULL_MODEL, modelName, baseUrl)
|
|
});
|