Skip to content

feature: host-join mode, ngrok HTTPS tunnelling, and session UX improvements#3

Closed
marknimbuspay wants to merge 3 commits intodiorwave:mainfrom
marknimbuspay:feature/cmd-chat-enhancements
Closed

feature: host-join mode, ngrok HTTPS tunnelling, and session UX improvements#3
marknimbuspay wants to merge 3 commits intodiorwave:mainfrom
marknimbuspay:feature/cmd-chat-enhancements

Conversation

@marknimbuspay
Copy link
Copy Markdown

Summary

This PR adds several quality-of-life and security improvements to cmd-chat, centred around three themes: hosting UX, public access via ngrok, and clean
session lifecycle management.

Host-join mode (--join)

  • New --join [USERNAME] flag on serve lets the person starting the server participate in the chat from the same command, without opening a second
    terminal
  • The server runs in a daemon subprocess; the host's client connects to it automatically once it's ready
  • When the host quits with q, a farewell message is broadcast to all participants and the server shuts down immediately — remote clients are dropped to
    a clean exit prompt in sync

ngrok HTTPS tunnel (--ngrok)

  • New --ngrok flag on serve opens a public HTTPS tunnel via pyngrok, allowing participants to connect from
    anywhere without port forwarding
  • Optional --ngrok-token TOKEN to supply a free ngrok authtoken (removes session limits)
  • After the server is ready, a Rich panel is printed with the public address and a ready-to-copy connect command
  • The tunnel uses ngrok's HTTP tunnel type (TLS-terminated), so clients automatically connect over wss:// and https://
  • The connect command example shows <username> and <password> as placeholders — the password is intentionally omitted with a prominent security
    warning: it must be pre-shared out-of-band and never sent over the chat channel
  • With --join, the host's screen holds at the public access panel until the first remote participant connects, giving time to share the address

Session lifecycle & terminal hygiene

  • On quit, all participants land on a "Press any key to clear screen and exit" prompt simultaneously — the screen and scrollback buffer are cleared
    via a single atomic write to /dev/tty (\033[H\033[2J\033[3J)
  • GRACEFUL_SHUTDOWN_TIMEOUT = 0 so Ctrl-C on serve exits immediately rather than waiting up to 15 seconds
  • Cancelled asyncio tasks are now explicitly awaited so the readline unblocks immediately when the server disconnects (no stale input prompt)
  • venv is auto-activated on launch via os.execv; the terminal is restored cleanly on exit

CLI improvements

  • username and password on connect are now optional positional arguments — if omitted, they are prompted interactively
  • --password on serve is now optional — prompted if not supplied
  • --join accepts an optional username; omitting it prompts interactively
  • Passwords are always prompted via getpass (no terminal echo)

Bug fixes

  • Fixed reconnect bug: users can now rejoin with the same username after disconnecting
  • Fixed SOCKS proxy error on macOS: proxy=None passed to websockets.connect to suppress automatic system proxy detection
  • Fixed client hang on server shutdown: replaced run_in_executor stdin reading with asyncio.connect_read_pipe, eliminating the thread that blocked
    asyncio.run() from returning

Test plan

  • serve without flags starts cleanly; Ctrl-C exits immediately
  • serve --join prompts for username if omitted, connects host to chat
  • serve --join alice --ngrok shows public access panel, waits for first participant, then opens chat with tunnel address in header
  • Remote client connects via ngrok address using wss:// (port 443)
  • Host quits with q — all clients drop to "press any key" simultaneously
  • connect without username/password prompts for both
  • Same username can reconnect after disconnecting

marksalter added 3 commits May 4, 2026 11:51
…ixes

Server:
- New --join/-j flag on serve command starts the server and immediately
  joins as a chat participant in the same process (server runs as a
  daemon child process via multiprocessing to work around signal handler
  restrictions on non-main threads)
- Host typing q broadcasts a farewell message to all connected clients
  before shutting the server down
- Session store now cleaned up on WebSocket disconnect, allowing users
  to reconnect with the same username after leaving

Client:
- Async stdin reading via connect_read_pipe replaces run_in_executor,
  eliminating the hang on exit caused by asyncio waiting for the
  blocked input() thread
- SOCKS proxy bypass (proxy=None) prevents websockets auto-detecting
  system proxy configuration for local connections
- Chat display scales to terminal height with messages pinned to the
  bottom and input at the foot of the screen (minimum 15 message rows)
- Separators fill terminal width dynamically
- Server shutdown detected and reported; client exits cleanly without
  requiring user input
- Screen cleared and session ended message shown on clean exit;
  error output preserved on connection or auth failure

Launcher:
- cmd_chat.py auto-selects venv interpreter via os.execv when not
  already running inside it (detected via sys.prefix comparison)
- Server and client imports are lazy so connect command has no
  dependency on Sanic
Adds optional ngrok HTTPS tunnel to the serve command via --ngrok and
--ngrok-token flags. On startup, opens an HTTP tunnel (TLS-terminated
at grok).
The host's chat UI shows the tunnel address in the Online
header. Client auto-selects https:// and wss:// when connecting on port 443.
Tunnel is torn down cleanly on server exit.
ngrok:
- Show public access panel after Sanic "Worker ready" (not before startup
  noise) via after_server_start listener
- With --join, hold at the panel until the first remote participant
  connects before launching the host's chat client
- Redisplay ngrok panel inside the host's chat client (cleared on start)
- Password omitted from connect command example — show <password>
  placeholder with a security warning to share it out-of-band

Session exit:
- Kill server process via on_disconnect callback the moment the host's
  websocket closes, so remote clients drop to "press any key" in sync
- Await cancelled tasks after asyncio.wait so readline unblocks
  immediately when the server disconnects; no stale input prompt
- Clear visible screen and scrollback (\033[H\033[2J\033[3J) written
  atomically to /dev/tty to avoid buffer-ordering issues with Rich
- GRACEFUL_SHUTDOWN_TIMEOUT = 0 so Ctrl-C on serve exits immediately

CLI:
- username and password are now optional positionals on connect,
  and --password is optional on serve; missing values are prompted
  interactively (password via getpass, no echo)
- --join now accepts an optional username; omitting it prompts
@marknimbuspay marknimbuspay closed this by deleting the head repository May 4, 2026
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.

2 participants