This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Prepare 0.5.0 release and audit shell completions

Bump version to 0.5.0, flesh out CHANGELOG with missing entries
(DA1 fix, postinstall fix, plugins, exit name), add Plugins section
to README, and audit all shell completions to cover every command
(stats, rm, gc, wrap, unwrap, test) including proper session-name
completion for pty rm and pty stats.

Nathan Herald (Apr 7, 2026, 11:50 PM +0200) 46519b72 fbefdfa1

+140 -29
+10 -1
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 - ## Unreleased 3 + ## 0.5.0 4 4 5 5 ### Client API (`@myobie/pty/client`) 6 6 - New `@myobie/pty/client` entry point for programmatic session management — no TUI framework dependency required ··· 17 17 18 18 ### CLI improvements 19 19 - Add `pty gc` command to remove all exited sessions at once 20 + - Git-style plugin support: `pty <anything>` looks for `pty-<anything>` in PATH and runs it, forwarding remaining args 20 21 - Prevent accidental session nesting: `pty run` inside an existing session execs the command directly instead of creating a nested session (`-d` bypasses the check) 21 22 - Set `PTY_SESSION` env var in child processes so they can detect they're inside a pty session 22 23 - Add CPU and memory usage to `pty stats` (child process and daemon, via `ps`) 23 24 - Add process PIDs to `pty stats` output 24 25 - Gracefully handle older daemons that don't report resource usage 26 + - Exit messages now include the session name (`[myserver exited with code 0]`) 25 27 26 28 ### Events 27 29 - Add terminal event logging — sessions capture bell, title changes, desktop notifications (OSC 9/99/777), focus requests, and cursor visibility transitions to a per-session JSONL file ··· 33 35 - Event files auto-truncate at 1,000 lines (keeping most recent 500) 34 36 - Event file I/O is fully async (non-blocking write queue) 35 37 - Event files are cleaned up with the existing 24-hour dead session TTL 38 + 39 + ### Fixes 40 + - Respond to DA1 (Primary Device Attribute) queries so fish shell 4.x starts in under 50ms instead of blocking 10s at startup (#5) 41 + - Fix postinstall `spawn-helper` chmod to work under pnpm's global virtual store layout, replacing the broken relative-path `chmod` with a proper Node.js script that uses `createRequire` to find node-pty regardless of layout (#8, thanks @schickling) 42 + 43 + ### Tests 44 + - Add shell integration tests covering bash, zsh, and fish startup 36 45 37 46 ## 0.4.1 38 47
+4
README.md
··· 125 125 126 126 Event files auto-truncate at 1,000 lines and are cleaned up with the 24-hour dead session TTL. 127 127 128 + ### Plugins 129 + 130 + Like `git`, `pty` supports extensions: if you run `pty foo` and there's a `pty-foo` executable in your `$PATH`, pty will run it with the remaining arguments. This lets you build your own subcommands without modifying pty. 131 + 128 132 ## Client API 129 133 130 134 @myobie/pty exposes a programmatic TypeScript API for building apps on top of pty sessions. Import from `@myobie/pty/client`.
+31 -11
completions/pty.bash
··· 6 6 COMPREPLY=() 7 7 cur="${COMP_WORDS[COMP_CWORD]}" 8 8 prev="${COMP_WORDS[COMP_CWORD-1]}" 9 - commands="run attach peek send kill list restart events help" 9 + commands="run attach a peek send events list ls stats restart kill rm remove gc wrap unwrap test help" 10 10 11 11 # Complete subcommand 12 12 if [[ ${COMP_CWORD} -eq 1 ]]; then ··· 14 14 return 15 15 fi 16 16 17 - # Complete session names for commands that take them 17 + local session_dir="${PTY_SESSION_DIR:-${HOME}/.local/state/pty}" 18 + local names="" 19 + if [[ -d "${session_dir}" ]]; then 20 + names=$(ls "${session_dir}"/*.json 2>/dev/null | xargs -I{} basename {} .json) 21 + fi 22 + 18 23 case "${COMP_WORDS[1]}" in 19 - attach|a|peek|send|kill|restart|events) 20 - local session_dir="${PTY_SESSION_DIR:-${HOME}/.local/state/pty}" 21 - if [[ -d "${session_dir}" ]]; then 22 - local names 23 - names=$(ls "${session_dir}"/*.json 2>/dev/null | xargs -I{} basename {} .json) 24 - COMPREPLY=($(compgen -W "${names}" -- "${cur}")) 25 - fi 26 - # Flags per subcommand 24 + attach|a|peek|send|kill|restart|events|rm|remove|stats) 27 25 if [[ "${cur}" == -* ]]; then 28 26 case "${COMP_WORDS[1]}" in 29 27 attach|a) COMPREPLY=($(compgen -W "--auto-restart -r" -- "${cur}")) ;; ··· 31 29 send) COMPREPLY=($(compgen -W "--seq --with-delay" -- "${cur}")) ;; 32 30 restart) COMPREPLY=($(compgen -W "--yes -y" -- "${cur}")) ;; 33 31 events) COMPREPLY=($(compgen -W "--all --recent --json" -- "${cur}")) ;; 32 + stats) COMPREPLY=($(compgen -W "--json --all" -- "${cur}")) ;; 34 33 esac 34 + else 35 + COMPREPLY=($(compgen -W "${names}" -- "${cur}")) 35 36 fi 36 37 ;; 37 38 list|ls) ··· 39 40 COMPREPLY=($(compgen -W "--json" -- "${cur}")) 40 41 fi 41 42 ;; 43 + gc) 44 + # No arguments 45 + ;; 46 + wrap) 47 + if [[ "${cur}" == -* ]]; then 48 + COMPREPLY=($(compgen -W "--list -l" -- "${cur}")) 49 + else 50 + COMPREPLY=($(compgen -c -- "${cur}")) 51 + fi 52 + ;; 53 + unwrap) 54 + # Complete from wrapped commands in ~/.local/pty/bin (or $PTY_BIN_PATH) 55 + local wrap_dir="${PTY_BIN_PATH:-${HOME}/.local/pty/bin}" 56 + if [[ -d "${wrap_dir}" ]]; then 57 + local wrapped 58 + wrapped=$(ls "${wrap_dir}" 2>/dev/null) 59 + COMPREPLY=($(compgen -W "${wrapped}" -- "${cur}")) 60 + fi 61 + ;; 42 62 run) 43 63 # After --, fall back to default file completion 44 64 local i ··· 50 70 done 51 71 # Before --, complete flags 52 72 if [[ "${cur}" == -* ]]; then 53 - COMPREPLY=($(compgen -W "--detach -d --attach -a --name" -- "${cur}")) 73 + COMPREPLY=($(compgen -W "--detach -d --attach -a --ephemeral -e --name" -- "${cur}")) 54 74 fi 55 75 ;; 56 76 esac
+53 -10
completions/pty.fish
··· 16 16 end 17 17 end 18 18 19 + function __pty_wrapped 20 + set -l wrap_dir "$PTY_BIN_PATH" 21 + if test -z "$wrap_dir" 22 + set wrap_dir "$HOME/.local/pty/bin" 23 + end 24 + if test -d "$wrap_dir" 25 + for f in $wrap_dir/* 26 + if test -f "$f" 27 + basename $f 28 + end 29 + end 30 + end 31 + end 32 + 19 33 function __pty_needs_command 20 34 set -l cmd (commandline -opc) 21 35 test (count $cmd) -eq 1 ··· 32 46 # Subcommands 33 47 complete -c pty -n __pty_needs_command -a run -d 'Create a session and attach' 34 48 complete -c pty -n __pty_needs_command -a attach -d 'Attach to an existing session' 49 + complete -c pty -n __pty_needs_command -a a -d 'Attach to an existing session' 35 50 complete -c pty -n __pty_needs_command -a peek -d 'Print current screen or follow output' 36 51 complete -c pty -n __pty_needs_command -a send -d 'Send text or keys to a session' 37 - complete -c pty -n __pty_needs_command -a kill -d 'Kill or remove a session' 38 - complete -c pty -n __pty_needs_command -a list -d 'List active sessions' 39 - complete -c pty -n __pty_needs_command -a restart -d 'Restart a session' 40 52 complete -c pty -n __pty_needs_command -a events -d 'Follow terminal events from sessions' 53 + complete -c pty -n __pty_needs_command -a list -d 'List active sessions' 54 + complete -c pty -n __pty_needs_command -a ls -d 'List active sessions' 55 + complete -c pty -n __pty_needs_command -a stats -d 'Show live session metrics' 56 + complete -c pty -n __pty_needs_command -a restart -d 'Restart an exited session' 57 + complete -c pty -n __pty_needs_command -a kill -d 'Kill a running session' 58 + complete -c pty -n __pty_needs_command -a rm -d 'Remove an exited session' 59 + complete -c pty -n __pty_needs_command -a remove -d 'Remove an exited session' 60 + complete -c pty -n __pty_needs_command -a gc -d 'Remove all exited sessions' 61 + complete -c pty -n __pty_needs_command -a wrap -d 'Auto-wrap a command in pty sessions' 62 + complete -c pty -n __pty_needs_command -a unwrap -d 'Remove a wrapper' 63 + complete -c pty -n __pty_needs_command -a test -d 'Run tests (vitest)' 41 64 complete -c pty -n __pty_needs_command -a help -d 'Show usage information' 42 65 43 66 # run: flags and file completion for the command argument 44 67 complete -c pty -n '__pty_using_command run' -s d -l detach -d 'Create in background' 45 68 complete -c pty -n '__pty_using_command run' -s a -l attach -d 'Attach if already running' 69 + complete -c pty -n '__pty_using_command run' -s e -l ephemeral -d 'Auto-remove on exit' 46 70 complete -c pty -n '__pty_using_command run' -l name -x -d 'Session name (auto-generated if omitted)' 47 71 complete -c pty -n '__pty_using_command run' -F 48 72 49 73 # attach: session names and flags 50 74 complete -c pty -n '__pty_using_command attach' -a '(__pty_sessions)' -d 'Session' 51 75 complete -c pty -n '__pty_using_command attach' -s r -l auto-restart -d 'Auto-restart if exited' 76 + complete -c pty -n '__pty_using_command a' -a '(__pty_sessions)' -d 'Session' 77 + complete -c pty -n '__pty_using_command a' -s r -l auto-restart -d 'Auto-restart if exited' 52 78 53 79 # peek: session names and flags 54 80 complete -c pty -n '__pty_using_command peek' -a '(__pty_sessions)' -d 'Session' ··· 60 86 complete -c pty -n '__pty_using_command send' -l seq -d 'Send a sequence item' -r 61 87 complete -c pty -n '__pty_using_command send' -l with-delay -d 'Delay between --seq items (seconds)' -r 62 88 63 - # kill: session names 64 - complete -c pty -n '__pty_using_command kill' -a '(__pty_sessions)' -d 'Session' 65 - 66 - # restart: session names and flags 67 - complete -c pty -n '__pty_using_command restart' -a '(__pty_sessions)' -d 'Session' 68 - complete -c pty -n '__pty_using_command restart' -s y -l yes -d 'Skip confirmation' 69 - 70 89 # events: session names and flags 71 90 complete -c pty -n '__pty_using_command events' -a '(__pty_sessions)' -d 'Session' 72 91 complete -c pty -n '__pty_using_command events' -l all -d 'Follow events from all sessions' ··· 75 94 76 95 # list: flags 77 96 complete -c pty -n '__pty_using_command list' -l json -d 'Output as JSON' 97 + complete -c pty -n '__pty_using_command ls' -l json -d 'Output as JSON' 98 + 99 + # stats: session names and flags 100 + complete -c pty -n '__pty_using_command stats' -a '(__pty_sessions)' -d 'Session' 101 + complete -c pty -n '__pty_using_command stats' -l json -d 'Output as JSON' 102 + complete -c pty -n '__pty_using_command stats' -l all -d 'Include exited sessions' 103 + 104 + # restart: session names and flags 105 + complete -c pty -n '__pty_using_command restart' -a '(__pty_sessions)' -d 'Session' 106 + complete -c pty -n '__pty_using_command restart' -s y -l yes -d 'Skip confirmation' 107 + 108 + # kill: session names 109 + complete -c pty -n '__pty_using_command kill' -a '(__pty_sessions)' -d 'Session' 110 + 111 + # rm/remove: session names 112 + complete -c pty -n '__pty_using_command rm' -a '(__pty_sessions)' -d 'Session' 113 + complete -c pty -n '__pty_using_command remove' -a '(__pty_sessions)' -d 'Session' 114 + 115 + # wrap: commands in PATH, and --list flag 116 + complete -c pty -n '__pty_using_command wrap' -s l -l list -d 'List all wrapped commands' 117 + complete -c pty -n '__pty_using_command wrap' -a '(__fish_complete_command)' -d 'Command' 118 + 119 + # unwrap: previously wrapped commands 120 + complete -c pty -n '__pty_using_command unwrap' -a '(__pty_wrapped)' -d 'Wrapped command'
+38 -3
completions/pty.zsh
··· 4 4 5 5 _pty() { 6 6 local session_dir="${PTY_SESSION_DIR:-${HOME}/.local/state/pty}" 7 + local wrap_dir="${PTY_BIN_PATH:-${HOME}/.local/pty/bin}" 7 8 8 9 _pty_sessions() { 9 10 local -a sessions ··· 13 14 _describe 'session' sessions 14 15 } 15 16 17 + _pty_wrapped() { 18 + local -a wrapped 19 + if [[ -d "${wrap_dir}" ]]; then 20 + wrapped=(${wrap_dir}/*(N:t)) 21 + fi 22 + _describe 'wrapped command' wrapped 23 + } 24 + 16 25 local -a commands 17 26 commands=( 18 27 'run:Create a session and attach' 19 28 'attach:Attach to an existing session' 29 + 'a:Attach to an existing session' 20 30 'peek:Print current screen or follow output' 21 31 'send:Send text or keys to a session' 22 - 'kill:Kill or remove a session' 32 + 'events:Follow terminal events from sessions' 23 33 'list:List active sessions' 24 34 'ls:List active sessions' 35 + 'stats:Show live session metrics' 25 36 'restart:Restart an exited session' 26 - 'events:Follow terminal events from sessions' 37 + 'kill:Kill a running session' 38 + 'rm:Remove an exited session' 39 + 'remove:Remove an exited session' 40 + 'gc:Remove all exited sessions' 41 + 'wrap:Auto-wrap a command in pty sessions' 42 + 'unwrap:Remove a wrapper' 43 + 'test:Run tests (vitest)' 27 44 'help:Show usage information' 28 45 ) 29 46 ··· 54 71 '--with-delay[Delay between --seq items (seconds)]:seconds:' \ 55 72 '*--seq[Send a sequence item]:value:' 56 73 ;; 57 - kill) 74 + kill|rm|remove) 58 75 _arguments '1:session:_pty_sessions' 59 76 ;; 60 77 restart) ··· 69 86 '--json[Output raw JSONL]' \ 70 87 '1:session:_pty_sessions' 71 88 ;; 89 + stats) 90 + _arguments \ 91 + '--json[Output as JSON]' \ 92 + '--all[Include exited sessions]' \ 93 + '1:session:_pty_sessions' 94 + ;; 72 95 list|ls) 73 96 _arguments \ 74 97 '--json[Output as JSON]' 75 98 ;; 99 + gc) 100 + # No arguments 101 + ;; 102 + wrap) 103 + _arguments \ 104 + '(-l --list)'{-l,--list}'[List all wrapped commands]' \ 105 + '1:command:_command_names -e' 106 + ;; 107 + unwrap) 108 + _arguments '1:wrapped:_pty_wrapped' 109 + ;; 76 110 run) 77 111 # After --, fall back to normal (command + file) completion 78 112 local -i i ··· 87 121 _arguments \ 88 122 '(-d --detach)'{-d,--detach}'[Create in background]' \ 89 123 '(-a --attach)'{-a,--attach}'[Attach if already running]' \ 124 + '(-e --ephemeral)'{-e,--ephemeral}'[Auto-remove on exit]' \ 90 125 '--name[Session name]:name:' 91 126 ;; 92 127 esac
+1 -1
flake.nix
··· 29 29 30 30 # Generated from package-lock.json. 31 31 # Regenerate with: nix run nixpkgs#prefetch-npm-deps -- package-lock.json 32 - npmDepsHash = "sha256-RpVlZ+xn3KY2xEgyDNqEe9/B6IOV3wX+MgGjXaNttDA="; 32 + npmDepsHash = "sha256-GURZuKFC4cuaI+RKJ3s4vLL/G1/1UrZXN9yhgGj7d2A="; 33 33 34 34 # node-pty has native code that needs these at build time 35 35 nativeBuildInputs = with pkgs; [ python3 pkg-config ];
+2 -2
package-lock.json
··· 1 1 { 2 2 "name": "@myobie/pty", 3 - "version": "0.3.0", 3 + "version": "0.5.0", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "@myobie/pty", 9 - "version": "0.3.0", 9 + "version": "0.5.0", 10 10 "hasInstallScript": true, 11 11 "license": "MIT", 12 12 "dependencies": {
+1 -1
package.json
··· 1 1 { 2 2 "name": "@myobie/pty", 3 - "version": "0.4.1", 3 + "version": "0.5.0", 4 4 "description": "Persistent terminal sessions with detach/attach, plus a Playwright-style testing library for TUI apps", 5 5 "type": "module", 6 6 "license": "MIT",