Skip to content

feature: integrated CLI panel (#91)#96

Merged
KIvanow merged 30 commits intomasterfrom
feature/91-cli-support
Apr 6, 2026
Merged

feature: integrated CLI panel (#91)#96
KIvanow merged 30 commits intomasterfrom
feature/91-cli-support

Conversation

@jamby77
Copy link
Copy Markdown
Collaborator

@jamby77 jamby77 commented Apr 3, 2026

Summary

  • Adds a WebSocket-based CLI panel to the Monitor UI for running Valkey/Redis commands directly from the browser
  • Two modes: safe (read-only allowlist, default) and unsafe (BETTERDB_UNSAFE_CLI=true, all commands)
  • Backend: WebSocket gateway with dedicated Valkey client per connection, command parser, response formatter, rate limiter, auth
  • Frontend: collapsible bottom panel with shadcn components, command history, auto-reconnecting WebSocket, keyboard shortcuts

Closes #92, closes #93, closes #94

What's included

Backend (apps/api/src/cli/)

  • cli.gateway.ts — WebSocket upgrade handler with token-bucket rate limiter (50 cmd/s), cloud-mode JWT auth, 1 MiB payload limit
  • cli.service.ts — command execution with two-tier filtering (safe/unsafe), 18 blocked commands, 30s timeout, 512 KB response truncation
  • command-parser.ts — state machine parser for quoted strings (ported from VS Code extension)
  • cli.module.ts, cli.types.ts — NestJS module and shared types

Frontend (apps/web/)

  • CliPanel.tsx — collapsible panel with shadcn ScrollArea, Collapsible, Tooltip, Separator
  • useCliWebSocket.ts — WebSocket connection with exponential backoff reconnection
  • useCliHistory.ts — ref-based command history with up/down navigation (no re-render cascade)
  • useCliPanel.ts — panel state with sessionStorage persistence and `Ctrl+`` shortcut
  • Built-in commands: help, clear, history, exit

Security hardening

  • SENTINEL removed from safe-mode allowlist (destructive subcommands)
  • Bare subcommand bypass fixed (e.g., CONFIG without subcommand rejected)
  • SLOWLOG RESET removed from safe mode
  • Cloud-mode JWT session cookie validation on WebSocket upgrade
  • 30s command timeout, 512 KB response truncation, 1 MiB WS payload limit
  • Token-bucket rate limiter per WebSocket connection

Other changes

  • BETTERDB_UNSAFE_CLI env var in Zod schema + startup warning
  • unsafeCliEnabled in health response
  • CLI types in packages/shared (used by both backend and frontend)
  • Cloud auth middleware updated to whitelist /cli/ws
  • PRD docs in docs/prd/

Test plan

  • Backend: 44 tests (command parser: 11, CLI service: 33 — blocked commands, safe/unsafe mode, subcommand enforcement, response formatting, credential checks)
  • Frontend: 13 tests (useCliHistory: 9, CliPanel: 4)
  • TypeScript: zero errors (backend + frontend)
  • Manual: pnpm dev → open browser → click CLI button → type PING → see "PONG"
  • Manual: BETTERDB_UNSAFE_CLI=true pnpm devSET foo barGET foo"bar"
  • Manual: Try SUBSCRIBE → blocked error
  • Manual: `Ctrl+`` toggles panel, up/down arrows navigate history

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new WebSocket command-execution surface area and a call() API on database adapters, which is security- and stability-sensitive despite safe-mode/blocked-command guards, rate limiting, timeouts, and output truncation.

Overview
Adds an integrated CLI that lets the web UI execute Valkey/Redis commands via a new /cli/ws WebSocket, with per-client FIFO execution, token-bucket rate limiting, 30s timeouts, and response formatting/truncation.

Introduces BETTERDB_UNSAFE_CLI (default false) to control safe vs unsafe command filtering, centralizes allow/deny logic in @betterdb/shared (checkSafeMode/checkBlocked), and updates database adapters/agent plumbing to support dedicated CLI connections via a new DatabasePort.call(..., { cli }) API.

Adds a collapsible frontend CliPanel (toggle + Ctrl+` shortcut) with command history and auto-reconnecting WebSocket hook, plus supporting shadcn/radix UI primitives and tests; also unifies HTTP upgrade handling in main.ts to route both CLI and agent WebSockets.

Reviewed by Cursor Bugbot for commit acfff20. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/web/src/components/CliPanel.tsx
Comment thread apps/api/src/cli/cli.gateway.ts
Comment thread apps/web/src/components/CliPanel.tsx Outdated
Comment thread apps/web/src/hooks/useCliWebSocket.ts
Comment thread apps/api/src/cli/cli.service.ts Outdated
Comment thread apps/api/src/cli/cli.gateway.ts
Comment thread apps/api/src/cli/cli.gateway.ts Outdated
Comment thread apps/api/src/cli/cli.service.ts Outdated
Comment thread apps/web/src/components/ui/scroll-area.tsx Outdated
Comment thread apps/api/src/cli/cli.gateway.ts
Comment thread apps/web/src/components/CliPanel.tsx Outdated
@jamby77 jamby77 requested review from KIvanow April 3, 2026 13:31
@jamby77 jamby77 force-pushed the feature/91-cli-support branch from fdddb0e to 50c535a Compare April 3, 2026 13:34
Comment thread apps/web/src/components/CliPanel.tsx Outdated
Comment thread packages/shared/src/types/command-safety.ts
Comment thread apps/web/src/components/ui/resizable.tsx Outdated
Comment thread apps/web/package.json Outdated
jamby77 added 18 commits April 4, 2026 10:47
Adds a WebSocket-based CLI panel to the Monitor UI that lets users
run Valkey/Redis commands directly from the browser. The CLI operates
in two modes: safe (read-only allowlist, default) and unsafe
(all commands, via BETTERDB_UNSAFE_CLI=true env var).

Backend:
- WebSocket gateway at /cli/ws with dedicated Valkey client per connection
- Command parser ported from VS Code extension (quoted strings, escapes)
- Two-tier command filtering: safe mode allowlist + always-blocked list
- Response formatting matching valkey-cli output style
- 30s command timeout, 512KB response truncation, 1MiB WS payload limit
- Token-bucket rate limiter (50 cmd/s per connection)
- Cloud-mode session cookie auth on WS upgrade

Frontend:
- Collapsible bottom panel using shadcn Collapsible, ScrollArea, Tooltip
- Command history with up/down arrow navigation (ref-based, no re-renders)
- Auto-reconnecting WebSocket hook with exponential backoff
- Built-in commands: help, clear, history, exit
- Ctrl+` keyboard shortcut to toggle panel

Closes #92, closes #93, closes #94
- Replace single pendingCommandRef with a queue to support rapid commands
- Reference-count CLI Valkey clients so multi-tab usage doesn't kill
  shared connections on single WS disconnect
- Remove non-functional ScrollArea wrapper (inner div handled overflow)
- Set isConnected=false immediately on connectionId change reconnect
- Prevent duplicate Valkey client creation from concurrent WS messages
  using an inflight connection promise map
- Chain command execution per WS client to guarantee FIFO response
  order, preventing out-of-order response matching
- Fix cookie parser truncating JWT values containing '=' signs
…eware

The project's CloudAuthMiddleware already runs on all HTTP requests
including WebSocket upgrades. Removed duplicated JWT validation and
cookie parsing from CliGateway, and removed /cli/ws from the
middleware's whitelist so it properly checks auth in cloud mode.
- CLI Valkey client now respects the connection's TLS setting
- Deleted unused scroll-area.tsx (ScrollArea was removed in earlier fix)
Instead of hand-rolling Valkey client creation, the CLI now uses
the registry's createAdapter() factory with connectionName 'BetterDB-CLI'.
This reuses existing connection config handling (TLS, credentials,
dbIndex) and follows the project's adapter pattern.

Made createAdapter() public and extended UnifiedDatabaseAdapterConfig
to accept optional connectionName, tls, and dbIndex.
The onOpenChange handler was only calling onClose, so clicking the
trigger bar couldn't open the panel. Now passes onToggle to Collapsible.
Replaced repetitive individual test cases with it.each tables in both
command-parser and cli.service specs. Same coverage, less boilerplate.
Moved ALLOWED_COMMANDS, ALLOWED_SUBCOMMANDS, BLOCKED_COMMANDS, and
BLOCKED_SUBCOMMANDS to packages/shared/src/types/command-safety.ts.
Both the cloud agent (packages/agent) and CLI service now import
from the same source, eliminating drift risk.

CLI safe mode uses the exact same allowlist as the agent.
… cli.service

Clarifies the purpose of the `BETTERDB_UNSAFE_CLI` variable with a schema description and reorganizes `MAX_RESPONSE_SIZE` placement for readability.
These are out of scope for the CLI feature. The only change needed
was adding the optional connectionName parameter. TLS and dbIndex
support for the adapter is a separate concern.
Not used by the frontend and leaks security config to public endpoint.
Removed field from HealthResponse type, ConfigService injection from
HealthService.
…header

Connection info is already visible in the sidebar, and the green dot
on the trigger bar shows connection status.
Redundant — clear is available via 'clear' command / Ctrl+L, and
close via trigger bar click / Ctrl+`.
jamby77 added 7 commits April 4, 2026 10:47
Added a drag handle at the top of the panel. Users can drag to resize
between 200px and 70% of viewport height. Default is 30vh.
valkey-cli uses quotes to distinguish types, but in the web UI
it looks wrong. Strings are now returned as-is.
- Changed auto-scroll dependency from entries.length to entries ref
  so it fires even when length stays at MAX_ENTRIES (500)
- Deleted unused resizable.tsx (custom drag handle is used instead)
@jamby77 jamby77 force-pushed the feature/91-cli-support branch from b877df7 to ed979c5 Compare April 4, 2026 07:47
jamby77 added 3 commits April 6, 2026 10:43
ConfigService returns raw env strings, so comparing against boolean true
always evaluated to false. Use string comparison consistent with other
boolean env vars in configuration.ts.
The ConfigService mock was returning boolean true, but the service now
compares against string 'true' to match raw env var behavior.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes here are wrong. The goal was to keep the whitelist and ignore it only if an environment variable/flag is used when the agent starts up, so it is secure by default and then only via explicit change allow the rest of the commands.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no funcitonal changes here, besides moving the allowed commands to shared package. The behavior is the same. Nothing is changing for the agent, regardless of of startup flag. Should the same logic apply as for the CLI?

- Move checkBlocked/checkSafeMode functions to @betterdb/shared
- CLI service uses connectionRegistry.get() instead of creating adapters
- Add call() to DatabasePort interface for generic command execution
- Agent supports BETTERDB_UNSAFE_CLI with lazy CLI client connection
- CLI commands tagged with cli flag in WebSocket protocol
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Agent CONFIG handler hardcodes GET, breaks unsafe mode
    • The CONFIG-specific fast path now only handles CONFIG GET so other CONFIG subcommands correctly fall through to generic execution in unsafe mode.

Create PR

Or push these changes by commenting:

@cursor push b5ecd77e73
Preview (b5ecd77e73)
diff --git a/packages/agent/src/command-executor.ts b/packages/agent/src/command-executor.ts
--- a/packages/agent/src/command-executor.ts
+++ b/packages/agent/src/command-executor.ts
@@ -55,7 +55,7 @@
       return this.client.lastsave();
     }
 
-    if (upperCmd === 'CONFIG' && args) {
+    if (upperCmd === 'CONFIG' && args && args.length > 0 && args[0].toUpperCase() === 'GET') {
       return this.client.config('GET', ...args.slice(1));
     }

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 98bf8d1. Configure here.

Comment thread packages/agent/src/command-executor.ts
@jamby77 jamby77 requested a review from KIvanow April 6, 2026 13:27
Use a separate lazily-created Valkey connection for CLI commands,
isolating interactive traffic from monitoring queries.
@KIvanow KIvanow merged commit a632e97 into master Apr 6, 2026
3 checks passed
@KIvanow KIvanow deleted the feature/91-cli-support branch April 6, 2026 14:50
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI: Frontend panel component CLI: WebSocket gateway and command execution CLI: BETTERDB_UNSAFE_CLI env var for unrestricted command mode

2 participants