Ctrl K
ComunidadGuías…

CLAUDE.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Web Console - Agent Context

## What This Is

Browser-based terminal workspace: "Termius in a browser". It has xterm.js terminals, tabs, split panes, workspaces, file manager, Monaco editor, uploads, voice input, circadian theme, and a theme gallery.

Production URL is deployment-specific.

Stack: Next.js 16, React 19, xterm.js v6, node-pty, Monaco, SQLite, Zustand, Tailwind 4, ws.

## Current Architecture

```text
browser
  -> nginx/basic_auth
       -> localhost:8790  web-console.service       Next.js standalone
            -> server-side Chrome sessions for browser panels
       -> localhost:8791  web-console-ws.service    authenticated WS gateway
            -> localhost:8792  web-console-pty.service  long-lived PTY daemon
                 -> node-pty bash sessions
```

Important consequence: `web-console-ws.service` is no longer the shell owner. It can restart without killing shell sessions. `web-console-pty.service` is the owner of live PTY processes; restarting it still kills live shells.

## Hard Rules

1. Keep raw `node-pty` semantics. Do not add tmux/zellij as a backend or default backend.
2. `web-console-pty.service` must bind to `127.0.0.1` only.
3. Browser-facing terminal traffic goes through `web-console-ws.service` with JWT auth.
4. Do not commit `.env`, SQLite DB files, uploads, or build output.
5. Do not reintroduce personal defaults as code constants. Use `.env.example` and env vars.
6. SSR stays effectively off for the main app. Zustand + localStorage workspace state is browser-owned.
7. xterm accessibility tree hiding in `TerminalPanel.tsx` is intentional; changing it can bring back copy/selection artifacts.
8. Ctrl+C must remain SIGINT. Use Ctrl+Shift+C for terminal copy.
9. On Ubuntu, protect `web-console-pty.service` and `web-console-ws.service` from automatic `needrestart` restarts. The PTY daemon owns interactive work; unattended restarts kill live shells.
10. Manual login after auth expiry must sync SQLite workspace state before authenticated UI effects can save state.
11. Trusted PTY daemon session lists are recovery data: live sessions missing from workspace JSON should be surfaced as recovered terminal tabs.
12. Browser panels are server-side Chrome frame surfaces, not iframe previews. Do not reintroduce iframe-only behavior as the main browser panel path.
13. `/api/browser` is the logged-in UI browser endpoint. `/api/admin/browser` must remain loopback-only and admin-token guarded.
14. Restarting `web-console.service` may end browser sessions, but it must clean Chrome and crashpad helper processes. Do not involve `web-console-pty.service` in browser cleanup.

## Services

```text
web-console.service
  ExecStart=/usr/bin/node .next/standalone/server.js
  PORT=8790

web-console-ws.service
  ExecStart=/usr/bin/node server/ws-server.mjs
  WS_PORT=8791
  Requires=web-console-pty.service

web-console-pty.service
  ExecStart=/usr/bin/node server/pty-daemon.mjs
  PTY_DAEMON_HOST=127.0.0.1
  PTY_DAEMON_PORT=8792
```

Deploy sequence:

```bash
pnpm build
systemctl restart web-console.service
systemctl restart web-console-ws.service
```

Only restart `web-console-pty.service` when intentionally accepting that live shell processes will be killed.

## Key Files

