MCPcopy
hub / github.com/garrytan/gstack / sendCommand

Function sendCommand

browse/src/cli.ts:527–617  ·  view source on GitHub ↗
(state: ServerState, command: string, args: string[], retries = 0)

Source from the content-addressed store, hash-verified

525
526// ─── Command Dispatch ──────────────────────────────────────────
527async function sendCommand(state: ServerState, command: string, args: string[], retries = 0): Promise<void> {
528 // Precedence: CLI --tab-id flag > BROWSE_TAB env var.
529 // make-pdf always passes --tab-id; human users typically rely on BROWSE_TAB
530 // (set by sidebar-agent per-tab) or the active tab.
531 const extracted = extractTabId(args);
532 args = extracted.args;
533 const envTab = process.env.BROWSE_TAB;
534 const tabId = extracted.tabId ?? (envTab ? parseInt(envTab, 10) : undefined);
535 const body = JSON.stringify({ command, args, ...(tabId !== undefined && !isNaN(tabId) ? { tabId } : {}) });
536
537 try {
538 const resp = await fetch(`http://127.0.0.1:${state.port}/command`, {
539 method: 'POST',
540 headers: {
541 'Content-Type': 'application/json',
542 'Authorization': `Bearer ${state.token}`,
543 },
544 body,
545 signal: AbortSignal.timeout(30000),
546 });
547
548 if (resp.status === 401) {
549 // Token mismatch — server may have restarted
550 console.error('[browse] Auth failed — server may have restarted. Retrying...');
551 const newState = readState();
552 if (newState && newState.token !== state.token) {
553 return sendCommand(newState, command, args);
554 }
555 throw new Error('Authentication failed');
556 }
557
558 const text = await resp.text();
559
560 if (resp.ok) {
561 process.stdout.write(text);
562 if (!text.endsWith('\n')) process.stdout.write('\n');
563 } else {
564 // Try to parse as JSON error
565 try {
566 const err = JSON.parse(text);
567 console.error(err.error || text);
568 if (err.hint) console.error(err.hint);
569 } catch {
570 console.error(text);
571 }
572 process.exit(1);
573 }
574 } catch (err: any) {
575 if (err.name === 'AbortError') {
576 // #1781: a 30s timeout on a heavy page usually means busy, not dead.
577 // Don't kill a live server (that's what triggered the crash-loop) — report
578 // and exit so the user can retry rather than losing their (headed) window.
579 const ts = readState();
580 const alive = ts?.pid ? isProcessAlive(ts.pid) : false;
581 console.error(alive
582 ? '[browse] Command timed out after 30s (server still alive — busy, not restarting). Retry, or raise load.'
583 : '[browse] Command timed out after 30s');
584 process.exit(1);

Callers 1

mainFunction · 0.85

Calls 9

isProcessAliveFunction · 0.90
extractTabIdFunction · 0.85
probeHealthWithBackoffFunction · 0.85
killServerFunction · 0.85
buildRestartEnvFunction · 0.85
startServerFunction · 0.85
fetchFunction · 0.70
readStateFunction · 0.70
textMethod · 0.45

Tested by

no test coverage detected