This repository has no description
0

Configure Feed

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

docs: fix stale client API + development docs (onboarding audit) (#63)

Audited README + all repo docs against the code on main. Two files carried
factual errors; the rest (README, CHANGELOG, disk-layout, SKILL, testing)
verified accurate.

docs/client.md:
- gc() documented as `Promise<string[]>` returning removed names; it actually
returns a `GcResult` object. Corrected the signature, example, and added the
GcResult shape.
- getSessionDir() described only `PTY_SESSION_DIR`; it prefers the canonical
`PTY_ROOT` (legacy name still honored).
- isReservedTagKey() listed `supervisor.status`, which is no longer a reserved
key (the supervisor was replaced by cron gc). Removed it.

DEVELOPMENT.md:
- Said the project "ships TypeScript directly, no build step, run via tsx".
Actually: tsx is not a dependency, `npm run build` compiles src/ to dist/
(rewriteRelativeImportExtensions), the published package ships dist/, and
bin/pty runs dist/cli.js with Node. Rewrote the build section, the design-
decision note, the architecture line, and the bin/pty entry; dev-usage
examples now use `node --experimental-strip-types src/cli.ts`.
- `pty run <name> <command>` -> `pty run -- <command>` (actual syntax).
- Socket-override env `$PTY_SESSION_DIR` -> canonical `$PTY_ROOT`.

verify-docs (docs/testing.md executable examples) still passes: 13/13.

authored by

Nathan and committed by
GitHub
(Jul 9, 2026, 10:33 AM +0200) 57c3c2ed 773fadbc

+40 -28
+20 -20
DEVELOPMENT.md
··· 5 5 ## Objectives 6 6 7 7 - Replace tmux/zellij for **session persistence** of long-running processes 8 - - Simple CLI: `pty run <name> <command>`, `pty attach <name>`, `pty peek <name>`, `pty restart <name>` 8 + - Simple CLI: `pty run -- <command>`, `pty attach <name>`, `pty peek <name>`, `pty restart <name>` 9 9 - Reliable detach/reattach with full screen replay (colors, cursor position, scrollback) 10 10 - Work seamlessly over SSH (Unix sockets, no port management) 11 11 - Multi-client support (multiple people can observe or interact with a session) ··· 26 26 npm run test:watch # run tests in watch mode 27 27 npm run verify-docs # run executable examples in docs/testing.md 28 28 29 - # Usage (during development) 30 - npx tsx src/cli.ts run <name> -- <command> [args...] 31 - npx tsx src/cli.ts run -d <name> -- <command> [args...] 32 - npx tsx src/cli.ts attach <name> 33 - npx tsx src/cli.ts peek <name> 34 - npx tsx src/cli.ts peek -f <name> 35 - npx tsx src/cli.ts list 36 - npx tsx src/cli.ts restart <name> 37 - npx tsx src/cli.ts kill <name> 38 - npx tsx src/cli.ts test # run tests via vitest 39 - npx tsx src/cli.ts test -t "pattern" # run matching tests 29 + # Usage (during development — run the TS source directly with Node) 30 + node --experimental-strip-types src/cli.ts run -- <command> [args...] 31 + node --experimental-strip-types src/cli.ts run -d -- <command> [args...] 32 + node --experimental-strip-types src/cli.ts attach <name> 33 + node --experimental-strip-types src/cli.ts peek <name> 34 + node --experimental-strip-types src/cli.ts peek -f <name> 35 + node --experimental-strip-types src/cli.ts list 36 + node --experimental-strip-types src/cli.ts restart <name> 37 + node --experimental-strip-types src/cli.ts kill <name> 38 + node --experimental-strip-types src/cli.ts test # run tests via vitest 39 + node --experimental-strip-types src/cli.ts test -t "pattern" # run matching tests 40 40 ``` 41 41 42 42 Detach from any attached/following session with **Ctrl+\\**. Press Ctrl+\\ twice quickly to send it to the process. 43 43 44 - ### No build step 44 + ### Build 45 45 46 - This project ships TypeScript source directly — there is no compile step. All `.ts` files use `.ts` import extensions and are executed at runtime by [tsx](https://tsx.is). The `bin/pty` entry point locates tsx from `node_modules` and runs `src/cli.ts`. The `tsc` command is used only for typechecking (`noEmit: true`). 46 + Source is written in TypeScript with `.ts` import extensions. `npm run build` compiles `src/` to `dist/` via `tsc -p tsconfig.build.json` (`rewriteRelativeImportExtensions` turns the `.ts` import specifiers into `.js` in the output), and the published package ships `dist/`. The `bin/pty` entry point runs the compiled `dist/cli.js` with Node — so run `npm run build` before invoking the installed `pty`. During development you can skip the build and run the source directly with Node's native type stripping: `node --experimental-strip-types src/cli.ts …`. The separate `tsconfig.json` (`noEmit: true`, `allowImportingTsExtensions: true`) backs `npm run typecheck` only. 47 47 48 48 ## Architecture 49 49 ··· 70 70 (attach) (attach) (read-only) 71 71 ``` 72 72 73 - Each session is a detached Node.js process running `src/server.ts` via tsx. It spawns the command in a PTY, feeds all output through xterm-headless to maintain a screen buffer, and listens on a Unix socket for client connections. 73 + Each session is a detached Node.js process running the compiled `server.js` with Node. It spawns the command in a PTY, feeds all output through xterm-headless to maintain a screen buffer, and listens on a Unix socket for client connections. 74 74 75 75 ## Protocol 76 76 ··· 90 90 91 91 ## Key Design Decisions 92 92 93 - ### No build step — ship TypeScript directly 93 + ### Compile to `dist/`, keep `.ts` sources runnable 94 94 95 - There is no `dist/` directory. Source files use `.ts` import extensions and run directly via tsx. The `tsconfig.json` has `noEmit: true` and `allowImportingTsExtensions: true` — tsc is only used for typechecking. This eliminates the compile-then-run dance entirely. tsx is a regular dependency (not devDependency) because it's needed at runtime. 95 + The published package ships compiled JavaScript in `dist/` (`tsc -p tsconfig.build.json`, with `rewriteRelativeImportExtensions` rewriting `.ts` imports to `.js`), so an install never depends on a TypeScript runtime or Node's still-experimental type stripping. Sources keep `.ts` import extensions, so during development they also run directly under `node --experimental-strip-types` with no build. `tsconfig.json` (`noEmit: true`, `allowImportingTsExtensions: true`) is typecheck-only. 96 96 97 97 ### No TypeScript enums 98 98 99 - We avoid TS enums because they emit runtime code that can't be type-stripped. Instead, we use `as const` objects with a derived union type. This is compatible with all TS runtimes: Node's native type stripping, tsx, esbuild, swc. 99 + We avoid TS enums because they emit runtime code that can't be type-stripped. Instead, we use `as const` objects with a derived union type. This keeps the sources runnable under Node's native type stripping (and any other type-stripper) without a build. 100 100 101 101 ### Peek clients don't affect terminal size 102 102 ··· 116 116 117 117 ### Unix sockets for local IPC 118 118 119 - Fast, zero-config, and work transparently over SSH. No ports to manage, no firewall rules. Session sockets live at `~/.local/state/pty/<name>.sock` (override with `$PTY_SESSION_DIR`). 119 + Fast, zero-config, and work transparently over SSH. No ports to manage, no firewall rules. Session sockets live at `~/.local/state/pty/<name>.sock` (override the root with `$PTY_ROOT`). 120 120 121 121 ### PtyServer never calls process.exit() 122 122 ··· 210 210 pty.bash Bash tab completion 211 211 pty.zsh Zsh tab completion 212 212 bin/ 213 - pty Entry point (locates tsx, runs src/cli.ts) 213 + pty Entry point (runs dist/cli.js with Node) 214 214 ``` 215 215 216 216 ## Future
+20 -8
docs/client.md
··· 25 25 26 26 ### `getSessionDir(): string` 27 27 28 - Returns the session directory path (`PTY_SESSION_DIR` env var or `~/.local/state/pty`). 28 + Returns the session directory path — `$PTY_ROOT` if set (the legacy `$PTY_SESSION_DIR` name is still honored), otherwise `~/.local/state/pty`. 29 29 30 30 ### `getSocketPath(name: string): string` 31 31 32 32 Returns the Unix socket path for a session. 33 33 34 - ### `gc(opts?: { dryRun?: boolean }): Promise<string[]>` 34 + ### `gc(opts?: { dryRun?: boolean; idleDays?: number; fastFailWindowSec?: number; fastFailLimit?: number }): Promise<GcResult>` 35 35 36 - Remove all exited **and** vanished sessions. Returns the names of removed sessions. Pass `{ dryRun: true }` to walk the same set without deleting anything — useful for preview UIs. 36 + Run one reconciliation pass: remove exited/vanished non-permanent sessions, kill orphaned `parent=` children, reap abandoned permanents, and respawn (or flap-skip) `strategy=permanent` sessions. Returns a `GcResult` describing everything the pass did. Pass `{ dryRun: true }` to compute the same plan without mutating anything — useful for preview UIs. 37 37 38 38 ```typescript 39 - const removed = await gc(); 40 - console.log(`Cleaned up ${removed.length} sessions`); 39 + const result = await gc(); 40 + console.log(`Removed ${result.removed.length}, respawned ${result.respawned.length}`); 41 41 42 - const would = await gc({ dryRun: true }); 43 - console.log(`Would clean up: ${would.join(", ")}`); 42 + const plan = await gc({ dryRun: true }); 43 + console.log(`Would remove: ${plan.removed.join(", ")}`); 44 + ``` 45 + 46 + ```typescript 47 + interface GcResult { 48 + removed: string[]; // exited/vanished non-permanent sessions cleaned up 49 + killedOrphanChildren: { name: string; parent: string; reason: "missing" | "dead" }[]; 50 + abandoned: { name: string; reason: "cwd-gone" | "idle"; idleDays?: number }[]; // live permanents reaped as abandoned 51 + respawned: { name: string; ptyfileReread: boolean }[]; 52 + respawnFailed: { name: string; error: string }[]; 53 + flapped: { name: string; counter: number; limit: number; window: number }[]; // flipped to strategy.status=flapping this tick 54 + flappingSkipped: string[]; // already-flapping, skipped this tick 55 + } 44 56 ``` 45 57 46 58 ### `isGone(status): boolean` ··· 60 72 61 73 ### `isReservedTagKey(key: string): boolean` 62 74 63 - Returns `true` for pty's internal bookkeeping keys (`ptyfile`, `ptyfile.session`, `ptyfile.tags`, `supervisor.status`, `strategy`) and for any key starting with `:` (the tool-owned-tag convention). Downstream tools should hide reserved keys from user-facing listings by default but still allow writes — set and unset them as needed. 75 + Returns `true` for pty's internal bookkeeping keys (`ptyfile`, `ptyfile.session`, `ptyfile.tags`, `strategy`) and for any key starting with `:` (the tool-owned-tag convention). Downstream tools should hide reserved keys from user-facing listings by default but still allow writes — set and unset them as needed. 64 76 65 77 ### `cleanupSocket(name: string): void` 66 78