improvements

This commit is contained in:
iliya 2026-02-27 15:58:38 +02:00
parent 427c5478e1
commit 995b6ef843
10 changed files with 48 additions and 42 deletions

View file

@ -9,11 +9,10 @@
* - Handle JSON parse errors gracefully * - Handle JSON parse errors gracefully
*/ */
import { setClaudeBasePathOverride } from '@main/utils/pathDecoder'; import { getHomeDir, setClaudeBasePathOverride } from '@main/utils/pathDecoder';
import { validateRegexPattern } from '@main/utils/regexValidation'; import { validateRegexPattern } from '@main/utils/regexValidation';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { DEFAULT_TRIGGERS, TriggerManager } from './TriggerManager'; import { DEFAULT_TRIGGERS, TriggerManager } from './TriggerManager';
@ -23,7 +22,7 @@ import type { SshConnectionProfile } from '@shared/types/api';
const logger = createLogger('Service:ConfigManager'); const logger = createLogger('Service:ConfigManager');
const CONFIG_DIR = path.join(os.homedir(), '.claude'); const CONFIG_DIR = path.join(getHomeDir(), '.claude');
const CONFIG_FILENAME = 'claude-devtools-config.json'; const CONFIG_FILENAME = 'claude-devtools-config.json';
const DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, CONFIG_FILENAME); const DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, CONFIG_FILENAME);

View file

@ -12,11 +12,11 @@
* - Emit IPC events to renderer: notification:new, notification:updated * - Emit IPC events to renderer: notification:new, notification:updated
*/ */
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { type BrowserWindow, Notification } from 'electron'; import { type BrowserWindow, Notification } from 'electron';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { type DetectedError } from '../error/ErrorMessageBuilder'; import { type DetectedError } from '../error/ErrorMessageBuilder';
@ -77,7 +77,7 @@ const MAX_NOTIFICATIONS = 100;
const THROTTLE_MS = 5000; const THROTTLE_MS = 5000;
/** Path to notifications storage file */ /** Path to notifications storage file */
const NOTIFICATIONS_PATH = path.join(os.homedir(), '.claude', 'claude-devtools-notifications.json'); const NOTIFICATIONS_PATH = path.join(getHomeDir(), '.claude', 'claude-devtools-notifications.json');
// ============================================================================= // =============================================================================
// NotificationManager Class // NotificationManager Class

View file

