Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ webtty/
└── README.md
```

## Development Workflow

Always build and stop the old server before testing:

```
bun run build && bun run webtty stop
```
Comment thread
jesse23 marked this conversation as resolved.

## Rules

- **Match existing patterns** — read 2-3 similar files before writing new ones.
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# webtty

A web TTY for running CLI/TUI applications in a browser tab, across platforms.

## Debugging

Build emits source maps (`dist/**/*.js.map`), so you can debug against the built output directly — no minification, original TypeScript line numbers preserved.

```
bun run build
bun --inspect run dist/server/index.js
# or
node --inspect dist/server/index.js
```
2 changes: 1 addition & 1 deletion docs/adrs/001.webtty.bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The original package requires `node-gyp` compilation on install. `@lydell/node-p
## Consequences

- Full round-trip working on both Bun and Node.js runtimes
- `bun run dev` uses `Bun.Terminal` (native); `npm run dev:node` uses `node-pty`
- `bun run dev` uses `Bun.Terminal` (native); `npm run dev:node` uses `node-pty` _(these scripts were removed in a later cleanup; see `package.json` for current scripts)_
- No config file yet — all values hardcoded; acceptable for this slice
- No auth — localhost-only, same security posture as ghostty-web/demo
- Frontend is plain HTML/JS — no TypeScript in browser until a build step is added
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
"lint:fix": "tsc --noEmit -p tsconfig.lint.json && bunx biome check --write .",
"test": "bun test",
"build": "bun run scripts/build.ts",
"dev": "bun run src/server/index.ts",
"prod": "bun run dist/server/index.js",
"dev:node": "tsx src/server/index.ts",
"prod:node": "node dist/server/index.js",
"server": "bun run dist/server/index.js",
"server:node": "node dist/server/index.js",
"webtty": "bun run dist/cli/index.js",
"prepack": "bun scripts/clean-pkg-scripts.ts strip",
"postpack": "bun scripts/clean-pkg-scripts.ts restore"
Expand Down
9 changes: 8 additions & 1 deletion src/cli/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ export async function isServerRunning(): Promise<boolean> {
}

export async function startServer(): Promise<void> {
const isTs = __filename.endsWith('.ts');
// When running from source (e.g. `bun run src/cli/index.ts` during development),
// __filename ends with .ts and Bun is the runtime, so we can point at the .ts
// server entry directly. Built output always lands in .js, so Node is never
// asked to execute TypeScript. In production isTs is always false.
const isBun = typeof (globalThis as Record<string, unknown>).Bun !== 'undefined';
const isTs = isBun && __filename.endsWith('.ts');
const serverEntry = path.resolve(__dirname, isTs ? '../server/index.ts' : '../server/index.js');
if (!fs.existsSync(serverEntry)) {
console.error(`webtty: server entry not found at ${serverEntry}`);
process.exit(1);
}
// Reuse the current runtime (bun, node, etc.) to spawn the server so the
// server always runs under the same runtime as the CLI.
const child = spawn(process.execPath, [serverEntry], {
detached: true,
stdio: 'ignore',
Expand Down
5 changes: 5 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export function render(sessionId: string): string {
setTimeout(() => window.close(), 500);
return;
}
if (event.code === 1001) {
term.write('\\r\\n\\x1b[33mServer stopped.\\x1b[0m\\r\\n');
setTimeout(() => window.close(), 500);
return;
}
console.log('[webtty] disconnected, reconnecting in 2s...');
term.write('\\r\\n\\x1b[31mConnection closed. Reconnecting in 2s...\\x1b[0m\\r\\n');
setTimeout(connect, 2000);
Expand Down
11 changes: 8 additions & 3 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const httpServer = http.createServer((req, res) => {
handleRequest(req, res, distPath, wasmPath, () => {
for (const session of sessionRegistry.values()) {
session.pty?.kill();
for (const client of session.clients) client.close();
for (const client of session.clients) client.close(1001, 'server stopped');
}
Comment thread
jesse23 marked this conversation as resolved.
wss.close();
httpServer.close(() => process.exit(0));
Expand All @@ -25,10 +25,15 @@ process.on('SIGINT', () => {
console.log('\n\nShutting down...');
for (const session of sessionRegistry.values()) {
session.pty?.kill();
for (const client of session.clients) client.close();
for (const client of session.clients) client.close(1001, 'server stopped');
}
wss.close();
process.exit(0);
const exit = () => process.exit(0);
const shutdownTimeout = setTimeout(exit, 1000);
httpServer.close(() => {
clearTimeout(shutdownTimeout);
exit();
});
});

httpServer.listen(HTTP_PORT, '127.0.0.1', () => {
Expand Down
Loading