Skip to content

feat(plugin-terminals): terminals plugin with readonly + interactive PTY modes#37

Draft
antfubot wants to merge 8 commits into
devframes:mainfrom
antfubot:plugin-terminal-2
Draft

feat(plugin-terminals): terminals plugin with readonly + interactive PTY modes#37
antfubot wants to merge 8 commits into
devframes:mainfrom
antfubot:plugin-terminal-2

Conversation

@antfubot

Copy link
Copy Markdown
Contributor

Summary

Adds @devframes/plugin-terminals — the built-in terminals plugin (#2 in the plugin planning index) — as a new plugins/* workspace. It's a portable, hub-native terminal panel built on the core devframe RPC + streaming surface (no hard hub dependency): it runs standalone via the CLI, mounts into a Vite host, and docks inside a hub.

Modes

  • Readonly — a piped child process whose merged stdout/stderr is streamed to viewers; write is rejected (DP_TERMINALS_0003). Ideal for dev servers / logs.
  • Interactive — a real PTY (@homebridge/node-pty-prebuilt-multiarch, prebuilt so there's no native compile step) that accepts keystrokes and resize. Degrades to a piped child process with a diagnostic when no PTY backend is available.

TUI support (e.g. Claude Code)

Interactive sessions are backed by a genuine pseudo-terminal, verified end-to-end:

  • the child sees a real TTY (process.stdout.isTTY === true),
  • stdin is forwarded keystroke-by-keystroke,
  • resize propagates to the PTY and fires SIGWINCH (so TUIs re-layout).

What's included (plugins/terminals/)

  • Node: TerminalManager (PTY + pipe backends; one stream per session kept open for the session's life so restart reuses the same id), setupTerminals(ctx), allow-list spawn model (presets + opt-in allowArbitraryCommands, default-deny), structured DP_TERMINALS_* diagnostics.
  • RPC (defineRpcFunction, namespaced devframes-plugin-terminals:*): list, presets, spawn, write, resize, terminate, restart, remove, with declare module 'devframe' augmentation for server functions + shared-state keys.
  • Client: mountTerminals() xterm.js renderer (CSS inlined, self-contained) — input wired for interactive, disabled for readonly, fit/resize handling. Reusable by the SPA and as a hub custom-render entry.
  • SPA: vanilla TS + Vite, served by the CLI and mountable as an iframe dock in a hub.
  • Adapters: . (createTerminalsDevframe factory + default export), /cli, /vite, plus bin.mjs. Mount path follows resolveBasePath (/ standalone, /__id/ hosted).

Repo wiring

pnpm-workspace.yaml (plugins/* glob + catalogs for node-pty & xterm + allow-build), turbo.json, alias.ts / tsconfig.base.json, vitest.config.ts.

Verification

pnpm lint && pnpm test && pnpm build all green (379 tests, 38 files). Plugin suite covers readonly streaming + exit, readonly write rejection, interactive PTY stdin, TTY detection, SIGWINCH resize, restart id reuse, list/remove, presets, and arbitrary-command rejection — over a real HTTP + WebSocket harness. tsnapi API snapshots added for every entry.

This PR was created with the help of an agent.

…ive PTY modes

Introduce @devframes/plugin-terminals, a portable hub-native terminal
panel built on the core devframe RPC + streaming surface (no hard hub
dependency). It runs standalone via the CLI, mounts into a Vite host,
and docks inside a hub.

Two interaction modes:
- readonly: a piped child process whose merged output is streamed to
  viewers; input is rejected. Ideal for dev servers / logs.
- interactive: a real PTY (node-pty prebuilt) that accepts keystrokes
  and resize, so full-screen TUIs (vim, htop, Claude Code) render
  correctly. Falls back to a piped child process with a diagnostic when
  no PTY backend is present.

Output streams over a per-session channel (one stream kept open for the
session's life so restart reuses the same id), session metadata syncs via
shared state, and spawning is allow-list gated (presets + opt-in
arbitrary commands). Ships node + client + cli + vite entries plus a
self-contained xterm SPA, structured DP_TERMINALS diagnostics, and an
e2e test suite covering streaming, stdin, TTY detection, and SIGWINCH.
@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit c58b6d9
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a34f72453a63a000843eac5
😎 Deploy Preview https://deploy-preview-37--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

antfubot added 7 commits June 19, 2026 03:26
…ith rename

- Follow the system color mode and react to changes at runtime: the UI
  chrome (CSS variables) and every xterm instance switch between dark and
  a GitHub-light palette without reload, driven by prefers-color-scheme.
- Tab labels show the live foreground process of the controlling TTY
  (e.g. bash → vim → bash), polled from node-pty for PTY sessions.
- Custom renaming: double-click a tab to edit inline; backed by a new
  `devframes-plugin-terminals:rename` RPC. Display precedence is
  customTitle > processName > base title.

Adds processName/customTitle to the session descriptor, a getProcessName
hook on the PTY backend, an unref'd poll timer (cleared on exit/restart/
remove/dispose), and tests for process-name tracking and renaming.
…etadata, per-package typecheck)

Merge upstream/main and reconcile the plugin with its newer baseline:
- resolve to nostics ^1.1.4 via the catalog and regenerate the lockfile
  (fixes the broken merged lockfile that failed frozen CI installs).
- supply the now-required DevframeDefinition metadata (version,
  packageName, homepage, description) from package.json.
- add a `typecheck` script and switch tsconfig to explicit include/exclude
  (drop composite) so `turbo run typecheck` passes for the package.
- refresh tsnapi snapshots for the nostics v1 diagnostics handle shape.
- Gate the PTY-semantics tests (stdin echo, SIGWINCH resize, foreground
  process name) to POSIX; Windows keeps the isTTY interactive coverage.
  These rely on behaviours conpty doesn't provide (no SIGWINCH; `.process`
  returns the TERM name).
- Ignore the Windows `xterm-256color` TERM-name fallback so it never
  surfaces as a session label.
- Dispose the terminal manager on test server close so spawned PTY/piped
  child processes don't leak across runs.
…, + tab

- Mirror the active terminal into the URL hash (`#id=<sessionId>`) and react
  to external hash changes (links, back/forward, manual edits).
- Spawn and select a fresh interactive session on every page load.
- Move the "new terminal" affordance to a compact "+" pinned at the end of
  the tab strip.

Avoids refocusing the active terminal on background shared-state updates by
only fitting/focusing when the selection actually changes.
The prebuilt PTY binary isn't published for every Node ABI on every OS
(e.g. Node 26 on Windows 404s and the source-build fallback crashes),
which failed `pnpm install`. node-pty is already lazily imported with a
piped-child fallback, so move it to optionalDependencies — a missing
prebuild is now non-fatal.

Gate the PTY tests on actual backend availability: the real-TTY check runs
wherever a PTY exists (incl. Windows conpty), while the POSIX-only
semantics (SIGWINCH, foreground process name, stdin echo) stay POSIX-gated.
…d of spawning

A reload was always starting a new shell: the sessions shared state
resolves with its empty initial value and backfills the server's sessions
asynchronously, so the autostart check always saw an empty list.

Decide autostart from the authoritative `list` RPC and seed the initial
render from it, so a refresh restores the persisted sessions (reselecting
the URL-hashed one) and only spawns a shell when none exist.
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.

1 participant