Debug command. (#23851)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Jacob Richman
2026-03-27 14:05:22 -07:00
committed by GitHub
parent ba71ffa736
commit ebe98fdee9
4 changed files with 153 additions and 21 deletions

View File

@@ -51,10 +51,11 @@ gemini.tsx / nonInteractiveCli.ts
## API Endpoints
| Endpoint | Method | Description |
| --------- | --------- | --------------------------------------------------------------------------- |
| `/ws` | WebSocket | Log ingestion from CLI sessions (register, network, console) |
| `/events` | SSE | Pushes snapshot on connect, then incremental network/console/session events |
| Endpoint | Method | Description |
| ----------------------- | --------- | --------------------------------------------------------------------------- |
| `/ws` | WebSocket | Log ingestion from CLI sessions (register, network, console) |
| `/events` | SSE | Pushes snapshot on connect, then incremental network/console/session events |
| `/api/trigger-debugger` | POST | Triggers the Node.js debugger for a specific CLI session via WebSocket |
## Development

View File

@@ -39,6 +39,21 @@ export default function App() {
null,
);
// --- Toast Logic ---
const [toastMessage, setToastMessage] = useState<string | null>(null);
const toastTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const showToast = (msg: string) => {
setToastMessage(msg);
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
toastTimeoutRef.current = setTimeout(() => {
setToastMessage(null);
toastTimeoutRef.current = null;
}, 5000);
};
// --- Theme Logic ---
const [themeMode, setThemeMode] = useState<ThemeMode>(() => {
const saved = localStorage.getItem('devtools-theme');
@@ -306,21 +321,52 @@ export default function App() {
>
{selectedSessionId &&
connectedSessions.includes(selectedSessionId) && (
<button
onClick={handleExport}
style={{
fontSize: '11px',
padding: '4px 8px',
border: `1px solid ${t.border}`,
background: t.bg,
color: t.text,
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 600,
}}
>
📤 Export
</button>
<>
<button
onClick={async () => {
try {
await fetch('/api/trigger-debugger', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId: selectedSessionId }),
});
showToast(
'Node debugger attached. Open chrome://inspect in Chrome to start debugging.',
);
} catch (e) {
console.error('Failed to trigger debugger:', e);
}
}}
style={{
fontSize: '11px',
padding: '4px 8px',
border: `1px solid ${t.border}`,
background: t.bg,
color: t.text,
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 600,
}}
title="Attach Node Debugger and open chrome://inspect"
>
🐞 Debug Node
</button>
<button
onClick={handleExport}
style={{
fontSize: '11px',
padding: '4px 8px',
border: `1px solid ${t.border}`,
background: t.bg,
color: t.text,
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 600,
}}
>
📤 Export
</button>
</>
)}
<label
@@ -487,6 +533,38 @@ export default function App() {
</div>
)}
</div>
{/* Toast Notification */}
{toastMessage && (
<div
style={{
position: 'fixed',
bottom: '24px',
right: '24px',
background: t.accent,
color: '#fff',
padding: '12px 24px',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
fontSize: '13px',
fontWeight: 500,
zIndex: 1000,
animation: 'fadeInOut 5s ease forwards',
}}
>
{toastMessage}
</div>
)}
{/* CSS Animations */}
<style>{`
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(10px); }
5% { opacity: 1; transform: translateY(0); }
95% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(10px); }
}
`}</style>
</div>
);
}

View File

@@ -177,7 +177,41 @@ export class DevTools extends EventEmitter {
}
// API routes
if (req.url === '/events') {
if (req.url === '/api/trigger-debugger' && req.method === 'POST') {
let body = '';
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
try {
const parsed: unknown = JSON.parse(body);
if (
typeof parsed !== 'object' ||
parsed === null ||
!('sessionId' in parsed) ||
typeof parsed.sessionId !== 'string'
) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid request' }));
return;
}
const sessionId = parsed.sessionId;
const session = this.sessions.get(sessionId);
if (session) {
session.ws.send(JSON.stringify({ type: 'trigger-debugger' }));
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Session not found' }));
}
} catch (_err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid request' }));
}
});
} else if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',