Conversation
fdddb0e to
50c535a
Compare
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+`.
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)
b877df7 to
ed979c5
Compare
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
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.
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.
Use a separate lazily-created Valkey connection for CLI commands, isolating interactive traffic from monitoring queries.


Summary
BETTERDB_UNSAFE_CLI=true, all commands)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 limitcli.service.ts— command execution with two-tier filtering (safe/unsafe), 18 blocked commands, 30s timeout, 512 KB response truncationcommand-parser.ts— state machine parser for quoted strings (ported from VS Code extension)cli.module.ts,cli.types.ts— NestJS module and shared typesFrontend (
apps/web/)CliPanel.tsx— collapsible panel with shadcn ScrollArea, Collapsible, Tooltip, SeparatoruseCliWebSocket.ts— WebSocket connection with exponential backoff reconnectionuseCliHistory.ts— ref-based command history with up/down navigation (no re-render cascade)useCliPanel.ts— panel state withsessionStoragepersistence and `Ctrl+`` shortcuthelp,clear,history,exitSecurity hardening
CONFIGwithout subcommand rejected)SLOWLOG RESETremoved from safe modeOther changes
BETTERDB_UNSAFE_CLIenv var in Zod schema + startup warningunsafeCliEnabledin health responsepackages/shared(used by both backend and frontend)/cli/wsdocs/prd/Test plan
pnpm dev→ open browser → click CLI button → typePING→ see"PONG"BETTERDB_UNSAFE_CLI=true pnpm dev→SET foo bar→GET foo→"bar"SUBSCRIBE→ blocked error🤖 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/wsWebSocket, 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 newDatabasePort.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 inmain.tsto 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.