omnyshell 1.8.2
omnyshell: ^1.8.2 copied to clipboard
Secure, Hub-centric remote shell platform written in pure Dart. Clients connect to a Hub by node identity (not host:port); the Hub authenticates, authorizes and brokers encrypted sessions to Nodes ove [...]
1.8.2 #
Fixed #
- Detach no longer leaves stray escape sequences on the local terminal. A
local
:detachreturned early without restoring the terminal, so DEC private modes a full-screen remote program (vim, claude…) had enabled — mouse tracking, bracketed paste, alt-screen, hidden cursor, SGR — stayed on and the terminal kept emitting special characters (e.g.ESC[<…Mmouse reports on every mouse move). Both detach paths now share one reset sequence and clean up the terminal.
1.8.1 #
Changed #
- Upgraded
omnydriveto^1.1.2. Picks up the fix that prevents a mount sync from silently discarding local-only changes. OmnyShell already guards this inDriveManager._autoDirection(read-only mounts only push, read-write mounts only pull when the local copy is unchanged, and a two-sided change surfaces a conflict); added regression tests covering remote→local pulls of new node files, read-only mounts never pulling, and divergent syncs refusing to clobber local work.
1.8.0 #
Added #
- Peek at a session's current screen without attaching. The new
omnyshell sessions peek <node> <session-id>prints a session's current screen — the same bytes a resume would paint, captured by the node's alt-screen-aware replay buffer — without connecting to it or delivering any input. Works for both running (attached) and detached sessions you own. sessions listnow shows the current command and path. Each row gains aCOMMAND(the foreground command, or-at the prompt) andPATH(the session's working directory) column, queried best-effort by the node from the OS (Linux/macOS).- New sessions start in your home directory. A freshly opened session now
starts in the node user's home directory (like
cd ~) instead of wherever the node process was launched. An explicit per-request working directory still takes precedence.
Changed #
- Profile
PATHis deduplicated on export. When syncing the node profile, the capturedPATHnow has empty and duplicate entries removed (first occurrence wins, order preserved) before it is written toprofile.yaml. - Clearer
omnyshell node profile syncreporting. When nothing changes it reportsNode PATH already up to date.; when it writes a change it now also reminds you to restart the node for the newPATHto take effect. (The restart hint is omitted duringnode start, which re-loads the profile immediately.)
1.7.0 #
Added #
- Node environment profile (
~/.omnyshell/profile.yaml). Sessions run the node's shell non-interactively, so no rc file is sourced and$PATHstarts bare. The node now applies anenv:map from~/.omnyshell/profile.yaml(values support${VAR}expansion) as thebaseEnvironmentof every shell and exec session. On an interactivenode startthe node derivesPATHfrom your login shell rc (~/.zshrc,~/.bash_profile, …) and prompts before writing it when it differs; non-interactive starts leave the profile untouched and print a hint. New flags--profile <path>and--no-profile-sync, plus anomnyshell node profile sync [--yes]subcommand to refresh on demand. - Remember
--insecure-skip-verifyat login. Logging in with--insecure-skip-verifynow asks whether to persist the setting for that Hub; when stored, later commands reusing the saved session skip TLS verification without re-passing the flag. A non-interactive login defaults to not storing it, and re-runningloginwithout the flag (orlogout) clears it.
1.6.1 #
- Dependency updates in
pubspec.yaml:cryptography: updated from ^2.7.0 to ^2.9.0omnydrive: updated from ^1.1.0 to ^1.1.1lints: updated from ^6.0.0 to ^6.1.0test: updated from ^1.25.6 to ^1.31.1
1.6.0 #
Added #
- Manage OmnyDrive mounts from inside a session with
:drive. The new local command is the in-session counterpart of the top-levelomnyshell driveCLI: because the session is already attached to one node, the node is implicit, so paths take no<node>:prefix and every operation is scoped to the connected node. Subcommands mirror the CLI —:drive ls,:drive mount <local-dir> <remote-path>(or--git <url> <remote-path>, with--rw,--no-initial-sync,--name,--branch,--depth),:drive status,:drive sync [--push|--pull],:drive resolve [--accept-local|--accept-origin| --reclone],:drive remount, and:drive unmount [--sync-first] [--no-keep-remote]. A mount-id belonging to a different node is refused, and mounts share the same on-disk registry as the CLI. - Background
:drive watch.:drive watch <mount-id> [--interval S] [--debounce MS]auto-syncs a mount in the background while the shell stays usable, logging each sync above the prompt;:drive unwatch [<mount-id>]stops one or all watchers (teardown also runs automatically when the session ends or the mount is unmounted). Background output repaints around the input line via a newLocalCommandContext.printAbovehook. - Live drive sync progress. Every drive operation now reports progress as it
runs instead of only a final count:
sync,mount,resolve,remountandwatch, for both theomnyshell driveCLI and the in-session:drivecommand. The top-level CLI renders an in-place bar ([##########----] 71% 5/7 files src/main.dart); in-session prints a throttledsyncing N/M: pathline above the prompt. Per-file granularity for directory mounts comes from omnydrive ≥ 1.1.0's per-fileProgressEvents; git push/clone show a coarsepushing…/cloning…phase. Threaded through a newDriveManageronProgresscallback and aSyncProgressBarrenderer.
1.5.1 #
Fixed #
-
No more stray characters on the terminal after a session detaches. Two separate leaks were writing to the local terminal once a session was already gone. (1) The
connectclient never cancelled its remote stdout/stderr listeners on detach, so bytes still buffered in the channel were flushed afterwards — at an idle prompt the line editor repainted around them, smearing erase/prompt escape sequences onto the detached terminal. The listeners are now guarded against the detached state and cancelled on teardown. (2) When a session was detached from another window mid full-screen program, the terminal reset undid the alternate screen, cursor and color attributes but not mouse reporting, so every later mouse move spewed SGR mouse reports (ESC[<…M) onto the terminal. The detach reset now also disables every mouse-tracking mode (1000/1002/1003 and the 1005/1006/1015 encodings) and bracketed paste (2004). -
Interactive sessions no longer freeze on heavy output. The
connectclient consumed remote stdout/stderr without ever replenishing the channel's send window, so after a cumulative 256 KiB the node's flow-control credit drained and all further output stalled — the session appeared frozen. This surfaced most often with full-screen TUIs that repaint the whole screen on every scroll (e.g.claude's plan view,vim,less,htop), which exhaust the window within a handful of redraws. The client now grants window credit for every chunk it consumes. The node grants stdin credit symmetrically, so a large paste into a full-screen program can't stall input either.
1.5.0 #
Added #
-
Detachable sessions. Leave a node without killing the remote shell and reconnect later. From the interactive prompt,
:detach [timeout]parks the session — the PTY, shell and child processes keep running on the node — and prints a short id to resume with (:detach,:detach 30m,:detach 2h,:detach 1d; unitss/m/h/d). Manage detached sessions from the CLI:omnyshell sessions list <node>,omnyshell sessions resume <node> <id>(a full id, short handle or unambiguous prefix), andomnyshell sessions kill <node> <id>. Sessions are owned by one authenticated user on one node; the node enforces ownership and never reveals another user's sessions. A dropped client connection auto-detaches by default (preserving the shell) rather than terminating it. Output produced while detached is retained in an in-memory capture and replayed on resume. Detached-session state lives only in node memory — nothing is written to disk and it is lost on node restart by design. The Hub only authenticates, routes and correlates replies; it never persists detached-session metadata. New client APIs:RemoteSession.detach(),ClientRuntime.resumeSession(),listDetachedSessions(),killDetachedSession(); newNodeConfigautoDetachOnDisconnect,autoDetachTimeout,cleanupInterval. -
Detach a running session from another window. Because
:detachcan't be typed while a full-screen program (vim, top, less, a REPL) owns the terminal,omnyshell sessions detach <node> [session-id] [timeout]detaches a running session from a separate terminal — the attached window drops out of the full-screen app with its terminal restored and prints a resume hint, and the remote shell keeps running. With nosession-idit targets your sole active session on that node (errors if several).omnyshell sessions list <node>now shows active sessions too (STATUSattached/detached) so you can find the id. New client APIs:ClientRuntime.detachActiveSession(),listSessions(), andRemoteSession.wasDetached/detachOutcome. -
sessions killterminates running sessions too.omnyshell sessions kill <node> <id>now resolves both active (attached) and detached sessions, so you can kill a running session from another window; the attached client is disconnected. NewClientRuntime.killSession()(the oldkillDetachedSessionremains as a deprecated alias). -
Resume restores full-screen programs. The node now keeps a continuous, alt-screen-aware capture of recent output for every session (not just while detached), so resuming into
nano/vim/htop/lessrepaints the program's current screen — the frame it had drawn before detaching — instead of a blank terminal. Resume into a full-screen program attaches in passthrough without injecting a prompt marker (which previously typed into the program); the program's existing completion marker restores the prompt when it exits. Restoration is at the detached geometry.SessionOpenedgains analtScreenflag (exposed asRemoteSession.resumedInAltScreen). The prompt-completion marker token is derived from the stable session id (reported unchanged across connect and resume), so a resumed client recognizes the marker the running program leaves behind and reliably repaints the prompt after the program exits — with the correct working directory.
1.4.0 #
Added #
-
Detachable sessions. Leave a node without killing the remote shell and reconnect later. From the interactive prompt,
:detach [timeout]parks the session — the PTY, shell and child processes keep running on the node — and prints a short id to resume with (:detach,:detach 30m,:detach 2h,:detach 1d; unitss/m/h/d). Manage detached sessions from the CLI:omnyshell sessions list <node>,omnyshell sessions resume <node> <id>(a full id, short handle or unambiguous prefix), andomnyshell sessions kill <node> <id>. Sessions are owned by one authenticated user on one node; the node enforces ownership and never reveals another user's sessions. A dropped client connection auto-detaches by default (preserving the shell) rather than terminating it. Output produced while detached is retained in an in-memory capture and replayed on resume. Detached-session state lives only in node memory — nothing is written to disk and it is lost on node restart by design. The Hub only authenticates, routes and correlates replies; it never persists detached-session metadata. New client APIs:RemoteSession.detach(),ClientRuntime.resumeSession(),listDetachedSessions(),killDetachedSession(); newNodeConfigautoDetachOnDisconnect,autoDetachTimeout,cleanupInterval. -
Detach a running session from another window. Because
:detachcan't be typed while a full-screen program (vim, top, less, a REPL) owns the terminal,omnyshell sessions detach <node> [session-id] [timeout]detaches a running session from a separate terminal — the attached window drops out of the full-screen app with its terminal restored and prints a resume hint, and the remote shell keeps running. With nosession-idit targets your sole active session on that node (errors if several).omnyshell sessions list <node>now shows active sessions too (STATUSattached/detached) so you can find the id. New client APIs:ClientRuntime.detachActiveSession(),listSessions(), andRemoteSession.wasDetached/detachOutcome. -
sessions killterminates running sessions too.omnyshell sessions kill <node> <id>now resolves both active (attached) and detached sessions, so you can kill a running session from another window; the attached client is disconnected. NewClientRuntime.killSession()(the oldkillDetachedSessionremains as a deprecated alias). -
Resume restores full-screen programs. The node now keeps a continuous, alt-screen-aware capture of recent output for every session (not just while detached), so resuming into
nano/vim/htop/lessrepaints the program's current screen — the frame it had drawn before detaching — instead of a blank terminal. Resume into a full-screen program attaches in passthrough without injecting a prompt marker (which previously typed into the program); the program's existing completion marker restores the prompt when it exits. Restoration is at the detached geometry.SessionOpenedgains analtScreenflag (exposed asRemoteSession.resumedInAltScreen). The prompt-completion marker token is derived from the stable session id (reported unchanged across connect and resume), so a resumed client recognizes the marker the running program leaves behind and reliably repaints the prompt after the program exits — with the correct working directory.
Changed #
-
Interactive
connectnow hands the terminal to the remote while a command runs, instead of guessing from the command text. The managed prompt is drawn only when the shell is idle; the moment a command is dispatched the client enters raw passthrough and lets the remote program own the terminal until theCwdMarkercompletion signal returns. This fixes full-screen programs (vim, nano, less, top) and interactive line-readers (read,y/Nconfirmations, REPLs) corrupting — or being corrupted by — the local prompt, and replaces the fragile alternate-screen detection plus hardcoded foreground-program list. -
Cooked-mode input now echoes correctly. Because the remote shell runs with
stty -echo, the dispatched command is wrapped to re-enable echo just for the program's runtime input (stty echo ; eval '<cmd>' ; <marker> ; stty -echo), soread/cat/y-Nprompts show what you type while password prompts stay hidden (those programs disable echo themselves). No node-side change is needed.
Fixed #
- Output arriving at the idle prompt no longer tangles with the input line.
A backgrounded job (
cmd &) printing while you type now erases and repaints the prompt around its output.
1.3.3 #
- dart_service_manager: ^1.2.2
1.3.2 #
Added #
-
--verbose(-v) flag on everyomnyshell servicesubcommand. Drops the service manager's console logger to debug level so the underlying install/lifecycle steps (and the 1.2.0 user-systemd/privilege diagnostics) are printed; without it, info and warnings are shown as before. -
dart_service_manager: ^1.2.1
1.3.1 #
Changed #
- Bumped
dart_service_managerto^1.2.0.omnyshell service installnow benefits from the package's new install-time safeguards with no flag changes: on Linux, user-scoped installs auto-configure a persistent per-user systemd environment (enables lingering, resolves the user D-Bus bus andXDG_RUNTIME_DIR) so a--userHub/Node survives logout/reboot and no longer hitsFailed to connect to bus; andinstallnow warns when the requested scope mismatches the current privilege level (e.g. running under sudo with the default user scope, or--systemwithout elevation).
1.3.0 #
Added #
omnyshell service— install the Hub or Node as a native OS service. A new command group registers the runningomnyshellexecutable with the platform service manager (systemd on Linux, launchd on macOS, the Service Control Manager on Windows) viadart_service_manager, so a Hub or Node starts at boot and is restarted on failure. The flags passed toservice install <hub|node> …are the exacthub start/node startflags and are captured into the generated unit/plist; path flags (--cert,--key,--ca,--authorized-keys) are absolutized. Subcommands:install,uninstall,start,stop,restart,status, andreconfigure. Installs to the current user by default;--systeminstalls machine-wide and runs withOMNYSHELL_HOME=/var/lib/omnyshell(override with--data-dir).--dry-runprints the rendered definition without touching the system, and--forcereplaces an existing service.
1.2.2 #
Fixed #
- Nodes now report their real OmnyShell version.
NodeConfig.agentVersiondefaults toomnyShellVersioninstead of a hardcoded0.1.0, so a node's advertisedAgent:version (shown in:infoand node details) reflects the actual build.omnyShellVersionis now the single source of truth and is kept in sync withpubspec.yaml, guarded by a newversion-tagged test.
1.2.1 #
Added #
--insecure-skip-verifyflag to bypass TLS verification. Available on all connection commands (login,node start,connect,exec,drive, …), it disables both CA-trust and hostname verification so clients/nodes can reach a Hub presenting a self-signed certificate or a cert whose CN/SAN does not match the connection hostname (common with self-hosted hubs). Opt-in and insecure: it prints a[security] WARNINGto stderr whenever active and is intended only for trusted self-signed/dev hubs. The setting is per-invocation and is not persisted into saved sessions.
1.2.0 #
Added #
omnyshell drivemounts directories and git repos onto remote node paths (OmnyDrive integration). A new command group binds a local directory — or a git repository — to a path on a connected node and synchronizes the two over the same authenticatedwsssession (no extra ports or credentials). Built on OmnyDrive: content-addressed manifests and explicit conflict detection (never a silent merge).drive mount <local-dir> <node>:<remote-path>(and--git <url>with--branch/--depth) creates a mount;--rwmakes the node mirror writable,--nameoverrides the mount name,--no-initial-syncskips the first push.drive ls,drive status <id>inspect mounts (read local state, no Hub);drive sync <id>(--push/--pull/auto) synchronizes once;drive watch <id>(--interval/--debounce) auto-syncs live on filesystem changes.drive resolve <id>(--accept-local/--accept-origin/--reclone) clears a conflict;drive unmount <id>(--sync-first,--no-keep-remote) tears one down;drive remount <id>re-establishes it after a node/CLI restart.- Direction is automatic: read-only mounts push, read-write mounts push/pull/no-op based on which side changed; a two-sided change surfaces a conflict instead of clobbering work.
- Transport: a new
SessionMode.drivecarries a framed request/response RPC (NodeDriveServiceon the node serves a sandboxed content source and git ops; the client runs OmnyDrive's directory synchronizer against aChannelContentSource). Mount state persists in~/.omnyshell/mounts.json. - Nodes advertise a
drivecapability and accept mounts by default; theNodeConfig.driveEnabled/driveRootsoptions gate and path-restrict them. - New client APIs:
DriveManager,MountStore/MountRecord,ChannelContentSource,DriveRpcClient.
1.1.0 #
Added #
omnyshell cert gengenerates the TLS files a Hub needs. A newcert gencommand builds a local CA and a Hub server certificate signed by it, writingca.crt/ca.key/server.crt/server.key— the set the Hub uses (hub start --cert/--key) and that nodes and clients trust (--ca). Options:--out(output directory, defaultcerts),--host(repeatable, adds SAN entries beyond the defaultlocalhost/127.0.0.1),--cn,--days,--ca-days, and--force(overwrite existing certs). It is the built-in equivalent of thetool/gen-dev-certs.shscript; both shell out toopenssl, which must be onPATH. The generation logic is also exposed in the API asCertGenerator.
1.0.0 #
Added #
-
:downloadcan fetch a remote path as a compressed archive. Pass--gzor--zipfor a file, or--tar.gzor--zipfor a directory, e.g.:download /var/log --tar.gzor:download /etc/hosts --gz. The archive is built on the node (viagzip/tar/zipin a temp file, removed afterward) so only the compressed bytes are transferred. The local file is named<base>.<ext>by default, or written into a destination directory / explicit path if given. Invalid combinations (e.g.--gzon a directory) and missing remote tools are reported clearly. Plain:downloadis unchanged. -
:pingaccepts a count, e.g.:ping 3sends three pings in sequence and prints each round-trip plus amin · avg · maxsummary.:pingwith no argument behaves as before (a single ping). -
Prefix-aware history search in
connect. When you have typed something at the prompt, Up/Down now walk only the history entries that start with that prefix (the text before the cursor), newest-first — e.g. typegitthen press Up to cycle just your previousgitcommands. With an empty line it behaves as before, walking all entries; editing the line recomputes the prefix. -
TAB completion in
connect, like a normalsshshell. Pressing Tab now completes the word under the cursor: command names (first word) are resolved by scanning the node's$PATH, and arguments are completed as file/directory paths (directories get a trailing/). A unique match is inserted (with a trailing space for non-directories); several matches complete the longest common prefix, and pressing Tab again lists them. Candidates are produced by a one-off remoteexecrun in the session's current directory (portable POSIXsh, nobash-onlycompgen), so relative paths resolve correctly.
Fixed #
-
Interactive confirmation prompts now work (e.g.
:download's "Proceed? [y/N]"). The line editor previously paused all input while a local command was running, so the answer line could never be read — the prompt appeared to ignorey+ Enter. Prompts are now read through the editor directly while a command is in flight; Ctrl-C cancels a prompt (counts as "no"), and non-interactive sessions auto-proceed. Stray keystrokes during a running transfer are still ignored. -
The cwd/git prompt marker no longer interferes with foreground programs. When a command launches an interactive program (editor/pager/monitor such as
nano/vim/less/top, or a bare REPL likepython/node), theconnectclient now switches to raw passthrough immediately — detected from the typed command, not just from the alternate-screen output sequence. This fixes the marker being injected into the program's stdin (e.g. on each Enter insidenano) on backends where the alternate screen is never reported to the client, such as the macOSscript(1)PTY whose stdout is not a tty. The Ctrl-C prompt resync is likewise suppressed while a foreground program owns the terminal. -
Enter is now recognised inside remote full-screen apps and their prompts (e.g. confirming nano's "File Name to Write" on
Ctrl-X). Raw passthrough now forwards the Enter key as a carriage return (\r), matchingssh: Dart's raw mode leavesICRNLenabled, so the local terminal delivers Enter as\n, which some apps accept in their editor body but ignore at status-bar prompts.
Changed #
- No
gitqueries after read-only commands. Commands that cannot change the working directory or git state (e.g.ls,cat,pwd,git status/log/diff) now enqueue a lightweight completion ping instead of the full cwd/git marker: the prompt still repaints in the right place (after the command's output) and keeps its current cwd/branch/status, but the remote no longer runsgit rev-parse/git status. Blank input lines repaint the prompt locally with no remote round-trip at all.
Removed #
- Disabled the
portable_pty/native PTY backend: the PTY exports are removed.
Added #
-
Default PTY backend now uses the system
script(1)utility — no FFI, no native library. Nodes serve interactiveconnectshells on a real pseudo-terminal allocated by the OSscriptcommand, launched as an ordinary child process. The child gets a genuine tty (isatty()true; full-screen apps likevim/htopwork) at the client's requested geometry. Selectable vianode start --pty-backend script|native|none(defaultscript). This avoids the nativeportable_ptycrash entirely. Trade-off: this backend cannot propagate live resize (SIGWINCH) to the remote terminal — only the initial geometry is honoured; use--pty-backend nativeif you need live resize. The client still advertises its localTERM/columns/rows when opening the session; when no PTY is available (Windows, orscriptmissing) the node falls back to the pipe-based shell and conveys the initial geometry viaTERM/COLUMNS/LINESenvironment variables. -
Command history keyed by node UID, with change detection. Interactive history is now scoped to the node's deterministic UID rather than its logical id, under
~/.omnyshell/history/<user>@<nodeUid>.history. The last-seen UID for each<user>@<node>connection target is tracked under~/.omnyshell/node-uids/; when a node reconnects with a changed UID the user is alerted and — interactively — prompted whether to migrate the prior UID's history into the new UID's history (non-interactive sessions migrate automatically). The old history file is always left intact as a backup. Nodes that report no UID fall back to the legacy<user>@<node>key. -
Deterministic global UIDs for nodes and hubs. Each node and hub now derives a stable identifier from its own identity material rather than a random/time seed, so the same machine resolves to the same UID on every start and across hubs. A node UID (
nod_…) combines the node's Ed25519 public key (empty for token/keyless nodes) with stable hardware/platform attributes — a per-OS machine id (/etc/machine-id, macOSIOPlatformUUID, WindowsMachineGuid), os, arch and hostname. A hub UID (hub_…) combines the TLS certificate's public key (SPKI, so it survives cert renewal when the keypair is reused) with the same hardware/platform attributes. Inputs are length-prefixed (TLV) and SHA-256 hashed under a per-kind domain-separation tag, then rendered as URL-safe base64 — every UID is also a valid node id. The UID is persisted under~/.omnyshell/{node,hub}.uid, recomputed on every start, and a change is reported loudly (the previous value is retired into the file's history). The node advertises its UID innode.register(surfaced in discovery and:info); the hub advertises its UID in the challengehelloso peers can identify and pin it. Both are printed at startup by the CLI. -
Command history with arrow-key navigation. While connected to a node, the interactive prompt now supports a real line editor: Up/Down walk backward/forward through previously entered commands, and Left/Right, Home/End (also
Ctrl-A/Ctrl-E), Backspace, Delete,Ctrl-C(discard line) andCtrl-D(EOF on an empty line) edit the line. History is persisted per node + user under~/.omnyshell/history/(mode600), so different nodes or principals never share a history; blank lines and consecutive duplicates are skipped and the file is capped at 1000 entries. Both remote shell commands and local:commandsare recorded; confirmation-prompt answers are not. The prompt switches stdin to raw mode on a TTY and restores it on exit; piped/non-interactive input falls back to plain line reading with history disabled.
Changed #
-
connectbanners now span the full terminal width. The welcome banner's horizontal rules stretch to the terminal width (no longer capped at 72 columns), and exiting/disconnecting now prints a full-width rule beforeSession closed, visually separating the finalized session from the local terminal output. The closing line also names where you were connected (Session closed (exit 0) · <node> @ <hub>). -
connectprompt colors refreshed. The working directory is now cyan and the git segment is blue with a red branch name and green status counts (was a blue cwd and a yellow git segment). -
The
portable_pty(FFI) PTY backend is temporarily deprecated.PtyShellBackend/PtyShellSessionare retained and still opt-in vianode start --pty-backend native(they support live resize), but are no longer the default: the underlying native library has aSIGCHLD-handler memory-safety bug that races the Dart VM's child reaper and can intermittently crash the node (EXC_BAD_ACCESSinsideportable_pty_open). Reported upstream; once fixed this backend will be promoted back to the default and the deprecation removed.
Fixed #
-
Ctrl-C interrupts the remote command instead of closing
connect. Raw mode clearsICANONbut notISIG, so the terminal raisedSIGINTon Ctrl-C and terminated omnyshell before the keystroke ever reached the line editor. Interactive sessions now interceptSIGINTat the process level (so omnyshell stays alive) and relay it to the remote foreground command, discarding the local input line first (or passing straight through to a full-screen app). The remote shell installs a no-opINTtrap at session start so it survives the signal — interrupting a running command without killing the (non-interactive) shell, while the command itself still receives the default disposition and stops. Non-interactive runs keep the default behaviour so a scripted session can still be killed with Ctrl-C. -
Full-screen apps (
nano/vim/less/top) now work overconnect. Two problems are fixed. (1) The cwd-markerprintfwas sent on its own line right after the command, so the non-interactive remote shell left it in the PTY input buffer where a foreground program read it as typed input (every newline innanoechoed the marker, and a straypico.savecould appear). The command and marker are now sent as one logical line —eval '<cmd>' ; <marker>— so the shell consumes both before executing; the marker runs only after the command/app exits.evalkeeps this valid for any command (pipes, trailing&,cd). (2) The client line editor kept buffering keystrokes while a full-screen app was running. The client now watches the output stream for the alternate-screen sequences (ESC[?1049h/ESC[?1049l) and, while the app owns the screen, switches the editor to raw passthrough so keystrokes reach the app verbatim; on exit it restores local line editing and repaints the prompt. (Non-alternate- screen interactive programs such as a bare REPL still have no client-observable signal and remain best-effort.) -
connectno longer shows the remote shell's own prompt over a real PTY. On a PTY the node's shell ran interactively and printed its own PS1/theme prompt and echoed every keystroke — duplicating the client's managed prompt. Thescript(1)backend now runs the shell non-interactively forshellmode, reading its command stream from/dev/stdinwith terminal echo disabled, so the client's prompt is the only one shown. A real PTY is still allocated (full-screen apps and the requested geometry keep working), matching the prompt-free behaviour of the pipe fallback theCwdMarkerdesign assumes. -
Prompt no longer corrupts the cursor on a real PTY. A PTY's
ONLCRrewrites the cwd-marker line's trailing\nas\r\n; the stray\rwas captured as the marker's last (privilege) field, so a non-root session rendered(⚠ \r)into the prompt and the carriage return jumped the cursor to column 0. The marker parser now drops a trailing\rbefore splitting fields. -
PTY sessions now terminate correctly on Linux. The
portable_ptynative library keeps the pty slave fd open for the handle's lifetime, so on Linux the master never reports EOF after the child exits (macOS does), leaving the output stream open forever — interactive sessions appeared to hang and the real-PTY tests timed out on CI. The session now detects child exit explicitly viatryWait()once all readable output has drained, instead of relying solely on master EOF.
0.3.0 #
Changed #
- Local commands now use a
:prefix (:help,:info,:exit, …) instead of/. The old/prefix collided with ordinary shell input that legitimately starts with/— most notably absolute binary paths such as/bin/bash, which were intercepted as unknown local commands instead of running. A colon never begins a real shell command, so local commands and remote shell input are no longer ambiguous. Breaking: scripts or muscle-memory using/help,/exit, etc. must switch to:help,:exit.
Added #
:download/:uploadfile transfer. Inside an interactive session,:download <remotePath> [localDest]and:upload <localPath> [remoteDest]move files and directories between the client and the node. Transfers run over a separate, parallel Hub connection (a dedicatedtransfer-mode session), so the interactive shell stays responsive. The payload is streamed per file and compressed with GZip level 4 (built-indart:iocodec), resumable by byte offset (re-run to continue a partial copy), and every file's SHA-256 is verified end-to-end — a mismatch drops the file so a re-run fetches it cleanly. Implemented purely over the existing binary channel + credit-window flow control, with no Hub changes (handshake/metadata ride a self-framed record stream on stdin/stdout). A progress bar is shown on a TTY.- Destination may be a file or a directory (
cp/scpsemantics, resolved on the receiving side): an existing directory or a path ending in/means write into it (keeping the source's top-level name); otherwise the destination names the result itself (a single file is written to exactly that path; a directory copied onto a non-existent path becomes the new root); copying a directory onto an existing file is refused. - Pre-transfer confirmation spells out the resolved destination, the chosen
mode, and the exact target path of each file (tagged
new/overwrite/resume) before anything is written.
- Destination may be a file or a directory (
- Git branch, status, and privilege in the prompt. When the remote working
directory is git-managed, the
connectprompt now shows the branch and a compact status —user@node:cwd git(branch +S ~M ?U) $— where+S ~M ?Ucounts staged/modified/untracked files and appears only when there are changes. A superuser session also shows a(⚠ root)indicator. Both are gathered over the existing per-command$PWDmarker (no extra round trip) and ANSI-colorized on a TTY (branch yellow, status counts red, root warning bold red;NO_COLORhonored). - Welcome banner on
connect. Opening an interactive session now prints a rule-separated welcome banner summarizing the connection: the OmnyShell CLI version, the node (id, display name, online status, platform, hostname, agent version), advertised capabilities (shells, features, max sessions), operator labels, the authenticated user and roles, the Hub URL, measured round-trip latency, and the session id/mode. Colorized on a TTY (honorsNO_COLOR) and falls back to a plain banner when piped. login/logoutcommands.omnyshell loginauthenticates to a Hub once (verifying the credentials with a real auth handshake) and saves the session to~/.omnyshell/credentials.json(file mode600). Subsequent client commands (connect,exec,nodes list,whoami) then run without credential flags. Sessions are keyed by Hub URL with a remembered default, so multiple Hubs are supported; explicit--principal/--token/--keystill take precedence.omnyshell logoutremoves a saved session (--hub) or all of them (--all). Key-based logins reference the existing Ed25519 seed file by path rather than copying the secret.
Documentation #
- Document the
login/logoutflow and the credential-free command usage in the README, and refresh the badge row (status, tag, commits, PRs, code size).
0.2.0 #
Added #
- Interactive prompt line.
omnyshell connectnow shows auser@node:cwd $prompt before each command. The working directory is tracked live via lightweight shell integration (a hidden per-session marker that reports$PWDafter each command), and the prompt is ANSI-colorized when stdout is a TTY (honoringNO_COLOR).
0.1.0 #
Initial release — Stage 1: secure core and a working Client → Hub → Node
vertical slice.
Added #
- Hub-centric architecture. Clients connect to a Hub by node identity, not
by
host:port. The Hub discovers nodes, authenticates and authorizes principals, and brokers sessions. - WebSocket-on-TLS transport. All connections are encrypted; there is no plaintext or raw-TCP mode. Nodes dial the Hub outbound and hold a persistent control connection (NAT-friendly).
- Multiplexed channel protocol. SSH-channel-style multiplexing over a single connection: JSON control messages on WebSocket text frames, binary stream data (stdin/stdout/stderr) behind a compact 10-byte header on binary frames.
- Pluggable authentication.
Authenticatorcontract with two implementations:PublicKeyAuthenticator(Ed25519,authorized_keys-style, replay-resistant nonce challenge) andTokenAuthenticator(bearer). - Authorization.
Authorizercontract with a default role-based implementation enforced by the Hub on every session open. - Node runtime. Connect → authenticate → register → advertise capabilities → serve sessions, with automatic reconnect and exponential backoff.
- Hub broker.
NodeRegistry,SessionRouter(tunnel relay), and aClock-drivenHeartbeatMonitor. - Client SDK.
execute()for one-shot commands andstartInteractiveShell()for real-time streaming sessions, plus an extensible local/command system. - Process shell backend.
ProcessShellBackendruns commands viaProcess.startbehind aShellBackendinterface (PTY backend can plug in later). - CLI.
omnyshell hub start,node start,connect,exec,nodes list, andwhoami, all built on the public Dart APIs. - Tests. Unit, integration and end-to-end coverage over real
wssloopback connections with a self-signed test certificate.