- Built-in editor (default): large modal with monospace textarea, Cmd+S save, dirty guard; reads/writes through the provider streams so it works on local and remote files identically (2MB cap with a friendly error) - External editor: remote files download to appDataDir/edits/<id>, open via macOS `open -a`, the temp DIR is watched (survives atomic rename-saves) and every save enqueues a normal upload job (progress/errors in the transfer list); watchers + temp dirs are cleaned when the session disconnects - Editor setting: Built-in | Custom with a NATIVE app picker (electrobun openFileDialog, /Applications, .app filter) and reset-to-default - Leftover temp files from a crash prompt for cleanup at boot - F4, toolbar button (mynaFileText), and File > Edit File (Cmd+E) menu entry - README: feature notes + Portability notes (open/start/xdg-open seam documented in code and README per user request) Verified headless against the box: sftp read/write roundtrip incl. UTF-8, size cap, watch->save->auto-upload->remote-updated, session cleanup, orphan listing and clearing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
174 lines
7.4 KiB
Markdown
174 lines
7.4 KiB
Markdown
# BunSCP
|
||
|
||
A cross-platform, dual-panel SFTP/local file manager — a small WinSCP-style
|
||
commander built on **Bun**, **Electrobun**, and **Angular**.
|
||
|
||
Each of the two panels independently shows either the local filesystem or a
|
||
remote server over SSH/SFTP, so you can move files **local↔remote**,
|
||
**remote↔remote**, or **local↔local** through one symmetric engine.
|
||
|
||
> **Name & icon are placeholders.** See [Rebranding](#rebranding) — it's a
|
||
> one-file change.
|
||
|
||
## Features
|
||
|
||
- Dual-panel commander UI; either panel can be Local or any saved connection.
|
||
- **Session tabs**: each tab is an independent left/right workspace; remote
|
||
sessions stay connected and transfers keep running in background tabs.
|
||
- Per-panel action toolbar with a direction-explicit copy button, plus a
|
||
VSCode-style status bar showing live transfer progress (click to expand the
|
||
transfer queue with per-file speed and ETA).
|
||
- **Overwrite confirmation** on name collisions, with a source-vs-destination
|
||
comparison (size, modified date, which is newer) and batch replace-all /
|
||
skip-all.
|
||
- **Settings** (gear, top-right): light / dark / system theme and speed units
|
||
(MB/s or Mbps), persisted across restarts.
|
||
- On macOS the tab bar is fused with the window titlebar; other platforms keep
|
||
a standard titlebar.
|
||
- **Edit files** (F4 / toolbar / File menu): a built-in editor works on local
|
||
and remote files directly; or pick an external app (Options → Editor →
|
||
Custom → Choose…). External edits of remote files download to a temp folder,
|
||
are watched, and every save auto-uploads (visible in the transfer list) until
|
||
the session disconnects. Leftover temp files from a crash are offered for
|
||
cleanup at the next start.
|
||
- Browse, sort (name/size/modified, folders first), keyboard navigation.
|
||
- Create folder / file, rename, delete (recursive), change permissions (chmod).
|
||
- Upload, download, and remote→remote copy — files **and** whole folders —
|
||
with a transfer queue, per-job progress, and cancel.
|
||
- Connection manager: saved connections organised into folders, password or
|
||
private-key auth (with passphrase).
|
||
|
||
## Stack
|
||
|
||
| Layer | Choice |
|
||
| ---------- | ------------------------------------------------------------- |
|
||
| Runtime | Bun (main process, all SSH/SFTP + file IO) |
|
||
| Desktop | Electrobun 1.18 (native webview, typed RPC bridge) |
|
||
| Frontend | Angular 22 (standalone, signals, zoneless) in `ui/` |
|
||
| Styling | Tailwind CSS v4, shadcn-style tokens (no component library) |
|
||
| Icons | @ng-icons/mynaui |
|
||
| SSH/SFTP | `ssh2` (pure-JS under Bun) |
|
||
|
||
The repo is a Bun workspace: the Electrobun app at the root, the Angular app in
|
||
`ui/`. Shared types and the RPC contract live in `src/shared/` and are imported
|
||
by both sides.
|
||
|
||
```
|
||
src/shared/ types.ts, rpc.ts (wire contract), branding.ts
|
||
src/bun/ Electrobun main process
|
||
fs/ FileSystemProvider + LocalProvider + SftpProvider + registry
|
||
transfers/ engine.ts (queue/stream copy) + walk.ts (recursive helpers)
|
||
store/ connections.json persistence
|
||
ui/src/app/ Angular components + signal services
|
||
```
|
||
|
||
## Prerequisites
|
||
|
||
- [Bun](https://bun.sh) ≥ 1.3 (`bun --version`).
|
||
- macOS: **Xcode Command Line Tools** (for the app build).
|
||
- No Node install required — a preload shim (`scripts/node-version-shim.ts`)
|
||
satisfies the Angular CLI's Node version check while running under Bun.
|
||
|
||
## Setup
|
||
|
||
```bash
|
||
bun install # installs root + ui workspace deps
|
||
```
|
||
|
||
## Develop
|
||
|
||
```bash
|
||
bun run dev
|
||
```
|
||
|
||
This starts the Angular dev server (hot reload for UI code) and launches the
|
||
Electrobun app pointed at it. Edits under `ui/` reload instantly; edits to the
|
||
Bun main process (`src/bun/**`, RPC schema) trigger an app relaunch.
|
||
|
||
## Build a packaged app
|
||
|
||
```bash
|
||
bun run build # ng build (ui/dist) + electrobun build -> build/*/BunSCP*.app
|
||
```
|
||
|
||
The Angular bundle is copied into the app under `views://app`. The macOS icon is
|
||
compiled from `assets/icon.iconset` into `AppIcon.icns` automatically.
|
||
|
||
For a distributable (optimized, with auto-update channel) build, use
|
||
`bunx electrobun build --env=stable` (see the
|
||
[Electrobun docs](https://framework.blackboard.sh/electrobun/) for signing,
|
||
notarization, and release hosting).
|
||
|
||
## Rebranding
|
||
|
||
Everything user-facing reads from **[`src/shared/branding.ts`](src/shared/branding.ts)**:
|
||
|
||
```ts
|
||
export const BRANDING = {
|
||
appName: "BunSCP", // window title, bundle name, app-data folder
|
||
identifier: "es.teknes.bunscp",
|
||
version: "0.1.0",
|
||
icon: "assets/icon.iconset",
|
||
} as const;
|
||
```
|
||
|
||
To change the icon: replace `assets/icon.png` (1024×1024) and run
|
||
`./scripts/gen-iconset.sh` (regenerates the `.iconset` with `sips`), or point
|
||
`icon` at your own `.iconset`. `scripts/make-icon.py` regenerates the current
|
||
placeholder.
|
||
|
||
> Renaming `appName` changes the app-data directory, so existing saved
|
||
> connections won't be found under the new name — copy `connections.json` across
|
||
> if you need them.
|
||
|
||
## Where connections are stored
|
||
|
||
Saved connections live in a plain JSON file in the OS app-data directory
|
||
(macOS: `~/Library/Application Support/BunSCP/connections.json`), written with
|
||
`0600` permissions.
|
||
|
||
> ⚠️ **Passwords are stored in plaintext** in this file (a deliberate v1
|
||
> simplicity choice). Anyone who can read the file can read the passwords. If
|
||
> that isn't acceptable for your use, use key-based auth or don't save the
|
||
> password (connect ad-hoc). OS-keychain storage is a natural future addition.
|
||
|
||
## Keyboard shortcuts
|
||
|
||
`Tab` switch panel · `↑`/`↓`/`PgUp`/`PgDn`/`Home`/`End` move cursor ·
|
||
`Space` select · `Enter` open folder · `Backspace` parent ·
|
||
`Ctrl/Cmd+A` select all · `F5` copy to other panel · `F2` rename ·
|
||
`F7` new folder · `F8`/`Delete` delete · `Cmd/Ctrl+T` new tab ·
|
||
`Cmd/Ctrl+W` close tab · `Ctrl+Tab` next tab.
|
||
|
||
(On Macs, function keys may need `fn` — every action also has a toolbar button.)
|
||
|
||
## Portability notes
|
||
|
||
The codebase is platform-neutral except for two seams, both macOS-only today:
|
||
|
||
- **External editor launch** (`launchEditor` in [src/bun/edits.ts](src/bun/edits.ts)):
|
||
uses macOS `open [-a app] file`. A Windows port should use
|
||
`cmd /c start "" [app] file`; Linux `xdg-open file` (or invoke the configured
|
||
editor binary directly). The `.app` picker in the `pickEditorApp` handler
|
||
needs the equivalent executable picker per platform.
|
||
- **Application menu** ([src/bun/menu.ts](src/bun/menu.ts)): electrobun menus
|
||
are macOS-only; the in-app UI covers every menu action.
|
||
|
||
## Known issues
|
||
|
||
- **Rare native crash under load.** A long-running session (multiple remote
|
||
connections, extended uptime) can hit a Bun panic — a segfault inside Bun
|
||
1.3.13's FFI thread-safe callbacks executing Electrobun's native→worker
|
||
bridge (`FFI_Callback_threadsafe_call` → `web_worker.spin` in the decoded
|
||
crash report). This is an upstream Bun × Electrobun bug, not app code; a
|
||
restart recovers cleanly and in-flight work isn't corrupted. Expected to
|
||
resolve when Electrobun bumps its pinned Bun (currently 1.3.13; 1.3.14+
|
||
available). Bun's own crash reporter link is printed to the console on panic.
|
||
|
||
## Not in v1
|
||
|
||
Drag-and-drop, file preview/edit, transfer resume, symlink following
|
||
(symlinks are listed but skipped by transfers), OS keychain, tab persistence
|
||
across restarts, Windows/Linux packaging (the code is portable; only macOS
|
||
packaging is wired up).
|