```text
server/
  pty-daemon.mjs        # Long-lived localhost daemon, owns PTY sessions
  ws-server.mjs         # Browser WS gateway, auth + proxy to daemon
  pty-manager.mjs       # node-pty pool, scrollback, create/write/resize/kill/list/stats

src/lib/
  browser-automation.ts # CDP/headless Chrome sessions, frames, input, lifecycle cleanup
  theme-palettes.ts     # Curated/imported theme palette schema and presets

scripts/
  smoke-pty-gateway.mjs # Verifies WS gateway restart does not kill shell session
  browser-regression.mjs # Browser/terminal/UI regression against a running app

bin/
  webc.mjs              # Localhost admin/control CLI

deploy/systemd/
  web-console.service
  web-console-ws.service
  web-console-pty.service

deploy/needrestart/
  99-web-console-protect-interactive.conf # Prevents unattended-upgrades from killing live PTYs

src/app/
  HomeClient.tsx        # Main UI, WS lifecycle, trusted welcome/session cleanup
  api/browser/          # Logged-in UI browser panel API
  api/admin/browser/    # Localhost/token admin browser API for webc and agents
  api/workspace/        # SQLite workspace persistence
  api/files/            # File APIs
  api/upload/           # Multipart uploads

src/components/
  TerminalPanel.tsx     # xterm.js instance and create/attach/reconnect logic
  BrowserPanel.tsx      # Server-side browser viewport, toolbar, input batching
  ThemeGallery.tsx      # Curated palettes, preview/apply, JSON import/export
  SplitContainer.tsx    # Split tree and pane lifecycle
  Sidebar.tsx           # Workspace switcher/templates/import/export
  FileManager.tsx       # File browser
  MonacoEditor.tsx      # Editor
  VoiceInput.tsx        # Recording/transcription

src/stores/
  workspace-store.ts    # Zustand state, local/server merge policy
```

## Environment

Use `.env.example` as the public-safe template.

Required:

```dotenv
AUTH_PASSWORD=...
JWT_SECRET=...
# ALLOW_INSECURE_COOKIE=true only for explicit non-loopback HTTP testing
PORT=8790
WS_PORT=8791
NEXT_PUBLIC_WS_PORT=8791
PTY_DAEMON_HOST=127.0.0.1
PTY_DAEMON_PORT=8792
PTY_DAEMON_URL=ws://127.0.0.1:8792
PTY_IDLE_TIMEOUT_HOURS=168
WORKSPACE_ROOT=/workspace
ALLOWED_CWD_ROOT=/workspace
UPLOAD_DIR=/workspace/uploads
NEXT_PUBLIC_WORKSPACE_ROOT=/workspace
NEXT_PUBLIC_UPLOAD_DIR=/workspace/uploads
NEXT_PUBLIC_APP_LABEL=Web Console
```

Optional:

```dotenv
CHROME=/usr/bin/google-chrome
WEB_CONSOLE_BROWSER_SESSION_TTL_MS=600000
GROQ_API_KEY=...
```

## State Model

- Browser `localStorage`: immediate workspace/layout cache.
- SQLite `data/web-console.db`: server-side cross-device workspace state.
- PTY daemon memory: live shell processes and scrollback.

PTY idle cleanup is controlled by `PTY_IDLE_TIMEOUT_HOURS`. The default is 168 hours, and values below 48 hours are raised to 48 hours. Browser attach, resize, terminal input/output, and metadata updates all count as activity.

The frontend must only clear missing terminal session IDs after a trusted live session list from the gateway. An untrusted or stale welcome must not erase local layout.

Merge rule: richer local workspace layout wins over poorer server snapshots even if the server timestamp is newer. This prevents F5/reconnect from collapsing multi-workspace layouts into an old single-tab server state.

Auth expiry rule: the explicit login form must call `syncFromServer()` before `setAuth(true, token)`. If `setAuth` runs first, `HomeClient` effects can create/save tabs from stale `localStorage` and overwrite SQLite before `/api/workspace` is read. This caused the 2026-05-01 "clean sheet" incident.

Recovery rule: a trusted gateway welcome contains the PTY daemon's live session list. If a daemon session is not referenced by the active tabs or any saved workspace, add it back as a recovered terminal tab. The daemon may still contain useful scrollback and resume commands even when workspace JSON lost the reference.

## Verification

Run before claiming the console is fixed:

```bash
pnpm exec tsc --noEmit
pnpm lint
pnpm build
pnpm test:pty-gateway
pnpm test:browser-regression
systemctl status web-console.service web-console-ws.service web-console-pty.service --no-pager
```

Behavioral checks:

