Refactor code for improved readability and maintainability

- Updated function calls in build_commands.py for better parameter alignment.
- Enhanced merge conflict checks in workspace_commands.py and workspace.py for clarity.
- Improved logging and output formatting in worktree.py and other files.
- Added proactive swap information to rate-limit-detector.ts and profile-storage.ts.
- Updated usage monitoring methods in usage-monitor.ts and IPC types for better integration.
This commit is contained in:
AndyMik90
2025-12-17 13:25:38 +01:00
parent e5b9488a86
commit 473b04530a
12 changed files with 93 additions and 58 deletions
@@ -13,10 +13,11 @@ export const STORE_VERSION = 3; // Bumped for encrypted token storage
*/
export const DEFAULT_AUTO_SWITCH_SETTINGS: ClaudeAutoSwitchSettings = {
enabled: false,
sessionThreshold: 85, // Consider switching at 85% session usage
weeklyThreshold: 90, // Consider switching at 90% weekly usage
proactiveSwapEnabled: false, // Proactive monitoring disabled by default
sessionThreshold: 95, // Consider switching at 95% session usage
weeklyThreshold: 99, // Consider switching at 99% weekly usage
autoSwitchOnRateLimit: false, // Prompt user by default
usageCheckInterval: 0 // Disabled by default (in ms, e.g., 300000 = 5 min)
usageCheckInterval: 30000 // Check every 30s when enabled (0 = disabled)
};
/**
@@ -221,32 +221,19 @@ export class UsageMonitor extends EventEmitter {
/**
* Fetch usage via CLI /usage command (fallback)
* Note: This is a fallback method. The API method is preferred.
* CLI-based fetching would require spawning a Claude process and parsing output,
* which is complex. For now, we rely on the API method.
*/
private async fetchUsageViaCLI(
profileId: string,
profileName: string
_profileId: string,
_profileName: string
): Promise<ClaudeUsageSnapshot | null> {
const profileManager = getClaudeProfileManager();
// Use existing CLI-based usage fetching mechanism
const result = await profileManager.fetchProfileUsage(profileId);
if (!result.success || !result.data) {
return null;
}
return {
sessionPercent: result.data.sessionUsagePercent || 0,
weeklyPercent: result.data.weeklyUsagePercent || 0,
sessionResetTime: result.data.sessionResetTime,
weeklyResetTime: result.data.weeklyResetTime,
profileId,
profileName,
fetchedAt: new Date(),
limitType: (result.data.weeklyUsagePercent || 0) > (result.data.sessionUsagePercent || 0)
? 'weekly'
: 'session'
};
// CLI-based usage fetching is not implemented yet.
// The API method should handle most cases. If we need CLI fallback,
// we would need to spawn a Claude process with /usage command and parse the output.
console.log('[UsageMonitor] CLI fallback not implemented, API method should be used');
return null;
}
/**
@@ -1,8 +1,9 @@
import { ipcMain } from 'electron';
import type { BrowserWindow } from 'electron';
import { IPC_CHANNELS } from '../../shared/constants';
import type { IPCResult, TerminalCreateOptions, ClaudeProfile, ClaudeProfileSettings } from '../../shared/types';
import type { IPCResult, TerminalCreateOptions, ClaudeProfile, ClaudeProfileSettings, ClaudeUsageSnapshot } from '../../shared/types';
import { getClaudeProfileManager } from '../claude-profile-manager';
import { getUsageMonitor } from '../claude-profile/usage-monitor';
import { TerminalManager } from '../terminal-manager';
import { projectStore } from '../project-store';
import { terminalNameGenerator } from '../terminal-name-generator';
@@ -558,16 +559,15 @@ export function registerTerminalHandlers(
* Call this after mainWindow is created
*/
export function initializeUsageMonitorForwarding(mainWindow: BrowserWindow): void {
const { getUsageMonitor } = require('../claude-profile/usage-monitor');
const monitor = getUsageMonitor();
// Forward usage updates to renderer
monitor.on('usage-updated', (usage: import('../../shared/types').ClaudeUsageSnapshot) => {
monitor.on('usage-updated', (usage: ClaudeUsageSnapshot) => {
mainWindow.webContents.send(IPC_CHANNELS.USAGE_UPDATED, usage);
});
// Forward proactive swap notifications to renderer
monitor.on('show-swap-notification', (notification: any) => {
monitor.on('show-swap-notification', (notification: unknown) => {
mainWindow.webContents.send(IPC_CHANNELS.PROACTIVE_SWAP_NOTIFICATION, notification);
});
@@ -228,6 +228,17 @@ export interface SDKRateLimitInfo {
detectedAt: Date;
/** Original error message */
originalError?: string;
// Auto-swap information
/** Whether this rate limit was automatically handled via account swap */
wasAutoSwapped?: boolean;
/** Profile that was swapped to (if auto-swapped) */
swappedToProfile?: {
id: string;
name: string;
};
/** Why the swap occurred: 'proactive' (before limit) or 'reactive' (after limit hit) */
swapReason?: 'proactive' | 'reactive';
}
/**
@@ -167,7 +167,7 @@ export function EnvironmentSettings({
<Star className="h-3 w-3" />
Active
</span>
{activeProfile.oauthToken ? (
{(activeProfile.oauthToken || (activeProfile.isDefault && activeProfile.configDir)) ? (
<span className="text-xs bg-success/20 text-success px-1.5 py-0.5 rounded flex items-center gap-1">
<Check className="h-3 w-3" />
Authenticated
@@ -72,20 +72,11 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
// Listen for OAuth authentication completion
useEffect(() => {
const unsubscribe = window.electronAPI.onTerminalOAuthToken(async (info) => {
console.log('[IntegrationSettings] OAuth authentication event:', {
terminalId: info.terminalId,
profileId: info.profileId,
email: info.email,
success: info.success
});
if (info.success && info.profileId) {
// Reload profiles to show updated state
await loadClaudeProfiles();
// Show simple success notification
alert(`✅ Profile authenticated successfully!\n\n${info.email ? `Account: ${info.email}` : 'Authentication complete.'}\n\nYou can now use this profile.`);
} else if (!info.success) {
console.log('[IntegrationSettings] Authentication detected but not saved:', info.message);
}
});
@@ -393,7 +384,7 @@ export function IntegrationSettings({ settings, onSettingsChange, isOpen }: Inte
Active
</span>
)}
{profile.oauthToken ? (
{(profile.oauthToken || (profile.isDefault && profile.configDir)) ? (
<span className="text-xs bg-success/20 text-success px-1.5 py-0.5 rounded flex items-center gap-1">
<Check className="h-3 w-3" />
Authenticated
@@ -39,10 +39,11 @@ export const claudeProfileMock = {
success: true,
data: {
enabled: false,
sessionThreshold: 80,
weeklyThreshold: 90,
proactiveSwapEnabled: false,
sessionThreshold: 95,
weeklyThreshold: 99,
autoSwitchOnRateLimit: false,
usageCheckInterval: 0
usageCheckInterval: 30000
}
}),
@@ -57,5 +58,15 @@ export const claudeProfileMock = {
onSDKRateLimit: () => () => {},
retryWithProfile: async () => ({ success: true })
retryWithProfile: async () => ({ success: true }),
// Usage Monitoring (Proactive Account Switching)
requestUsageUpdate: async () => ({
success: true,
data: null
}),
onUsageUpdated: () => () => {},
onProactiveSwapNotification: () => () => {}
};
+15 -1
View File
@@ -51,7 +51,8 @@ import type {
ClaudeProfileSettings,
ClaudeProfile,
ClaudeAutoSwitchSettings,
ClaudeAuthResult
ClaudeAuthResult,
ClaudeUsageSnapshot
} from './agent';
import type { AppSettings, SourceEnvConfig, SourceEnvCheckResult, AutoBuildSourceUpdateCheck, AutoBuildSourceUpdateProgress } from './settings';
import type {
@@ -206,6 +207,19 @@ export interface ElectronAPI {
/** Retry a rate-limited operation with a different profile */
retryWithProfile: (request: RetryWithProfileRequest) => Promise<IPCResult>;
// Usage Monitoring (Proactive Account Switching)
/** Request current usage snapshot */
requestUsageUpdate: () => Promise<IPCResult<ClaudeUsageSnapshot | null>>;
/** Listen for usage data updates */
onUsageUpdated: (callback: (usage: ClaudeUsageSnapshot) => void) => () => void;
/** Listen for proactive swap notifications */
onProactiveSwapNotification: (callback: (notification: {
fromProfile: { id: string; name: string };
toProfile: { id: string; name: string };
reason: string;
usageSnapshot: ClaudeUsageSnapshot;
}) => void) => () => void;
// App settings
getSettings: () => Promise<IPCResult<AppSettings>>;
saveSettings: (settings: Partial<AppSettings>) => Promise<IPCResult>;
+5 -2
View File
@@ -184,8 +184,11 @@ def handle_build_command(
source_spec_dir = spec_dir
working_dir, worktree_manager, localized_spec_dir = setup_workspace(
project_dir, spec_dir.name, workspace_mode, source_spec_dir=spec_dir,
base_branch=base_branch
project_dir,
spec_dir.name,
workspace_mode,
source_spec_dir=spec_dir,
base_branch=base_branch,
)
# Use the localized spec directory (inside worktree) for AI access
if localized_spec_dir:
+9 -5
View File
@@ -266,7 +266,11 @@ def _check_git_merge_conflicts(project_dir: Path, spec_name: str) -> dict:
if match:
file_path = match.group(1).strip()
# Skip .auto-claude files - they should never be merged
if file_path and file_path not in result["conflicting_files"] and not _is_auto_claude_file(file_path):
if (
file_path
and file_path not in result["conflicting_files"]
and not _is_auto_claude_file(file_path)
):
result["conflicting_files"].append(file_path)
# Fallback: if we didn't parse conflicts, use diff to find files changed in both branches
@@ -300,7 +304,9 @@ def _check_git_merge_conflicts(project_dir: Path, spec_name: str) -> dict:
# Files modified in both = potential conflicts
# Filter out .auto-claude files - they should never be merged
conflicting = main_files & spec_files
result["conflicting_files"] = [f for f in conflicting if not _is_auto_claude_file(f)]
result["conflicting_files"] = [
f for f in conflicting if not _is_auto_claude_file(f)
]
debug(
MODULE, f"Found {len(conflicting)} files modified in both branches"
)
@@ -447,9 +453,7 @@ def handle_merge_preview_command(project_dir: Path, spec_name: str) -> dict:
# Filter lock files from the git conflicts list for the response
non_lock_conflicting_files = [
f
for f in git_conflicts.get("conflicting_files", [])
if not is_lock_file(f)
f for f in git_conflicts.get("conflicting_files", []) if not is_lock_file(f)
]
result = {
+15 -4
View File
@@ -550,7 +550,11 @@ def _check_git_conflicts(project_dir: Path, spec_name: str) -> dict:
if match:
file_path = match.group(1).strip()
# Skip .auto-claude files - they should never be merged
if file_path and file_path not in result["conflicting_files"] and not _is_auto_claude_file(file_path):
if (
file_path
and file_path not in result["conflicting_files"]
and not _is_auto_claude_file(file_path)
):
result["conflicting_files"].append(file_path)
# Fallback: if we didn't parse conflicts, use diff to find files changed in both branches
@@ -582,7 +586,9 @@ def _check_git_conflicts(project_dir: Path, spec_name: str) -> dict:
# Files modified in both = potential conflicts
# Filter out .auto-claude files - they should never be merged
conflicting = main_files & spec_files
result["conflicting_files"] = [f for f in conflicting if not _is_auto_claude_file(f)]
result["conflicting_files"] = [
f for f in conflicting if not _is_auto_claude_file(f)
]
except Exception as e:
print(muted(f" Error checking git conflicts: {e}"))
@@ -735,7 +741,10 @@ def _resolve_git_conflicts_with_ai(
# They must be regenerated after merge by running the package manager
# (e.g., npm install, pnpm install, uv sync, cargo update)
lock_files_excluded.append(file_path)
debug(MODULE, f" {file_path}: lock file (excluded - regenerate after merge)")
debug(
MODULE,
f" {file_path}: lock file (excluded - regenerate after merge)",
)
else:
# Regular file - needs AI merge
files_needing_ai_merge.append(
@@ -928,7 +937,9 @@ def _resolve_git_conflicts_with_ai(
if lock_files_excluded:
result["lock_files_excluded"] = lock_files_excluded
print()
print(muted(f" {len(lock_files_excluded)} lock file(s) excluded from merge:"))
print(
muted(f" {len(lock_files_excluded)} lock file(s) excluded from merge:")
)
for lock_file in lock_files_excluded:
print(muted(f" - {lock_file}"))
print()
+3 -1
View File
@@ -127,7 +127,9 @@ class WorktreeManager:
break
if files_to_unstage:
print(f"Unstaging {len(files_to_unstage)} auto-claude/gitignored file(s)...")
print(
f"Unstaging {len(files_to_unstage)} auto-claude/gitignored file(s)..."
)
# Unstage each file
for file in files_to_unstage:
self._run_git(["reset", "HEAD", "--", file])