Add bash shell tab and file manager for running containers
All checks were successful
Build App / build-macos (push) Successful in 2m23s
Build App / build-windows (push) Successful in 3m52s
Build App / build-linux (push) Successful in 4m53s
Build App / sync-to-github (push) Successful in 12s

Adds two new features for running project containers:

1. Bash Shell Tab: A "Shell" button on running projects opens a plain
   bash -l session instead of Claude Code, useful for direct container
   inspection, package installation, and debugging. Tab labels show
   "(bash)" suffix to distinguish from Claude sessions.

2. File Manager: A "Files" button opens a modal file browser for
   navigating container directories, downloading files to the host,
   and uploading files from the host. Supports breadcrumb navigation
   and works with any path including those outside mounted projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 06:32:53 -08:00
parent 429acd2fb5
commit ab16ac11e7
12 changed files with 578 additions and 8 deletions

View File

@@ -0,0 +1,74 @@
import { useState, useCallback } from "react";
import { save, open as openDialog } from "@tauri-apps/plugin-dialog";
import type { FileEntry } from "../lib/types";
import * as commands from "../lib/tauri-commands";
export function useFileManager(projectId: string) {
const [currentPath, setCurrentPath] = useState("/workspace");
const [entries, setEntries] = useState<FileEntry[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const navigate = useCallback(
async (path: string) => {
setLoading(true);
setError(null);
try {
const result = await commands.listContainerFiles(projectId, path);
setEntries(result);
setCurrentPath(path);
} catch (e) {
setError(String(e));
} finally {
setLoading(false);
}
},
[projectId],
);
const goUp = useCallback(() => {
if (currentPath === "/") return;
const parent = currentPath.replace(/\/[^/]+$/, "") || "/";
navigate(parent);
}, [currentPath, navigate]);
const refresh = useCallback(() => {
navigate(currentPath);
}, [currentPath, navigate]);
const downloadFile = useCallback(
async (entry: FileEntry) => {
try {
const hostPath = await save({ defaultPath: entry.name });
if (!hostPath) return;
await commands.downloadContainerFile(projectId, entry.path, hostPath);
} catch (e) {
setError(String(e));
}
},
[projectId],
);
const uploadFile = useCallback(async () => {
try {
const selected = await openDialog({ multiple: false, directory: false });
if (!selected) return;
await commands.uploadFileToContainer(projectId, selected as string, currentPath);
await navigate(currentPath);
} catch (e) {
setError(String(e));
}
}, [projectId, currentPath, navigate]);
return {
currentPath,
entries,
loading,
error,
navigate,
goUp,
refresh,
downloadFile,
uploadFile,
};
}

View File

@@ -17,10 +17,10 @@ export function useTerminal() {
);
const open = useCallback(
async (projectId: string, projectName: string) => {
async (projectId: string, projectName: string, sessionType: "claude" | "bash" = "claude") => {
const sessionId = crypto.randomUUID();
await commands.openTerminalSession(projectId, sessionId);
addSession({ id: sessionId, projectId, projectName });
await commands.openTerminalSession(projectId, sessionId, sessionType);
addSession({ id: sessionId, projectId, projectName, sessionType });
return sessionId;
},
[addSession],