- Create terminal, split pane, create another workspace, split again.
- Reload page: workspace count, split count, and session IDs must remain stable.
- Restart only `web-console-ws.service`: PTY daemon session IDs and PIDs must remain stable.
- Close a split pane: UI tab count and daemon session count must decrease.
- Upload/file manager/voice should still work after reconnect.
- Browser regression should pass against the deployed app before closing auth/reconnect/cursor fixes.
- Theme Gallery should open from the status bar and command palette, preview palettes on hover/focus, apply a curated palette, and reset to `Circadian Auto`.
- Monitoring recipe should create four terminal sessions and render output from `top`, logs, network, and disk panes.
- Workspace rename/duplicate/delete must be reachable on touch devices through the sidebar `...` action button; do not rely only on right-click.
- Server-side browser panel should render a visible frame, accept click/type input, and remain controllable through `webc browser` using the same `browserSessionId`.
- After restarting only `web-console.service`, `ps` should not show leftover `web-console-browser`, Chrome remote-debugging, or session-specific crashpad helper processes.

## Known Gotchas

### Theme Gallery

Theme Gallery builds on `useCircadianTheme`, not the Zustand workspace store. Keep selected palette persistence in `localStorage` (`circadian-theme-palette`) and imported palette persistence in `localStorage` (`circadian-theme-custom-palettes`) unless cross-device theme sync is explicitly designed.

`Circadian Auto` removes inline CSS variable overrides and lets `data-circadian-phase` drive the existing CSS. Fixed palettes write CSS variables to `:root`, set `data-theme-palette`, set `data-terminal-tone`, and dispatch `circadian-theme-change`. Terminal and Monaco theme updates depend on that event path.

Keep palette JSON validation in `src/lib/theme-palettes.ts`; do not import arbitrary CSS text or execute user-provided code. Imported themes should be data-only variable maps.

### Mobile Panes And Recipes

Narrow screens render one split pane at a time. Keep `TerminalPanel`, `MonacoEditorPanel`, and `BrowserPanel` keyed by `tab.id` in `HomeClient.tsx`; otherwise React can reuse stale component state when mobile/split pane selection changes. That breaks recipe `initCommand` behavior and can leave Monitoring panes connected but sitting at a prompt.

`TerminalPanel` must keep `initCommandRef` synchronized with the latest prop before session creation. The Monitoring recipe depends on each pane sending its own command after the PTY session is created.

Sidebar workspace actions must stay touch reachable. Desktop right-click is useful, but mobile users need the visible `...` row action to rename, duplicate, or delete a workspace.

### Server-Side Browser Panel

The browser panel is a server-side Chrome viewport rendered as JPEG frames through the logged-in `/api/browser` endpoint. Mouse, keyboard, paste, wheel, back, forward, and reload actions are sent to the same Chrome session through CDP. Browser tabs persist `browserSessionId` so `webc browser` and the visible UI can inspect and control the same session.

Keep the API split strict: `/api/browser` uses normal app auth for the visible UI and must not expose admin tokens to client JavaScript. `/api/admin/browser` is for scripts/agents, must stay loopback-only, and must require `WEB_CONSOLE_ADMIN_TOKEN` or `JWT_SECRET`.

Browser sessions belong to `web-console.service`. Restarting that service intentionally ends browser sessions, but shutdown must call `closeAllBrowserSessions()`, signal Chrome process groups, and clean session-specific `chrome_crashpad_handler` processes. Do not restart `web-console-pty.service` for browser cleanup; it owns live shells.

This mode is still headless server Chrome. Sites with strong anti-bot, IP reputation, or endless verification checks can fail even when local previews and normal sites work. Treat that as an architecture limitation, not a UI bug. A headed/VNC/WebRTC/residential-proxy design would be a separate phase.

Keep `pnpm test:browser-regression` passing before changing `BrowserPanel.tsx`, `/api/browser`, `/api/admin/browser`, `browser-automation.ts`, or `bin/webc.mjs`.

### Gateway Restart

The daemon/gateway split fixed the original hard blocker: restarting `web-console-ws.service` used to run `ptyManager.destroy()` and kill all shells. The gateway must not import `pty-manager.mjs` directly.

### Ubuntu needrestart

