diff --git a/ui-electrobun/src/bun/handlers/files-handlers.ts b/ui-electrobun/src/bun/handlers/files-handlers.ts index 6f5b55d..095956c 100644 --- a/ui-electrobun/src/bun/handlers/files-handlers.ts +++ b/ui-electrobun/src/bun/handlers/files-handlers.ts @@ -163,6 +163,16 @@ export function createFilesHandlers() { } }, + "files.ensureDir": async ({ path: dirPath }: { path: string }) => { + try { + const resolved = path.resolve(dirPath.replace(/^~/, process.env.HOME ?? "")); + fs.mkdirSync(resolved, { recursive: true }); + return { ok: true, path: resolved }; + } catch (err) { + return { ok: false, error: err instanceof Error ? err.message : String(err) }; + } + }, + "files.write": async ({ path: filePath, content }: { path: string; content: string }) => { const guard = guardPath(filePath); if (!guard.valid) { diff --git a/ui-electrobun/src/bun/handlers/misc-handlers.ts b/ui-electrobun/src/bun/handlers/misc-handlers.ts index 683dc62..073c6d1 100644 --- a/ui-electrobun/src/bun/handlers/misc-handlers.ts +++ b/ui-electrobun/src/bun/handlers/misc-handlers.ts @@ -56,16 +56,30 @@ export function createMiscHandlers(deps: MiscDeps) { return { // ── Files: picker + homeDir ───────────────────────────────────────── - "files.pickDirectory": async ({ startingFolder }: { startingFolder?: string }) => { + "files.pickDirectory": async ({ startingFolder, createIfMissing }: { startingFolder?: string; createIfMissing?: boolean }) => { try { const { execSync } = await import("child_process"); - const start = startingFolder?.replace(/^~/, process.env.HOME || "/home") || process.env.HOME || "/home"; + const fs = await import("fs"); + const home = process.env.HOME || "/home"; + let start = (startingFolder || "~/").replace(/^~/, home); + // Resolve to nearest existing parent + while (start && start !== "/" && !fs.existsSync(start)) { + start = start.replace(/\/[^/]+\/?$/, "") || home; + } + if (!start || !fs.existsSync(start)) start = home; const result = execSync( `zenity --file-selection --directory --title="Select Project Folder" --filename="${start}/"`, { encoding: "utf-8", timeout: 120_000 }, ).trim(); + if (result && createIfMissing) { + fs.mkdirSync(result, { recursive: true }); + } return { path: result || null }; - } catch { + } catch (err: unknown) { + // Exit code 1 = user cancelled — not an error + if (err && typeof err === "object" && "status" in err && (err as {status:number}).status === 1) { + return { path: null }; + } return { path: null }; } }, diff --git a/ui-electrobun/src/bun/model-fetcher.ts b/ui-electrobun/src/bun/model-fetcher.ts index 3259987..58e9aab 100644 --- a/ui-electrobun/src/bun/model-fetcher.ts +++ b/ui-electrobun/src/bun/model-fetcher.ts @@ -13,24 +13,35 @@ export interface ModelInfo { const TIMEOUT = 8000; +// Known Claude models as fallback when API key is not available +const KNOWN_CLAUDE_MODELS: ModelInfo[] = [ + { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'claude' }, + { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'claude' }, + { id: 'claude-opus-4-5-20250514', name: 'Claude Opus 4.5', provider: 'claude' }, + { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', provider: 'claude' }, + { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', provider: 'claude' }, + { id: 'claude-sonnet-3-7-20250219', name: 'Claude Sonnet 3.7', provider: 'claude' }, +]; + export async function fetchClaudeModels(): Promise { const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) return []; + if (!apiKey) return KNOWN_CLAUDE_MODELS; try { - const res = await fetch('https://api.anthropic.com/v1/models', { + const res = await fetch('https://api.anthropic.com/v1/models?limit=100', { headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', }, signal: AbortSignal.timeout(TIMEOUT), }); - if (!res.ok) return []; + if (!res.ok) return KNOWN_CLAUDE_MODELS; const data = await res.json() as { data?: Array<{ id: string; display_name?: string }> }; - return (data.data ?? []) + const live = (data.data ?? []) .map(m => ({ id: m.id, name: m.display_name ?? m.id, provider: 'claude' })) .sort((a, b) => a.id.localeCompare(b.id)); + return live.length > 0 ? live : KNOWN_CLAUDE_MODELS; } catch { - return []; + return KNOWN_CLAUDE_MODELS; } } diff --git a/ui-electrobun/src/mainview/PathBrowser.svelte b/ui-electrobun/src/mainview/PathBrowser.svelte index 96d5adc..29893a4 100644 --- a/ui-electrobun/src/mainview/PathBrowser.svelte +++ b/ui-electrobun/src/mainview/PathBrowser.svelte @@ -125,10 +125,14 @@
- +
{t('wizard.step1.browse' as any)} - +
+ {currentPath} + + +
@@ -172,10 +176,9 @@ {/if}
- + @@ -199,6 +202,25 @@ justify-content: space-between; padding: 0.375rem 0.5rem; border-bottom: 1px solid var(--ctp-surface0); + position: sticky; + top: 0; + z-index: 2; + background: var(--ctp-mantle); + } + .pb-header-actions { + display: flex; + align-items: center; + gap: 0.375rem; + } + .pb-current-compact { + font-size: 0.6875rem; + color: var(--ctp-subtext0); + max-width: 12rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + direction: rtl; + text-align: left; } .pb-title { font-size: 0.75rem; font-weight: 600; color: var(--ctp-text); } diff --git a/ui-electrobun/src/mainview/ProjectWizard.svelte b/ui-electrobun/src/mainview/ProjectWizard.svelte index f171254..53c8981 100644 --- a/ui-electrobun/src/mainview/ProjectWizard.svelte +++ b/ui-electrobun/src/mainview/ProjectWizard.svelte @@ -137,6 +137,10 @@ async function createProject() { const cwd = sourceType === 'remote' ? `ssh://${sanitize(remoteUser)}@${sanitize(remoteHost)}:${sanitize(remotePath)}` : (sanitizePath(localPath) || localPath.trim()); + // Ensure directory exists for local paths + if (sourceType !== 'remote' && cwd) { + try { await appRpc.request['files.ensureDir']({ path: cwd }); } catch { /* best effort */ } + } onCreated({ id: `p-${Date.now()}`, name: sanitize(projectName) || 'Untitled', cwd, provider, model: model || undefined, systemPrompt: systemPrompt || undefined, autoStart, groupId: selectedGroupId, useWorktrees: useWorktrees || undefined, shell: shellChoice, icon: projectIcon, color: projectColor, modelConfig: Object.keys(modelConfig).length > 0 ? modelConfig : undefined }); resetState(); } diff --git a/ui-electrobun/src/shared/pty-rpc-schema.ts b/ui-electrobun/src/shared/pty-rpc-schema.ts index 44375a9..f47fbbc 100644 --- a/ui-electrobun/src/shared/pty-rpc-schema.ts +++ b/ui-electrobun/src/shared/pty-rpc-schema.ts @@ -146,6 +146,10 @@ export type PtyRPCRequests = { error?: string; }; }; + "files.ensureDir": { + params: { path: string }; + response: { ok: boolean; path?: string; error?: string }; + }; /** Unguarded directory listing for PathBrowser (dirs only, no file content) */ "files.browse": { params: { path: string };