omnyshell 1.3.2
omnyshell: ^1.3.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.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.