Skip to content

Upload phone files over SFTP and insert remote paths#149

Merged
gwitko merged 15 commits into
gwitko:devfrom
DrMulungu:feat/terminal-file-upload
Jul 24, 2026
Merged

Upload phone files over SFTP and insert remote paths#149
gwitko merged 15 commits into
gwitko:devfrom
DrMulungu:feat/terminal-file-upload

Conversation

@DrMulungu

Copy link
Copy Markdown

User problem

Attaching a screenshot, PDF, or video to an agent prompt from a phone means leaving the terminal, opening SFTP, navigating folders, uploading, then typing a remote path by hand — with any typo or unquoted space silently breaking the command.

New workflow

An Upload key in the terminal key row opens the native file picker (any file type, multiple selection). A confirmation sheet shows each filename, size, and the remote destination — nothing uploads until you confirm. Uploads stream with per-file progress and a working Cancel; on success you can Insert path (each path independently shell-quoted, multiple files joined by single spaces, never submitted — so you can write around them), copy the paths, or close. Local-shell sessions explain that uploads need an SSH machine.

Files land in ~/.conduit/uploads/<yyyy-mm-dd>/ by default; each machine's form gains an Upload directory override (absolute or ~/-relative) and a Delete my uploads after preference (Never / 7 / 30 / 90 days).

Screenshots

Rendered from the real widgets at Pixel-class resolution (1080×2400) via a widget-test screenshot harness.

Confirm before upload Uploaded, quoted paths ready Path inserted, not executed

Technical design

  • TerminalUploadController (ChangeNotifier) owns one batch: opens a dedicated SFTP session via the existing SftpRepository/SshClientFactory auth stack, resolves home server-side, creates the directory chain (tolerating existing components), lists the target once for collision detection, then streams each file through the existing chunked SftpSession.write — nothing is ever loaded wholly into memory. Cancel closes the batch session so an in-flight transfer aborts promptly; the session is always closed in finally.
  • Names: picked filenames are reduced to a single path segment (path separators and control characters stripped, everything else — spaces, Unicode — preserved for recognizability); collisions get a compact -HHmmss suffix before the extension, then a counter.
  • Quoting: posixShellQuote passes only [A-Za-z0-9._/-] (non-leading-dash) through unquoted; everything else is single-quoted with ''\'', keeping spaces, quotes, parentheses, $, globs, and newlines literal in sh/bash/zsh/fish.
  • Cleanup safety: every uploaded file is recorded in a per-host manifest (conduit.upload_manifest.v1, defensive parsing) — including files that landed before a mid-batch failure or cancel, so nothing app-owned escapes the ledger. Cleanup runs only after a fully successful batch, deletes only exact manifest-listed file paths past the cutoff, and never touches directories or anything Conduit didn't write.
  • SftpUploadFile and the picker pattern are reused from the SFTP browser rather than duplicated.

Failure and security behavior

Permission-denied, disk-full, interrupted connections, SFTP-unavailable, duplicate names, zero-byte, and large files all have explicit paths (and tests). A failed or cancelled batch never deletes anything and reports which files did land; credentials and file contents never appear in logs or error strings. New SavedHost fields default safely for old records and ride backup export/import via the existing host serialization.

Test evidence (all executed)

  • dart format — no diff; flutter analyze — 0 issues; flutter test287/287 green.
  • New tests (34): directory resolution (default/dated/absolute/~/-relative/blank), hostile-name sanitization (../../etc/passwd, backslash paths, control chars), collision suffixing, quoting (spaces, $( ), globs, embedded quotes, newlines, leading dash, empty), manifest round-trip/corrupt/partial payloads, controller behaviors (mkdir chain order, progress, custom dir, connect failure, write failure, partial-batch manifest recording, cancel-between-files, cancelled-batch-never-deletes, zero-byte, cleanup-cutoff-only, cleanup-disabled), SavedHost round-trip + corrupt-value defaults, and full widget flows (picker → confirm → progress → insert-quoted-path-without-Enter; error state).
  • Android debug build compiles clean (APK built and published on the fork's sandbox-relay prerelease for sideload testing). iOS: not built — no macOS host available.

Dependency / merge order

Based on master, independent of the other PRs proposed from this fork. It does not stack on the SFTP viewer/file-tabs branches; if those merge first, TerminalPage's new constructor params conflict trivially with the file-tabs PR's identical-shaped change. Adds wakelock_plus_platform_interface as a dev dependency for widget tests.

Manual phone checklist

  • Upload a screenshot, a PDF, and a large video — watch streaming progress; cancel one midway and confirm the batch reports cancelled without deleting anything.
  • Verify the file on the server under ~/.conduit/uploads/<date>/ and that the inserted path is quoted correctly and not executed.
  • Upload two files with the same name — second gets a time suffix.
  • Set a custom upload directory on one machine and confirm it's used; set cleanup to 7 days and confirm only old Conduit uploads disappear on a later upload.
  • Try the Upload key in a local-shell tab — clear explanation, no crash.
  • Multi-select several files — paths insert space-separated, each quoted.

gwitko and others added 15 commits June 29, 2026 20:17
fix: skip Termux setup in Debian release build
fix:  make release workflow pass under act
Merge pull request #113 from gwitko/fix/ios

fix: select hk
feat: android file system access in full version
Add an Upload key to the terminal keyboard bar: pick one or more phone
files (photos, videos, documents) with the native picker, stream them to
the active SSH host over SFTP, and insert the shell-quoted remote paths
into the terminal without executing anything.

Uploads land in an app-managed directory — ~/.conduit/uploads/<date>/ by
default, configurable per machine (absolute or ~/-relative) in the host
form. Directories are created as needed, picked filenames are sanitized
to a single path segment with control characters stripped, collisions
get a compact time suffix before the extension, and transfers stream
chunk-by-chunk through the existing SftpSession.write path with live
progress and cancellation (cancel closes the batch session).

Every uploaded file is recorded in a per-host manifest
(conduit.upload_manifest.v1, defensive parsing). The new per-machine
'Delete my uploads after' setting (never/7/30/90 days) removes only
manifest-listed files past the cutoff during later uploads — never
arbitrary directories or files Conduit didn't write.

Path insertion quotes each path for POSIX shells (spaces, quotes,
parentheses, dollar signs, newlines) and joins multiple files with
spaces. Insertion never submits, so the user can compose text around
the paths. The key ships in the default layout and rides the unseen
built-ins migration; local-shell sessions explain that uploads need an
SSH machine. New SavedHost fields ride backup export/import via the
existing host serialization.
Review follow-ups: files that reached the server are now recorded in
the per-host manifest even when a later file fails or the batch is
cancelled mid-write, so automatic cleanup can always manage them.
Cleanup of expired earlier uploads now runs only after a fully
successful batch — failed or cancelled batches never delete anything.
Cancelling during connect no longer creates remote directories, and a
manifest write failure no longer misreports a successful upload as
failed.
@gwitko
gwitko changed the base branch from master to dev July 24, 2026 08:41
@gwitko
gwitko merged commit fe3de64 into gwitko:dev Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants