export type EntryType = "file" | "dir" | "symlink"; export interface FileEntry { name: string; path: string; // absolute path on its filesystem type: EntryType; size: number; // bytes mtime: number; // epoch milliseconds mode: number; // permission bits only (masked & 0o7777) owner?: string; // resolved name when the provider knows it group?: string; } /** Optional file-list columns (Name is always shown). */ export type ColumnKey = "size" | "mtime" | "kind" | "rights" | "owner" | "group"; /** Every RPC response uses this envelope; errors never cross the bridge as exceptions. */ export type Result = ({ ok: true } & T) | { ok: false; error: string }; export type AuthConfig = | { kind: "password"; password: string } | { kind: "key"; privateKeyPath: string; passphrase?: string } | { kind: "agent" }; export interface ConnectionConfig { host: string; port: number; username: string; auth: AuthConfig; initialPath?: string | null; } export interface SavedConnection extends ConnectionConfig { id: string; stackId: string | null; name: string; /** Other-panel local dir applied when that panel is local and untouched. */ localDir?: string | null; } /** A named group of connections that can be opened together as tabs. */ export interface Stack { id: string; name: string; /** Collapsed in the connection-manager list (UI state, persisted). */ collapsed?: boolean; } /** A concrete host alias resolved from ~/.ssh/config (read-only). */ export interface SshConfigHost { name: string; // the Host alias as written hostname: string; // resolved HostName (defaults to alias) port: number; username: string; identityFile?: string; // first existing IdentityFile, absolute path unsupported?: "proxy"; // ProxyJump/ProxyCommand present — direct connect can't work } export interface ConnectionsFile { version: 2; stacks: Stack[]; connections: SavedConnection[]; } /** UI languages the app ships translations for. */ export type SupportedLocale = "en" | "es" | "eu" | "fr" | "de" | "zh" | "ja"; export type AccentTone = | "graphite" | "blue" | "indigo" | "purple" | "rose" | "orange" | "green" | "teal"; export interface AppSettings { /** UI language; "system" resolves to the OS locale (fallback English). */ locale: "system" | SupportedLocale; theme: "system" | "light" | "dark"; /** Dark appearance: grey ("dark") or near-black AMOLED ("darker"). */ darkTone: "dark" | "darker"; /** Accent tone for chrome and selection tint; graphite = neutral zinc. */ accent: AccentTone; /** File-list icons: plain neutral glyphs, or per-category glyphs and colors. */ iconColors: "plain" | "multi"; /** Visible optional file-list columns (both panels; Name always shows). */ columns: ColumnKey[]; speedUnit: "MBps" | "Mbps"; /** Size of the panel interface: file list, path bar, header controls. */ textSize: "compact" | "default" | "large"; /** Run transfer jobs one at a time, or several in parallel. */ transferMode: "single" | "multi"; /** How many jobs run at once when transferMode is "multi". */ transferConcurrency: number; /** Remove completed transfers from the list automatically. */ autoClearCompleted: boolean; /** Edit files with the built-in editor or an external application. */ editor: "builtin" | "custom"; /** Full path to the external editor .app; empty = OS default application. */ editorApp: string; // Auto-refresh the active tab's panels (local watched, remote polled). Always // on for now; uncomment (+ the settings.service and tab-bar breadcrumbs, and // gate the poll/watch in panels.service) to expose a toggle later. // autoRefresh: boolean; } export const DEFAULT_SETTINGS: AppSettings = { locale: "system", theme: "system", darkTone: "dark", accent: "blue", iconColors: "multi", columns: ["size", "mtime"], speedUnit: "MBps", textSize: "default", transferMode: "single", transferConcurrency: 3, autoClearCompleted: true, editor: "builtin", editorApp: "", // autoRefresh: true, }; export function effectiveConcurrency(settings: AppSettings): number { if (settings.transferMode === "single") return 1; return Math.max(1, Math.min(8, Math.round(settings.transferConcurrency) || 1)); } export type TransferState = | "queued" | "scanning" | "running" | "completed" | "failed" | "cancelled"; export interface TransferSnapshot { id: string; name: string; // display name (basename of source) srcSessionId: string; srcPath: string; dstSessionId: string; dstDir: string; state: TransferState; totalFiles: number; totalBytes: number; doneFiles: number; doneBytes: number; currentFile: string; currentFileBytes: number; currentFileTotal: number; error?: string; }