feat: standardize logging across UI components

- Replaced console.log and console.error statements with logger methods from @automaker/utils in various UI components, ensuring consistent log formatting and improved readability.
- Enhanced error handling by utilizing logger methods to provide clearer context for issues encountered during operations.
- Updated multiple views and hooks to integrate the new logging system, improving maintainability and debugging capabilities.

This update significantly enhances the observability of UI components, facilitating easier troubleshooting and monitoring.
This commit is contained in:
Shirone
2026-01-02 17:25:13 +01:00
parent 96a999817f
commit 69f3ba9724
86 changed files with 1079 additions and 677 deletions

View File

@@ -1,4 +1,5 @@
// Type definitions for Electron IPC API
import { createLogger } from '@automaker/utils/logger';
import type { SessionListItem, Message } from '@/types/electron';
import type { ClaudeUsageResponse } from '@/store/app-store';
import type {
@@ -94,6 +95,8 @@ import type {
ProviderStatus,
} from '@/types/electron';
const logger = createLogger('Electron');
// Import HTTP API client (ES module)
import { getHttpApiClient, getServerUrlSync } from './http-api-client';
@@ -774,8 +777,8 @@ export const getCurrentApiMode = (): 'http' => {
// Debug helpers
if (typeof window !== 'undefined') {
(window as any).__checkApiMode = () => {
console.log('Current API mode:', getCurrentApiMode());
console.log('isElectron():', isElectron());
logger.info('Current API mode:', getCurrentApiMode());
logger.info('isElectron():', isElectron());
};
}
@@ -1016,7 +1019,7 @@ const getMockElectronAPI = (): ElectronAPI => {
// Store the image data in mock file system for testing
mockFileSystem[tempFilePath] = data;
console.log('[Mock] Saved image to temp:', tempFilePath);
logger.info('Mock saved image to temp:', tempFilePath);
return { success: true, path: tempFilePath };
},
@@ -1061,7 +1064,7 @@ const getMockElectronAPI = (): ElectronAPI => {
// Mock Claude API
claude: {
getUsage: async () => {
console.log('[Mock] Getting Claude usage');
logger.info('Mock getting Claude usage');
return {
sessionTokensUsed: 0,
sessionLimit: 0,
@@ -1168,7 +1171,7 @@ interface SetupAPI {
function createMockSetupAPI(): SetupAPI {
return {
getClaudeStatus: async () => {
console.log('[Mock] Getting Claude status');
logger.info('Mock Getting Claude status');
return {
success: true,
status: 'not_installed',
@@ -1185,7 +1188,7 @@ function createMockSetupAPI(): SetupAPI {
},
installClaude: async () => {
console.log('[Mock] Installing Claude CLI');
logger.info('Mock Installing Claude CLI');
// Simulate installation delay
await new Promise((resolve) => setTimeout(resolve, 1000));
return {
@@ -1196,7 +1199,7 @@ function createMockSetupAPI(): SetupAPI {
},
authClaude: async () => {
console.log('[Mock] Auth Claude CLI');
logger.info('Mock Auth Claude CLI');
return {
success: true,
requiresManualAuth: true,
@@ -1205,13 +1208,13 @@ function createMockSetupAPI(): SetupAPI {
},
storeApiKey: async (provider: string, apiKey: string) => {
console.log('[Mock] Storing API key for:', provider);
logger.info('Mock Storing API key for:', provider);
// In mock mode, we just pretend to store it (it's already in the app store)
return { success: true };
},
getApiKeys: async () => {
console.log('[Mock] Getting API keys');
logger.info('Mock Getting API keys');
return {
success: true,
hasAnthropicKey: false,
@@ -1220,7 +1223,7 @@ function createMockSetupAPI(): SetupAPI {
},
deleteApiKey: async (provider: string) => {
console.log('[Mock] Deleting API key for:', provider);
logger.info('Mock Deleting API key for:', provider);
return { success: true, message: `API key for ${provider} deleted` };
},
@@ -1237,8 +1240,8 @@ function createMockSetupAPI(): SetupAPI {
},
verifyClaudeAuth: async (authMethod?: 'cli' | 'api_key', apiKey?: string) => {
console.log(
'[Mock] Verifying Claude auth with method:',
logger.info(
'Mock verifying Claude auth with method:',
authMethod,
apiKey ? '(with key)' : ''
);
@@ -1251,7 +1254,7 @@ function createMockSetupAPI(): SetupAPI {
},
getGhStatus: async () => {
console.log('[Mock] Getting GitHub CLI status');
logger.info('Mock Getting GitHub CLI status');
return {
success: true,
installed: false,
@@ -1278,7 +1281,7 @@ function createMockSetupAPI(): SetupAPI {
function createMockWorktreeAPI(): WorktreeAPI {
return {
mergeFeature: async (projectPath: string, featureId: string, options?: object) => {
console.log('[Mock] Merging feature:', {
logger.info('Mock Merging feature:', {
projectPath,
featureId,
options,
@@ -1287,7 +1290,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
getInfo: async (projectPath: string, featureId: string) => {
console.log('[Mock] Getting worktree info:', { projectPath, featureId });
logger.info('Mock Getting worktree info:', { projectPath, featureId });
return {
success: true,
worktreePath: `/mock/worktrees/${featureId}`,
@@ -1297,7 +1300,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
getStatus: async (projectPath: string, featureId: string) => {
console.log('[Mock] Getting worktree status:', {
logger.info('Mock Getting worktree status:', {
projectPath,
featureId,
});
@@ -1311,12 +1314,12 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
list: async (projectPath: string) => {
console.log('[Mock] Listing worktrees:', { projectPath });
logger.info('Mock Listing worktrees:', { projectPath });
return { success: true, worktrees: [] };
},
listAll: async (projectPath: string, includeDetails?: boolean) => {
console.log('[Mock] Listing all worktrees:', {
logger.info('Mock Listing all worktrees:', {
projectPath,
includeDetails,
});
@@ -1337,7 +1340,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
create: async (projectPath: string, branchName: string, baseBranch?: string) => {
console.log('[Mock] Creating worktree:', {
logger.info('Mock Creating worktree:', {
projectPath,
branchName,
baseBranch,
@@ -1353,7 +1356,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
delete: async (projectPath: string, worktreePath: string, deleteBranch?: boolean) => {
console.log('[Mock] Deleting worktree:', {
logger.info('Mock Deleting worktree:', {
projectPath,
worktreePath,
deleteBranch,
@@ -1368,7 +1371,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
commit: async (worktreePath: string, message: string) => {
console.log('[Mock] Committing changes:', { worktreePath, message });
logger.info('Mock Committing changes:', { worktreePath, message });
return {
success: true,
result: {
@@ -1381,7 +1384,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
push: async (worktreePath: string, force?: boolean) => {
console.log('[Mock] Pushing worktree:', { worktreePath, force });
logger.info('Mock Pushing worktree:', { worktreePath, force });
return {
success: true,
result: {
@@ -1393,7 +1396,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
createPR: async (worktreePath: string, options?: any) => {
console.log('[Mock] Creating PR:', { worktreePath, options });
logger.info('Mock Creating PR:', { worktreePath, options });
return {
success: true,
result: {
@@ -1408,7 +1411,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
getDiffs: async (projectPath: string, featureId: string) => {
console.log('[Mock] Getting file diffs:', { projectPath, featureId });
logger.info('Mock Getting file diffs:', { projectPath, featureId });
return {
success: true,
diff: "diff --git a/src/feature.ts b/src/feature.ts\n+++ new file\n@@ -0,0 +1,10 @@\n+export function feature() {\n+ return 'hello';\n+}",
@@ -1421,7 +1424,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
getFileDiff: async (projectPath: string, featureId: string, filePath: string) => {
console.log('[Mock] Getting file diff:', {
logger.info('Mock Getting file diff:', {
projectPath,
featureId,
filePath,
@@ -1434,7 +1437,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
pull: async (worktreePath: string) => {
console.log('[Mock] Pulling latest changes for:', worktreePath);
logger.info('Mock Pulling latest changes for:', worktreePath);
return {
success: true,
result: {
@@ -1446,7 +1449,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
checkoutBranch: async (worktreePath: string, branchName: string) => {
console.log('[Mock] Creating and checking out branch:', {
logger.info('Mock Creating and checking out branch:', {
worktreePath,
branchName,
});
@@ -1461,7 +1464,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
listBranches: async (worktreePath: string) => {
console.log('[Mock] Listing branches for:', worktreePath);
logger.info('Mock Listing branches for:', worktreePath);
return {
success: true,
result: {
@@ -1478,7 +1481,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
switchBranch: async (worktreePath: string, branchName: string) => {
console.log('[Mock] Switching to branch:', { worktreePath, branchName });
logger.info('Mock Switching to branch:', { worktreePath, branchName });
return {
success: true,
result: {
@@ -1490,7 +1493,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
openInEditor: async (worktreePath: string) => {
console.log('[Mock] Opening in editor:', worktreePath);
logger.info('Mock Opening in editor:', worktreePath);
return {
success: true,
result: {
@@ -1501,7 +1504,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
getDefaultEditor: async () => {
console.log('[Mock] Getting default editor');
logger.info('Mock Getting default editor');
return {
success: true,
result: {
@@ -1512,7 +1515,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
initGit: async (projectPath: string) => {
console.log('[Mock] Initializing git:', projectPath);
logger.info('Mock Initializing git:', projectPath);
return {
success: true,
result: {
@@ -1523,7 +1526,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
startDevServer: async (projectPath: string, worktreePath: string) => {
console.log('[Mock] Starting dev server:', { projectPath, worktreePath });
logger.info('Mock Starting dev server:', { projectPath, worktreePath });
return {
success: true,
result: {
@@ -1536,7 +1539,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
stopDevServer: async (worktreePath: string) => {
console.log('[Mock] Stopping dev server:', worktreePath);
logger.info('Mock Stopping dev server:', worktreePath);
return {
success: true,
result: {
@@ -1547,7 +1550,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
listDevServers: async () => {
console.log('[Mock] Listing dev servers');
logger.info('Mock Listing dev servers');
return {
success: true,
result: {
@@ -1557,7 +1560,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
getPRInfo: async (worktreePath: string, branchName: string) => {
console.log('[Mock] Getting PR info:', { worktreePath, branchName });
logger.info('Mock Getting PR info:', { worktreePath, branchName });
return {
success: true,
result: {
@@ -1573,7 +1576,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
function createMockGitAPI(): GitAPI {
return {
getDiffs: async (projectPath: string) => {
console.log('[Mock] Getting git diffs for project:', { projectPath });
logger.info('Mock Getting git diffs for project:', { projectPath });
return {
success: true,
diff: "diff --git a/src/feature.ts b/src/feature.ts\n+++ new file\n@@ -0,0 +1,10 @@\n+export function feature() {\n+ return 'hello';\n+}",
@@ -1586,7 +1589,7 @@ function createMockGitAPI(): GitAPI {
},
getFileDiff: async (projectPath: string, filePath: string) => {
console.log('[Mock] Getting git file diff:', { projectPath, filePath });
logger.info('Mock Getting git file diff:', { projectPath, filePath });
return {
success: true,
diff: `diff --git a/${filePath} b/${filePath}\n+++ new file\n@@ -0,0 +1,5 @@\n+// New content`,
@@ -1610,7 +1613,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
}
mockAutoModeRunning = true;
console.log(`[Mock] Auto mode started with maxConcurrency: ${maxConcurrency || 3}`);
logger.info(`Mock auto mode started with maxConcurrency: ${maxConcurrency || 3}`);
const featureId = 'auto-mode-0';
mockRunningFeatures.add(featureId);
@@ -1679,8 +1682,8 @@ function createMockAutoModeAPI(): AutoModeAPI {
};
}
console.log(
`[Mock] Running feature ${featureId} with useWorktrees: ${useWorktrees}, worktreePath: ${worktreePath}`
logger.info(
`Mock running feature ${featureId} with useWorktrees: ${useWorktrees}, worktreePath: ${worktreePath}`
);
mockRunningFeatures.add(featureId);
simulateAutoModeLoop(projectPath, featureId);
@@ -1847,7 +1850,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
};
}
console.log('[Mock] Follow-up feature:', {
logger.info('Mock Follow-up feature:', {
featureId,
prompt,
imagePaths,
@@ -1864,7 +1867,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
},
commitFeature: async (projectPath: string, featureId: string, worktreePath?: string) => {
console.log('[Mock] Committing feature:', {
logger.info('Mock Committing feature:', {
projectPath,
featureId,
worktreePath,
@@ -1909,7 +1912,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
editedPlan?: string,
feedback?: string
) => {
console.log('[Mock] Plan approval:', {
logger.info('Mock Plan approval:', {
projectPath,
featureId,
approved,
@@ -2070,7 +2073,7 @@ function createMockSuggestionsAPI(): SuggestionsAPI {
}
mockSuggestionsRunning = true;
console.log(`[Mock] Generating ${suggestionType} suggestions for: ${projectPath}`);
logger.info(`Mock generating ${suggestionType} suggestions for: ${projectPath}`);
// Simulate async suggestion generation
simulateSuggestionsGeneration(suggestionType);
@@ -2294,8 +2297,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
}
mockSpecRegenerationRunning = true;
console.log(
`[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
logger.info(
`Mock creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
);
// Simulate async spec creation
@@ -2319,8 +2322,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
}
mockSpecRegenerationRunning = true;
console.log(
`[Mock] Regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
logger.info(
`Mock regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
);
// Simulate async spec regeneration
@@ -2338,8 +2341,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
}
mockSpecRegenerationRunning = true;
console.log(
`[Mock] Generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}`
logger.info(
`Mock generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}`
);
// Simulate async feature generation
@@ -2604,7 +2607,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
// Store features in mock file system using features/{id}/feature.json pattern
return {
getAll: async (projectPath: string) => {
console.log('[Mock] Getting all features for:', projectPath);
logger.info('Mock Getting all features for:', projectPath);
// Check if test has set mock features via global variable
const testFeatures = (window as any).__mockFeatures;
@@ -2629,7 +2632,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
features.push(feature);
}
} catch (error) {
console.error('[Mock] Failed to parse feature:', error);
logger.error('Mock Failed to parse feature:', error);
}
}
@@ -2642,7 +2645,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
},
get: async (projectPath: string, featureId: string) => {
console.log('[Mock] Getting feature:', { projectPath, featureId });
logger.info('Mock Getting feature:', { projectPath, featureId });
const featurePath = `${projectPath}/.automaker/features/${featureId}/feature.json`;
const content = mockFileSystem[featurePath];
if (content) {
@@ -2652,7 +2655,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
},
create: async (projectPath: string, feature: Feature) => {
console.log('[Mock] Creating feature:', {
logger.info('Mock Creating feature:', {
projectPath,
featureId: feature.id,
});
@@ -2662,7 +2665,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
},
update: async (projectPath: string, featureId: string, updates: Partial<Feature>) => {
console.log('[Mock] Updating feature:', {
logger.info('Mock Updating feature:', {
projectPath,
featureId,
updates,
@@ -2678,7 +2681,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
},
delete: async (projectPath: string, featureId: string) => {
console.log('[Mock] Deleting feature:', { projectPath, featureId });
logger.info('Mock Deleting feature:', { projectPath, featureId });
const featurePath = `${projectPath}/.automaker/features/${featureId}/feature.json`;
delete mockFileSystem[featurePath];
// Also delete agent-output.md if it exists
@@ -2688,14 +2691,14 @@ function createMockFeaturesAPI(): FeaturesAPI {
},
getAgentOutput: async (projectPath: string, featureId: string) => {
console.log('[Mock] Getting agent output:', { projectPath, featureId });
logger.info('Mock Getting agent output:', { projectPath, featureId });
const agentOutputPath = `${projectPath}/.automaker/features/${featureId}/agent-output.md`;
const content = mockFileSystem[agentOutputPath];
return { success: true, content: content || null };
},
generateTitle: async (description: string) => {
console.log('[Mock] Generating title for:', description.substring(0, 50));
logger.info('Mock Generating title for:', description.substring(0, 50));
// Mock title generation - just take first few words
const words = description.split(/\s+/).slice(0, 6).join(' ');
const title = words.length > 40 ? words.substring(0, 40) + '...' : words;
@@ -2708,7 +2711,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
function createMockRunningAgentsAPI(): RunningAgentsAPI {
return {
getAll: async () => {
console.log('[Mock] Getting all running agents');
logger.info('Mock Getting all running agents');
// Return running agents from mock auto mode state
const runningAgents: RunningAgent[] = Array.from(mockRunningFeatures).map((featureId) => ({
featureId,
@@ -2733,7 +2736,7 @@ let mockValidationCallbacks: ((event: IssueValidationEvent) => void)[] = [];
function createMockGitHubAPI(): GitHubAPI {
return {
checkRemote: async (projectPath: string) => {
console.log('[Mock] Checking GitHub remote for:', projectPath);
logger.info('Mock Checking GitHub remote for:', projectPath);
return {
success: true,
hasGitHubRemote: false,
@@ -2743,7 +2746,7 @@ function createMockGitHubAPI(): GitHubAPI {
};
},
listIssues: async (projectPath: string) => {
console.log('[Mock] Listing GitHub issues for:', projectPath);
logger.info('Mock Listing GitHub issues for:', projectPath);
return {
success: true,
openIssues: [],
@@ -2751,7 +2754,7 @@ function createMockGitHubAPI(): GitHubAPI {
};
},
listPRs: async (projectPath: string) => {
console.log('[Mock] Listing GitHub PRs for:', projectPath);
logger.info('Mock Listing GitHub PRs for:', projectPath);
return {
success: true,
openPRs: [],
@@ -2759,7 +2762,7 @@ function createMockGitHubAPI(): GitHubAPI {
};
},
validateIssue: async (projectPath: string, issue: IssueValidationInput, model?: ModelAlias) => {
console.log('[Mock] Starting async validation:', { projectPath, issue, model });
logger.info('Mock Starting async validation:', { projectPath, issue, model });
// Simulate async validation in background
setTimeout(() => {
@@ -2800,7 +2803,7 @@ function createMockGitHubAPI(): GitHubAPI {
};
},
getValidationStatus: async (projectPath: string, issueNumber?: number) => {
console.log('[Mock] Getting validation status:', { projectPath, issueNumber });
logger.info('Mock Getting validation status:', { projectPath, issueNumber });
return {
success: true,
isRunning: false,
@@ -2808,21 +2811,21 @@ function createMockGitHubAPI(): GitHubAPI {
};
},
stopValidation: async (projectPath: string, issueNumber: number) => {
console.log('[Mock] Stopping validation:', { projectPath, issueNumber });
logger.info('Mock Stopping validation:', { projectPath, issueNumber });
return {
success: true,
message: `Validation for issue #${issueNumber} stopped`,
};
},
getValidations: async (projectPath: string, issueNumber?: number) => {
console.log('[Mock] Getting validations:', { projectPath, issueNumber });
logger.info('Mock Getting validations:', { projectPath, issueNumber });
return {
success: true,
validations: [],
};
},
markValidationViewed: async (projectPath: string, issueNumber: number) => {
console.log('[Mock] Marking validation as viewed:', { projectPath, issueNumber });
logger.info('Mock Marking validation as viewed:', { projectPath, issueNumber });
return {
success: true,
};
@@ -2834,7 +2837,7 @@ function createMockGitHubAPI(): GitHubAPI {
};
},
getIssueComments: async (projectPath: string, issueNumber: number, cursor?: string) => {
console.log('[Mock] Getting issue comments:', { projectPath, issueNumber, cursor });
logger.info('Mock Getting issue comments:', { projectPath, issueNumber, cursor });
return {
success: true,
comments: [],

View File

@@ -10,6 +10,10 @@
* user confirmation or server-side path resolution.
*/
import { createLogger } from '@automaker/utils/logger';
const logger = createLogger('FilePicker');
/**
* Directory picker result with structure information for server-side resolution
*/
@@ -65,18 +69,18 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
focusTimeout = null;
}
console.log('[FilePicker] Change event fired');
logger.info('Change event fired');
const files = input.files;
console.log('[FilePicker] Files selected:', files?.length || 0);
logger.info('Files selected:', files?.length || 0);
if (!files || files.length === 0) {
console.log('[FilePicker] No files selected');
logger.info('No files selected');
safeResolve(null);
return;
}
const firstFile = files[0];
console.log('[FilePicker] First file:', {
logger.info('First file:', {
name: firstFile.name,
webkitRelativePath: firstFile.webkitRelativePath,
// @ts-expect-error - path property is non-standard but available in some browsers
@@ -92,12 +96,12 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
if (firstFile.path) {
// @ts-expect-error - path property is non-standard but available in some browsers
const filePath = firstFile.path as string;
console.log('[FilePicker] Found file.path:', filePath);
logger.info('Found file.path:', filePath);
// Extract directory path (remove filename)
const lastSeparator = Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/'));
if (lastSeparator > 0) {
const absolutePath = filePath.substring(0, lastSeparator);
console.log('[FilePicker] Found absolute path:', absolutePath);
logger.info('Found absolute path:', absolutePath);
// Return as directory name for now - server can validate it directly
directoryName = absolutePath;
}
@@ -106,11 +110,11 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
// Method 2: Extract directory name from webkitRelativePath
if (directoryName === 'Selected Directory' && firstFile.webkitRelativePath) {
const relativePath = firstFile.webkitRelativePath;
console.log('[FilePicker] Using webkitRelativePath:', relativePath);
logger.info('Using webkitRelativePath:', relativePath);
const pathParts = relativePath.split('/');
if (pathParts.length > 0) {
directoryName = pathParts[0]; // Top-level directory name
console.log('[FilePicker] Extracted directory name:', directoryName);
logger.info('Extracted directory name:', directoryName);
}
}
@@ -127,7 +131,7 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
}
}
console.log('[FilePicker] Directory info:', {
logger.info('Directory info:', {
directoryName,
fileCount: files.length,
sampleFiles: sampleFiles.slice(0, 5), // Log first 5
@@ -147,7 +151,7 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
// Only resolve as canceled if change event hasn't fired after a delay
focusTimeout = setTimeout(() => {
if (!resolved && !changeEventFired && (!input.files || input.files.length === 0)) {
console.log('[FilePicker] Dialog canceled (no files after focus and no change event)');
logger.info('Dialog canceled (no files after focus and no change event)');
safeResolve(null);
}
}, 2000); // Increased timeout for Windows - give it time
@@ -155,19 +159,19 @@ export async function openDirectoryPicker(): Promise<DirectoryPickerResult | nul
// Add to DOM temporarily
document.body.appendChild(input);
console.log('[FilePicker] Opening directory picker...');
logger.info('Opening directory picker...');
// Try to show picker programmatically
if ('showPicker' in HTMLInputElement.prototype) {
try {
(input as any).showPicker();
console.log('[FilePicker] Using showPicker()');
logger.info('Using showPicker()');
} catch (error) {
console.log('[FilePicker] showPicker() failed, using click()', error);
logger.info('showPicker() failed, using click()', error);
input.click();
}
} else {
console.log('[FilePicker] Using click()');
logger.info('Using click()');
input.click();
}

View File

@@ -5,6 +5,7 @@
* but communicates with the backend server via HTTP/WebSocket.
*/
import { createLogger } from '@automaker/utils/logger';
import type {
ElectronAPI,
FileResult,
@@ -32,6 +33,8 @@ import type { Feature, ClaudeUsageResponse } from '@/store/app-store';
import type { WorktreeAPI, GitAPI, ModelDefinition, ProviderStatus } from '@/types/electron';
import { getGlobalFileBrowser } from '@/contexts/file-browser-context';
const logger = createLogger('HttpClient');
// Cached server URL (set during initialization in Electron mode)
let cachedServerUrl: string | null = null;
@@ -46,9 +49,9 @@ export const initServerUrl = async (): Promise<void> => {
if (electron?.getServerUrl) {
try {
cachedServerUrl = await electron.getServerUrl();
console.log('[HTTP Client] Server URL from Electron:', cachedServerUrl);
logger.info('Server URL from Electron:', cachedServerUrl);
} catch (error) {
console.warn('[HTTP Client] Failed to get server URL from Electron:', error);
logger.warn('Failed to get server URL from Electron:', error);
}
}
};
@@ -145,16 +148,16 @@ export const initApiKey = async (): Promise<void> => {
try {
cachedApiKey = await window.electronAPI.getApiKey();
if (cachedApiKey) {
console.log('[HTTP Client] Using API key from Electron');
logger.info('Using API key from Electron');
return;
}
} catch (error) {
console.warn('[HTTP Client] Failed to get API key from Electron:', error);
logger.warn('Failed to get API key from Electron:', error);
}
}
// In web mode, authentication is handled via HTTP-only cookies
console.log('[HTTP Client] Web mode - using cookie-based authentication');
logger.info('Web mode - using cookie-based authentication');
} finally {
// Mark as initialized after completion, regardless of success or failure
apiKeyInitialized = true;
@@ -182,7 +185,7 @@ export const checkAuthStatus = async (): Promise<{
required: data.required ?? true,
};
} catch (error) {
console.error('[HTTP Client] Failed to check auth status:', error);
logger.error('Failed to check auth status:', error);
return { authenticated: false, required: true };
}
};
@@ -207,23 +210,23 @@ export const login = async (
// Store the session token if login succeeded
if (data.success && data.token) {
setSessionToken(data.token);
console.log('[HTTP Client] Session token stored after login');
logger.info('Session token stored after login');
// Verify the session is actually working by making a request to an authenticated endpoint
const verified = await verifySession();
if (!verified) {
console.error('[HTTP Client] Login appeared successful but session verification failed');
logger.error('Login appeared successful but session verification failed');
return {
success: false,
error: 'Session verification failed. Please try again.',
};
}
console.log('[HTTP Client] Login verified successfully');
logger.info('Login verified successfully');
}
return data;
} catch (error) {
console.error('[HTTP Client] Login failed:', error);
logger.error('Login failed:', error);
return { success: false, error: 'Network error' };
}
};
@@ -243,20 +246,20 @@ export const fetchSessionToken = async (): Promise<boolean> => {
});
if (!response.ok) {
console.log('[HTTP Client] Failed to check auth status');
logger.info('Failed to check auth status');
return false;
}
const data = await response.json();
if (data.success && data.authenticated) {
console.log('[HTTP Client] Session cookie is valid');
logger.info('Session cookie is valid');
return true;
}
console.log('[HTTP Client] Session cookie is not authenticated');
logger.info('Session cookie is not authenticated');
return false;
} catch (error) {
console.error('[HTTP Client] Failed to check session:', error);
logger.error('Failed to check session:', error);
return false;
}
};
@@ -273,11 +276,11 @@ export const logout = async (): Promise<{ success: boolean }> => {
// Clear the cached session token
clearSessionToken();
console.log('[HTTP Client] Session token cleared on logout');
logger.info('Session token cleared on logout');
return await response.json();
} catch (error) {
console.error('[HTTP Client] Logout failed:', error);
logger.error('Logout failed:', error);
return { success: false };
}
};
@@ -310,7 +313,7 @@ export const verifySession = async (): Promise<boolean> => {
// Check for authentication errors
if (response.status === 401 || response.status === 403) {
console.warn('[HTTP Client] Session verification failed - session expired or invalid');
logger.warn('Session verification failed - session expired or invalid');
// Clear the session since it's no longer valid
clearSessionToken();
// Try to clear the cookie via logout (fire and forget)
@@ -324,14 +327,14 @@ export const verifySession = async (): Promise<boolean> => {
}
if (!response.ok) {
console.warn('[HTTP Client] Session verification failed with status:', response.status);
logger.warn('Session verification failed with status:', response.status);
return false;
}
console.log('[HTTP Client] Session verified successfully');
logger.info('Session verified successfully');
return true;
} catch (error) {
console.error('[HTTP Client] Session verification error:', error);
logger.error('Session verification error:', error);
return false;
}
};
@@ -350,14 +353,14 @@ export const checkSandboxEnvironment = async (): Promise<{
});
if (!response.ok) {
console.warn('[HTTP Client] Failed to check sandbox environment');
logger.warn('Failed to check sandbox environment');
return { isContainerized: false, error: 'Failed to check environment' };
}
const data = await response.json();
return { isContainerized: data.isContainerized ?? false };
} catch (error) {
console.error('[HTTP Client] Sandbox environment check failed:', error);
logger.error('Sandbox environment check failed:', error);
return { isContainerized: false, error: 'Network error' };
}
};
@@ -399,7 +402,7 @@ export class HttpApiClient implements ElectronAPI {
this.connectWebSocket();
})
.catch((error) => {
console.error('[HttpApiClient] API key initialization failed:', error);
logger.error('API key initialization failed:', error);
// Still attempt WebSocket connection - it may work with cookie auth
this.connectWebSocket();
});
@@ -428,7 +431,7 @@ export class HttpApiClient implements ElectronAPI {
});
if (!response.ok) {
console.warn('[HttpApiClient] Failed to fetch wsToken:', response.status);
logger.warn('Failed to fetch wsToken:', response.status);
return null;
}
@@ -439,7 +442,7 @@ export class HttpApiClient implements ElectronAPI {
return null;
} catch (error) {
console.error('[HttpApiClient] Error fetching wsToken:', error);
logger.error('Error fetching wsToken:', error);
return null;
}
}
@@ -456,9 +459,7 @@ export class HttpApiClient implements ElectronAPI {
if (isElectronMode()) {
const apiKey = getApiKey();
if (!apiKey) {
console.warn(
'[HttpApiClient] Electron mode: API key not ready, delaying WebSocket connect'
);
logger.warn('Electron mode: API key not ready, delaying WebSocket connect');
this.isConnecting = false;
if (!this.reconnectTimer) {
this.reconnectTimer = setTimeout(() => {
@@ -482,12 +483,12 @@ export class HttpApiClient implements ElectronAPI {
this.establishWebSocket(`${wsUrl}?wsToken=${encodeURIComponent(wsToken)}`);
} else {
// Fallback: try connecting without token (will fail if not authenticated)
console.warn('[HttpApiClient] No wsToken available, attempting connection anyway');
logger.warn('No wsToken available, attempting connection anyway');
this.establishWebSocket(wsUrl);
}
})
.catch((error) => {
console.error('[HttpApiClient] Failed to prepare WebSocket connection:', error);
logger.error('Failed to prepare WebSocket connection:', error);
this.isConnecting = false;
});
}
@@ -500,7 +501,7 @@ export class HttpApiClient implements ElectronAPI {
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log('[HttpApiClient] WebSocket connected');
logger.info('WebSocket connected');
this.isConnecting = false;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
@@ -511,8 +512,8 @@ export class HttpApiClient implements ElectronAPI {
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log(
'[HttpApiClient] WebSocket message:',
logger.info(
'WebSocket message:',
data.type,
'hasPayload:',
!!data.payload,
@@ -521,16 +522,16 @@ export class HttpApiClient implements ElectronAPI {
);
const callbacks = this.eventCallbacks.get(data.type);
if (callbacks) {
console.log('[HttpApiClient] Dispatching to', callbacks.size, 'callbacks');
logger.info('Dispatching to', callbacks.size, 'callbacks');
callbacks.forEach((cb) => cb(data.payload));
}
} catch (error) {
console.error('[HttpApiClient] Failed to parse WebSocket message:', error);
logger.error('Failed to parse WebSocket message:', error);
}
};
this.ws.onclose = () => {
console.log('[HttpApiClient] WebSocket disconnected');
logger.info('WebSocket disconnected');
this.isConnecting = false;
this.ws = null;
// Attempt to reconnect after 5 seconds
@@ -543,11 +544,11 @@ export class HttpApiClient implements ElectronAPI {
};
this.ws.onerror = (error) => {
console.error('[HttpApiClient] WebSocket error:', error);
logger.error('WebSocket error:', error);
this.isConnecting = false;
};
} catch (error) {
console.error('[HttpApiClient] Failed to create WebSocket:', error);
logger.error('Failed to create WebSocket:', error);
this.isConnecting = false;
}
}
@@ -747,7 +748,7 @@ export class HttpApiClient implements ElectronAPI {
const fileBrowser = getGlobalFileBrowser();
if (!fileBrowser) {
console.error('File browser not initialized');
logger.error('File browser not initialized');
return { canceled: true, filePaths: [] };
}
@@ -769,7 +770,7 @@ export class HttpApiClient implements ElectronAPI {
return { canceled: false, filePaths: [result.path] };
}
console.error('Invalid directory:', result.error || 'Path not allowed');
logger.error('Invalid directory:', result.error || 'Path not allowed');
return { canceled: true, filePaths: [] };
}
@@ -777,7 +778,7 @@ export class HttpApiClient implements ElectronAPI {
const fileBrowser = getGlobalFileBrowser();
if (!fileBrowser) {
console.error('File browser not initialized');
logger.error('File browser not initialized');
return { canceled: true, filePaths: [] };
}
@@ -796,7 +797,7 @@ export class HttpApiClient implements ElectronAPI {
return { canceled: false, filePaths: [path] };
}
console.error('File not found');
logger.error('File not found');
return { canceled: true, filePaths: [] };
}
@@ -1910,5 +1911,5 @@ export function getHttpApiClient(): HttpApiClient {
// This ensures the init promise is created early, even before React components mount
// The actual async work happens in the background and won't block module loading
initApiKey().catch((error) => {
console.error('[HTTP Client] Failed to initialize API key:', error);
logger.error('Failed to initialize API key:', error);
});

View File

@@ -5,8 +5,11 @@
* new or existing projects.
*/
import { createLogger } from '@automaker/utils/logger';
import { getElectronAPI } from './electron';
const logger = createLogger('ProjectInit');
export interface ProjectInitResult {
success: boolean;
isNewProject: boolean;
@@ -72,22 +75,22 @@ export async function initializeProject(projectPath: string): Promise<ProjectIni
// Initialize git repository if it doesn't exist
const gitDirExists = await api.exists(`${projectPath}/.git`);
if (!gitDirExists) {
console.log('[project-init] Initializing git repository...');
logger.info('Initializing git repository...');
try {
// Initialize git and create an initial empty commit via server route
const result = await api.worktree?.initGit(projectPath);
if (result?.success && result.result?.initialized) {
createdFiles.push('.git');
console.log('[project-init] Git repository initialized with initial commit');
logger.info('Git repository initialized with initial commit');
} else if (result?.success && !result.result?.initialized) {
// Git already existed (shouldn't happen since we checked, but handle it)
existingFiles.push('.git');
console.log('[project-init] Git repository already exists');
logger.info('Git repository already exists');
} else {
console.warn('[project-init] Failed to initialize git repository:', result?.error);
logger.warn('Failed to initialize git repository:', result?.error);
}
} catch (gitError) {
console.warn('[project-init] Failed to initialize git repository:', gitError);
logger.warn('Failed to initialize git repository:', gitError);
// Don't fail the whole initialization if git init fails
}
} else {
@@ -123,7 +126,7 @@ export async function initializeProject(projectPath: string): Promise<ProjectIni
existingFiles,
};
} catch (error) {
console.error('[project-init] Failed to initialize project:', error);
logger.error('Failed to initialize project:', error);
return {
success: false,
isNewProject: false,
@@ -153,7 +156,7 @@ export async function isProjectInitialized(projectPath: string): Promise<boolean
return true;
} catch (error) {
console.error('[project-init] Error checking project initialization:', error);
logger.error('Error checking project initialization:', error);
return false;
}
}
@@ -191,7 +194,7 @@ export async function getProjectInitStatus(projectPath: string): Promise<{
existingFiles,
};
} catch (error) {
console.error('[project-init] Error getting project status:', error);
logger.error('Error getting project status:', error);
return {
initialized: false,
missingFiles: REQUIRED_STRUCTURE.directories,
@@ -212,7 +215,7 @@ export async function hasAppSpec(projectPath: string): Promise<boolean> {
const fullPath = `${projectPath}/.automaker/app_spec.txt`;
return await api.exists(fullPath);
} catch (error) {
console.error('[project-init] Error checking app_spec.txt:', error);
logger.error('Error checking app_spec.txt:', error);
return false;
}
}
@@ -229,7 +232,7 @@ export async function hasAutomakerDir(projectPath: string): Promise<boolean> {
const fullPath = `${projectPath}/.automaker`;
return await api.exists(fullPath);
} catch (error) {
console.error('[project-init] Error checking .automaker dir:', error);
logger.error('Error checking .automaker dir:', error);
return false;
}
}

View File

@@ -3,11 +3,14 @@
* Centralizes the logic for determining where projects should be created/opened
*/
import { createLogger } from '@automaker/utils/logger';
import { getHttpApiClient } from './http-api-client';
import { getElectronAPI } from './electron';
import { getItem, setItem } from './storage';
import path from 'path';
const logger = createLogger('WorkspaceConfig');
const LAST_PROJECT_DIR_KEY = 'automaker:lastProjectDir';
/**
@@ -20,9 +23,7 @@ async function getDefaultDocumentsPath(): Promise<string | null> {
const documentsPath = await api.getPath('documents');
return path.join(documentsPath, 'Automaker');
} catch (error) {
if (typeof window !== 'undefined' && window.console) {
window.console.error('Failed to get documents path:', error);
}
logger.error('Failed to get documents path:', error);
return null;
}
}
@@ -81,9 +82,7 @@ export async function getDefaultWorkspaceDirectory(): Promise<string | null> {
const documentsPath = await getDefaultDocumentsPath();
return documentsPath;
} catch (error) {
if (typeof window !== 'undefined' && window.console) {
window.console.error('Failed to get default workspace directory:', error);
}
logger.error('Failed to get default workspace directory:', error);
// On error, try last used dir and Documents
const lastUsedDir = getItem(LAST_PROJECT_DIR_KEY);