Ubuntu's `needrestart` apt hook can run after unattended package upgrades and
auto-restart services with old libraries mapped. For this project,
`web-console-pty.service` is not a stateless daemon; it owns live `node-pty`
shells and child processes such as Claude Code, editors, `uv`, `node`, and
browser automation.

Install `deploy/needrestart/99-web-console-protect-interactive.conf` to
`/etc/needrestart/conf.d/` on production hosts. The 2026-04-28 incident showed
the failure mode: unattended upgrade restarted `web-console-pty.service`, the
daemon logged `All sessions destroyed`, and systemd SIGKILLed two active
`claude` processes. Treat this guard as part of production install, not an
optional hardening step.

### No tmux Backend

Do not introduce tmux/zellij as a backend for this project. The 2026-04-25 hardening decision explicitly rejected tmux as the default backend and selected raw `node-pty` because the app is meant to preserve Termius/Claude Code terminal semantics: direct PTY process ownership, predictable signal behavior, simple session lifecycle, and no extra terminal multiplexer state hidden underneath the browser UI.

The accepted architecture is raw `node-pty` owned by long-lived `web-console-pty.service`, with `web-console-ws.service` acting only as a restartable authenticated gateway. This guarantees survival across `web-console-ws.service` restarts. It does not guarantee survival across `web-console-pty.service` restarts; that limitation is known and intentional until a separate session backend is designed.

### Trusted Welcome

Do not send a browser `welcome` with an empty cached session list and allow the frontend to treat it as authoritative. That was the source of F5 creating duplicate sessions.

### Manual Relogin

The manual login path is different from the cookie-valid startup path. Keep `LoginPage` in sync with `HomeClient`: after successful password auth, fetch `/api/auth`, call `syncFromServer()`, then call `setAuth(true, wsToken)`.

The 2026-05-01 incident was caused by skipping this server sync. Browser `localStorage` wrote an older two-tab workspace to SQLite at login time, while two other live PTY sessions stayed alive but orphaned in `web-console-pty.service`.

### Split Close Ordering

`SplitContainer` must call close handling before replacing the tree, or the app can no longer find the leaf being closed and the PTY will leak.

### Clipboard

`navigator.clipboard.writeText()` from `onSelectionChange` is best-effort only because browsers require user activation. Ctrl+Shift+C is the reliable copy path.

### Click-To-Cursor

Click-to-cursor across wrapped lines is covered by `pnpm test:browser-regression`. Keep that check passing before changing the terminal click handler.

### Attention Notifications

Terminal attention UI is fed by live OSC output only. Do not replay notifications from scrollback: `scrollback` messages should restore terminal pixels, while `output` messages may create unread tab/workspace/pane badges. Keep OSC 777 browser regression coverage before changing this parser path.

### Session Metadata

Collect live session metadata outside the shell through daemon-side `/proc` reads and bounded `git -C` calls. Do not inject prompt hooks, aliases, or background commands into user PTYs just to learn cwd/git status.

### Recipes

Recipes are loaded from built-ins plus `${WORKSPACE_ROOT}/.web-console.json` through `/api/recipes`, then launched from the browser command palette. Recipe-created browser tabs may set a URL, but keep browser automation actions explicit through `/api/admin/browser` and `webc browser`; do not hide clicks, eval, or screenshots inside recipe launch.

### Local Admin API / webc

The `/api/admin/*` control plane is for localhost scripts and agents only. Keep it token-guarded with `WEB_CONSOLE_ADMIN_TOKEN` or `JWT_SECRET`, deny non-loopback `X-Forwarded-For`, and do not expose it as a public unauthenticated nginx route. `webc` should remain a thin CLI over this API.

### Public Release

Public release baseline:

- Repository visibility is public from clean release commit `42b7ff2`.
- Keep HEAD secret/internal-reference scans clean.
- Keep `.env`, databases, uploads, runtime browser profiles, and local deployment paths out of git.
- Rotate any real local `.env` secrets before publishing forks, support bundles, or new release-history rewrites.
- Re-run fresh clone install/build/run before future public release tags.