Lines 17-57javascript
17// Covers the plain keys (Up, Tab, …) AND modifier combos on them — C-Up, M-Up, S-Left, C-Tab, C-S-Up, … —
18// so the 按键 editor can bind e.g. Ctrl+Arrow or Ctrl+Tab, not just Ctrl + a letter.
19const NAMED_KEY = new RegExp(`^(?:C-)?(?:M-)?(?:S-)?(?:${NAMED})$`);
20// Ctrl and/or Alt + one letter or digit — C-r, C-a, M-b, C-M-k. Must carry a modifier (never a bare char;
21// Shift+letter folds to the uppercase character, so no S- here).
22const CHAR_KEY = /^(?:C-)?(?:M-)?[a-z0-9]$/;
23export function isAllowedKey(k) {
24 if (typeof k !== 'string') return false;
25 return NAMED_KEY.test(k) || (CHAR_KEY.test(k) && k.includes('-'));
28// Pause between typing the text and pressing Enter on a /send. A TUI like Claude Code needs a
29// beat to ingest the pasted line; without it, the Enter can fold into the input as a newline
30// instead of submitting. 120ms is imperceptible but enough to settle.
31const SUBMIT_GAP_MS = 120;
32const delay = (ms) => new Promise((r) => setTimeout(r, ms));
34export function terminalRoutes({ commands }) {
35 const r = express.Router();
37 r.get('/history', async (req, res, next) => {
38 if (!isPaneId(req.query.pane)) return res.status(400).json({ error: 'bad pane id' });
39 const lines = Math.min(Math.max(Number(req.query.lines) || 1000, 1), 5000);
LowWeak Crypto
Package source references weak cryptographic algorithms.
src/routes/terminal.jsView on unpkg · L37 41 // Read the pane's state FIRST — whether it's on the alternate screen decides how we capture it.
42 const { width, height, cursorX, cursorY, cursorVisible, altScreen, mouseAware } = await commands.paneInfo(req.query.pane);
43 // An ALT-screen pane (a full-screen app: vim/less/htop) is a fixed height×width screen with NO
44 // scrollback. Asking tmux for history (-S -lines) there bleeds the MAIN screen's scrollback in
45 // ABOVE the app — you'd scroll up into terminal history that isn't the app's — and capping its
46 // trailing blank rows mangles the fixed screen (those blanks are real cells). So capture an alt
47 // pane as exactly its visible screen (lines=0) and skip the blank-trim. A normal pane still pulls
48 // `lines` of scrollback and caps the empty grid below the cursor (fresh shell = "prompt + a wall of
49 // blank rows") so the phone's bottom-anchored render shows content, not blank. See trimCapture.js.
50 const raw = await commands.capturePane(req.query.pane, altScreen ? 0 : lines);
51 const ansi = altScreen ? raw : capTrailingBlankRows(raw);
52 // The cursor's row counted from the BOTTOM of the (trimmed) capture. The live screen is the
53 // capture's last `height` rows, so the cursor sits `height-1-cursorY` rows above the bottom —
54 // less however many trailing blank rows capTrailingBlankRows dropped (all of them below the
55 // cursor). The client re-places xterm's cursor this many rows up from the seed's last row.
56 const rowsOf = (s) => (s.endsWith('\n') ? s.slice(0, -1) : s).split('\n').length;