@ -8,9 +8,9 @@
* - Gracefully handle missing/unreadable files * - Gracefully handle missing/unreadable files
*/ */
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import SSHConfig from 'ssh-config'; import SSHConfig from 'ssh-config';
@ -22,7 +22,7 @@ export class SshConfigParser {
private configPath: string; private configPath: string;
constructor(configPath?: string) { constructor(configPath?: string) {
this.configPath = configPath ?? path.join(os.homedir(), '.ssh', 'config'); this.configPath = configPath ?? path.join(getHomeDir(), '.ssh', 'config');
} }
/** /**
@ -151,7 +151,7 @@ export class SshConfigParser {
} }
const pattern = match[1].trim(); const pattern = match[1].trim();
const expandedPattern = pattern.replace(/^~/, os.homedir()); const expandedPattern = pattern.replace(/^~/, getHomeDir());
try { try {
// Handle glob-like patterns by checking if the path contains wildcards // Handle glob-like patterns by checking if the path contains wildcards

View file

@ -9,6 +9,7 @@
* - Handle reconnection on errors * - Handle reconnection on errors
*/ */
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { execFile } from 'child_process'; import { execFile } from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
@ -267,7 +268,7 @@ export class SshConnectionManager extends EventEmitter {
break; break;
case 'privateKey': { case 'privateKey': {
const keyPath = config.privateKeyPath ?? path.join(os.homedir(), '.ssh', 'id_rsa'); const keyPath = config.privateKeyPath ?? path.join(getHomeDir(), '.ssh', 'id_rsa');
try { try {
const keyData = await fs.promises.readFile(keyPath, 'utf8'); const keyData = await fs.promises.readFile(keyPath, 'utf8');
connectConfig.privateKey = keyData; connectConfig.privateKey = keyData;
@ -347,15 +348,15 @@ export class SshConnectionManager extends EventEmitter {
const knownPaths = [ const knownPaths = [
// 1Password SSH agent // 1Password SSH agent
path.join( path.join(
os.homedir(), getHomeDir(),
'Library', 'Library',
'Group Containers', 'Group Containers',
'2BUA8C4S2C.com.1password', '2BUA8C4S2C.com.1password',
'agent.sock' 'agent.sock'
), ),
path.join(os.homedir(), '.1password', 'agent.sock'), path.join(getHomeDir(), '.1password', 'agent.sock'),
// Common user agent socket // Common user agent socket
path.join(os.homedir(), '.ssh', 'agent.sock'), path.join(getHomeDir(), '.ssh', 'agent.sock'),
]; ];
// Linux: add system paths // Linux: add system paths
@ -395,8 +396,8 @@ export class SshConnectionManager extends EventEmitter {
// The config parser already told us there's an identity file. // The config parser already told us there's an identity file.
// Try common identity file locations from config // Try common identity file locations from config
const configKeyPaths = [ const configKeyPaths = [
path.join(os.homedir(), '.ssh', 'id_ed25519'), path.join(getHomeDir(), '.ssh', 'id_ed25519'),
path.join(os.homedir(), '.ssh', 'id_rsa'), path.join(getHomeDir(), '.ssh', 'id_rsa'),
]; ];
for (const keyPath of configKeyPaths) { for (const keyPath of configKeyPaths) {
try { try {
@ -417,9 +418,9 @@ export class SshConnectionManager extends EventEmitter {
// Try default key files // Try default key files
const defaultKeys = [ const defaultKeys = [
path.join(os.homedir(), '.ssh', 'id_ed25519'), path.join(getHomeDir(), '.ssh', 'id_ed25519'),
path.join(os.homedir(), '.ssh', 'id_rsa'), path.join(getHomeDir(), '.ssh', 'id_rsa'),
path.join(os.homedir(), '.ssh', 'id_ecdsa'), path.join(getHomeDir(), '.ssh', 'id_ecdsa'),
]; ];
for (const keyPath of defaultKeys) { for (const keyPath of defaultKeys) {

View file

@ -8,10 +8,9 @@
* - Support tilde (~) expansion to home directory * - Support tilde (~) expansion to home directory
*/ */
import { encodePath, getClaudeBasePath } from '@main/utils/pathDecoder'; import { encodePath, getClaudeBasePath, getHomeDir } from '@main/utils/pathDecoder';
import { countTokens } from '@main/utils/tokenizer'; import { countTokens } from '@main/utils/tokenizer';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { app } from 'electron';
import * as path from 'path'; import * as path from 'path';
import { LocalFileSystemProvider } from '../infrastructure/LocalFileSystemProvider'; import { LocalFileSystemProvider } from '../infrastructure/LocalFileSystemProvider';
@ -48,7 +47,7 @@ export interface ClaudeMdReadResult {
*/ */
function expandTilde(filePath: string): string { function expandTilde(filePath: string): string {
if (filePath.startsWith('~')) { if (filePath.startsWith('~')) {
const homeDir = app.getPath('home'); const homeDir = getHomeDir();
return path.join(homeDir, filePath.slice(1)); return path.join(homeDir, filePath.slice(1));
} }
return filePath; return filePath;

View file

@ -1,5 +1,5 @@
import { getHomeDir } from '@main/utils/pathDecoder';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
async function isExecutable(filePath: string): Promise<boolean> { async function isExecutable(filePath: string): Promise<boolean> {
@ -61,7 +61,7 @@ function expandWindowsBinaryNames(binaryName: string): string[] {
} }
async function collectNvmCandidates(): Promise<string[]> { async function collectNvmCandidates(): Promise<string[]> {
const nvmNodeRoot = path.join(os.homedir(), '.nvm', 'versions', 'node'); const nvmNodeRoot = path.join(getHomeDir(), '.nvm', 'versions', 'node');
let versions: string[]; let versions: string[];
try { try {
versions = await fs.promises.readdir(nvmNodeRoot); versions = await fs.promises.readdir(nvmNodeRoot);
@ -172,9 +172,9 @@ export class ClaudeBinaryResolver {
const candidateDirs: string[] = [ const candidateDirs: string[] = [
// Native binary installation path (claude install) // Native binary installation path (claude install)
path.join(os.homedir(), '.local', 'bin'), path.join(getHomeDir(), '.local', 'bin'),
path.join(os.homedir(), '.npm-global', 'bin'), path.join(getHomeDir(), '.npm-global', 'bin'),
path.join(os.homedir(), '.npm', 'bin'), path.join(getHomeDir(), '.npm', 'bin'),
process.platform === 'win32' process.platform === 'win32'
? process.env.APPDATA ? process.env.APPDATA
? path.join(process.env.APPDATA, 'npm') ? path.join(process.env.APPDATA, 'npm')

View file

@ -1,3 +1,4 @@
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { diffLines } from 'diff'; import { diffLines } from 'diff';
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
@ -257,8 +258,13 @@ export class FileContentResolver {
if (!backupFileName) continue; if (!backupFileName) continue;
// Construct the file-history path // Construct the file-history path
const homeDir = process.env.HOME || process.env.USERPROFILE || ''; const historyPath = path.join(
const historyPath = path.join(homeDir, '.claude', 'file-history', sessionId, backupFileName); getHomeDir(),
'.claude',
'file-history',
sessionId,
backupFileName
);
try { try {
await access(historyPath); await access(historyPath);

View file

@ -5,6 +5,7 @@ import {
extractBaseDir, extractBaseDir,
getAutoDetectedClaudeBasePath, getAutoDetectedClaudeBasePath,
getClaudeBasePath, getClaudeBasePath,
getHomeDir,
getProjectsBasePath, getProjectsBasePath,
getTasksBasePath, getTasksBasePath,
getTeamsBasePath, getTeamsBasePath,
@ -2439,7 +2440,7 @@ export class TeamProvisioningService {
private async buildProvisioningEnv(): Promise<ProvisioningEnvResolution> { private async buildProvisioningEnv(): Promise<ProvisioningEnvResolution> {
const shellEnv = await resolveInteractiveShellEnv(); const shellEnv = await resolveInteractiveShellEnv();
const home = shellEnv.HOME?.trim() || process.env.HOME?.trim() || os.homedir(); const home = shellEnv.HOME?.trim() || process.env.HOME?.trim() || getHomeDir();
const user = shellEnv.USER?.trim() || process.env.USER?.trim() || os.userInfo().username; const user = shellEnv.USER?.trim() || process.env.USER?.trim() || os.userInfo().username;
const shell = shellEnv.SHELL?.trim() || process.env.SHELL?.trim() || '/bin/zsh'; const shell = shellEnv.SHELL?.trim() || process.env.SHELL?.trim() || '/bin/zsh';
const xdgConfigHome = const xdgConfigHome =

View file

@ -228,24 +228,24 @@ export const KanbanTaskCard = ({
#{task.id} #{task.id}
</Badge> </Badge>
{task.owner ? <MemberBadge name={task.owner} color={colorMap.get(task.owner)} /> : null} {task.owner ? <MemberBadge name={task.owner} color={colorMap.get(task.owner)} /> : null}
{task.needsClarification ? (
<span
className={`inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${
task.needsClarification === 'user'
? 'bg-red-500/15 text-red-400'
: 'bg-blue-500/15 text-blue-400'
}`}
>
<HelpCircle size={10} />
{task.needsClarification === 'user' ? 'Awaiting user' : 'Awaiting lead'}
</span>
) : null}
{!compact && ( {!compact && (
<h5 className="min-w-0 truncate text-sm font-medium text-[var(--color-text)]"> <h5 className="min-w-0 truncate text-sm font-medium text-[var(--color-text)]">
{task.subject} {task.subject}
</h5> </h5>
)} )}
</div> </div>
{task.needsClarification ? (
<span
className={`mt-1 inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${
task.needsClarification === 'user'
? 'bg-red-500/15 text-red-400'
: 'bg-blue-500/15 text-blue-400'
}`}
>
<HelpCircle size={10} />
{task.needsClarification === 'user' ? 'Awaiting user' : 'Awaiting lead'}
</span>
) : null}
{compact && ( {compact && (
<h5 className="mt-1 truncate text-sm font-medium text-[var(--color-text)]"> <h5 className="mt-1 truncate text-sm font-medium text-[var(--color-text)]">
{task.subject} {task.subject}

View file

@ -7,7 +7,7 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { setClaudeBasePathOverride } from '../../../src/main/utils/pathDecoder'; import { getHomeDir, setClaudeBasePathOverride } from '../../../src/main/utils/pathDecoder';
import { import {
isPathWithinAllowedDirectories, isPathWithinAllowedDirectories,
@ -17,7 +17,7 @@ import {
} from '../../../src/main/utils/pathValidation'; } from '../../../src/main/utils/pathValidation';
describe('pathValidation', () => { describe('pathValidation', () => {
const homeDir = os.homedir(); const homeDir = getHomeDir();
const claudeDir = path.join(homeDir, '.claude'); const claudeDir = path.join(homeDir, '.claude');
const testProjectPath = path.resolve('/home/user/my-project'); const testProjectPath = path.resolve('/home/user/my-project');