rmycli 0.1.0

A Redis shell with live command help and type-aware completion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# rmycli

A Rust Redis shell with live command help, metadata-driven argument completion,
and type-aware key completion.

## Status

The first interactive implementation is available. It connects to Redis,
executes arbitrary commands, discovers the server's command catalog, shows
syntax and argument help while editing, and completes keys using bounded,
cancellable `SCAN` requests. It also estimates `LRANGE` replies before execution.

Common `redis-cli` connection, scripting, and output options are supported,
including RESP3 and cluster redirects. Cluster-wide discovery, replication/RDB
transfers, and estimates for commands other than `LRANGE` are not implemented.
See **Compatibility and limits** below for metadata and streaming limitations.

## Install and connect

Use Rust 1.88 or newer (the current stable toolchain is recommended).

Once version 0.1.0 is published to crates.io, install it with:

```sh
cargo install rmycli --locked
```

To install from a source checkout instead, run `cargo install --path . --locked`
in the project directory. Then connect to Redis:

```sh
rmycli
rmycli -3
rmycli -h localhost -p 6379 -n 2
rmycli -s /tmp/redis.sock
rmycli -u redis://localhost:6379/2
rmycli -u rediss://redis.example.com:6379/0
REDISCLI_AUTH=secret rmycli --user alice
```

`--tls` enables certificate-verified TLS for `-h`/`-p` connections or upgrades
an existing TCP URI without changing its host or port. System trust roots are
used unless `--cacert FILE` or `--cacertdir DIR` supplies PEM CA certificates.
Use `--cert FILE --key FILE` for a PEM client certificate chain and private key.
These flags also work with `rediss://` URLs and TLS instances. `--insecure`
explicitly disables certificate verification; verification is otherwise on.
A URL supplies connection settings; explicit `-n`, `--user`,
and `-a`/`REDISCLI_AUTH` override its database and credentials. `REDIS_URL` is
also supported. Use `rmycli --help` for all options (`-h` selects the host).

Commands can also be supplied directly or one per line on stdin:

```sh
rmycli PING
rmycli --raw GET greeting
printf 'MULTI\nSET greeting "hello world"\nEXEC\n' | rmycli
rmycli --yes LRANGE big-list 0 -1
```

Command-line arguments pass through as supplied by the operating system.
Interactive and stdin input support single/double quotes, backslash escapes,
and `\xHH` byte escapes. Suggestions round-trip through the same parser, so
spaces, quotes, backslashes, NULs, and non-UTF-8 key bytes remain intact.
Formatted output escapes terminal control sequences. Redirected stdout now
uses raw output by default, like `redis-cli`; `--no-raw` restores formatted
output. `--raw` writes reply bytes verbatim in noninteractive mode. Redis errors
exit noninteractive mode with a nonzero status, with or without `-e`.
Server errors show the server's message without the Redis library's preamble
(for example, `(error) syntax error` in the editor). Errors without a message
show their server error code. Client and connection errors retain their context.

## Familiar redis-cli options

`rmycli --help` groups options by purpose and includes connection, scripting,
TLS, and configuration examples. `-h` selects the host; help is `--help`.
Options precede the command. Once a command starts, subsequent arguments pass
through unchanged, including `-1`, `--raw`, and other option-looking values.
Existing rmycli long names, including `--password`, remain supported.
The names and core semantics follow the [Redis CLI reference][redis-cli].

| Purpose | Supported redis-cli options |
| --- | --- |
| Connection | `-h`, `-p`, `-s`, `-u`, `-n`, `-t` |
| Authentication | `-a`, `--pass`, `--user`, `--askpass`, `--no-auth-warning` |
| Protocol and session | `-2`, `-3`, `--name`, `-c`, `--show-pushes yes/no` |
| TLS | `--tls`, `--cacert`, `--cacertdir`, `--cert`, `--key`, `--insecure` |
| Output | `--raw`, `--no-raw`, `--csv`, `--json`, `--quoted-json`, `-d`, `-D` |
| Scripting | `-x`, `-X`, `--quoted-input`, `-r`, `-i`, `--eval`, `-e` |
| Key listing | `--scan`, `--pattern`, `--quoted-pattern`, `--count` |
| Information | `--help`, `--version`, `-v`, `--verbose` |

```sh
rmycli -3 --name maintenance -h localhost -p 6379
rmycli --askpass --user alice
rmycli --tls --cacert ca.pem --cert client.pem --key client.key
rmycli --json HGETALL account:1
rmycli -r 5 -i 0.5 INCR counter
printf 'hello\n' | rmycli -x SET greeting
rmycli -D '' --raw DUMP key > key.dump
rmycli -X payload RESTORE key2 0 payload REPLACE < key.dump
rmycli --quoted-input GET '"key\x00with-nul"'
rmycli --eval script.lua key1 key2 , arg1 arg2
rmycli --scan --pattern 'user:*' --count 100
```

- RESP2 remains the default. `-3` negotiates RESP3 on connection; `--json`
  and `--quoted-json` also select RESP3 unless `-2` is given. `-2` and `-3`
  together are rejected. Auxiliary connections use RESP2 independently.
- `--pass`/`-a` takes precedence over `REDISCLI_AUTH`; `--askpass` overrides
  every password source. The prompt requires terminal stdin and disables echo;
  it cannot be combined with `-x`/`-X`. Use `REDISCLI_AUTH` with piped input.
  Explicit command-line credentials produce a warning unless suppressed.
- `-t` is a connection timeout in **seconds**, including fractions; `-t 0`
  uses the OS timeout. Without `-t`, the existing `--timeout-ms` default of
  1,000 ms applies. Auxiliary connections stay bounded by `--timeout-ms` even
  with `-t 0`. Commands that intentionally block still have no read timeout.
- `-x` appends all stdin bytes, including trailing newlines, as one argument.
  `-X TAG` replaces the first argument matching TAG and rejects a missing tag
  or replacement of the command name. `--quoted-input` decodes each supplied
  argument separately, including `\xHH` escapes.
- `-r N` repeats a command in noninteractive mode, with `-i` seconds between
  executions. `-r 0` executes nothing; `-r -1` repeats until Ctrl-C.
  `--eval FILE` sends `EVAL`, using the standalone comma to separate keys
  from arguments; without a comma all trailing arguments are keys.
- `-d` separates raw array elements and `-D` separates raw replies. Empty
  delimiters are allowed. JSON preserves valid UTF-8; invalid UTF-8 falls back
  to ASCII byte escapes. `--quoted-json` always represents byte escapes as
  literal JSON strings. Nonfinite doubles become strings and nested errors
  become objects so output remains valid JSON. CSV uses redis-cli's escaped
  strings, rather than RFC 4180 quoting. The live editor always escapes
  terminal control bytes, even with `--raw`; JSON and CSV work in the editor.
- `--show-pushes` controls unsolicited RESP3 messages (default: on for terminal
  stdout). The async session reader receives messages while idle and wakes the
  worker when a message or submitted command arrives. Invalidations appear
  without another command, in bold, while preserving the input and cursor. The
  default interactive format is `-> invalidate: 'key'`, with one line per key, or
  `-> invalidate: (nil)` for a cache-wide invalidation. Keys are escaped safely;
  binary keys use double quotes and byte escapes. JSON, CSV, and noninteractive
  output retain their existing formats. Reading sends no Redis commands and
  also runs during WATCH and MULTI. Hidden pushes are
  still drained; Pub/Sub messages remain visible. Push styling is currently
  fixed. Noninteractive push display remains command driven.
- `--scan` streams one SCAN batch at a time until cursor zero without retaining
  the whole keyspace. Ctrl-C interrupts it; `-i` throttles requests. `--count`
  is a Redis work hint, not a result limit. Like redis-cli, this explicit listing
  may contain duplicates and is not a snapshot. It does not use the bounded
  completion budgets and makes no unique-key-count claim.
- `-c` follows up to five MOVED/ASK redirects, including ASKING before an ASK
  retry. It preserves TLS, authentication, database, protocol, and client name.
  It never retries network failures, or redirects while MULTI/WATCH is active.
  Reconnecting to a redirected node does not restore other connection-local
  state. Discovery and estimates cover only the currently connected node.
  `-c` cannot be combined with `-s`.

### Deliberately omitted and currently unsupported options

Use `redis-cli` for its separate administration and diagnostic tools:
`--cluster` and its sub-options; `--replica`/`--slave`, `--rdb`,
`--functions-rdb`; `--pipe`/`--pipe-timeout`; `--stat`, `--latency*`,
`--intrinsic-latency`, `--lru-test`, `--vset-recall*`; `--bigkeys`, `--memkeys*`,
`--keystats*`, `--hotkeys` and their `--cursor`/`--top` modifiers; and
`--ldb`/`--ldb-sync-mode`. These are distinct workflows beyond the interactive
shell and ordinary command execution.

Address-family selection (`-4`/`-6`), a separate `--sni`, `--tls-ciphers`, and
`--tls-ciphersuites` are also currently unsupported: rmycli does not expose
those transport controls. TLS uses the connection hostname for SNI and rustls
cipher defaults. Unsupported switches before the command fail with a usage
error; they are never accepted as no-ops. Full option parity is
not claimed.

The session uses asynchronous TCP, Unix sockets, or TLS, with the published
Redis 1.8 parser and command encoder; no vendored dependency is required.
A dedicated reader keeps partial frames across waits and feeds a bounded
64-message channel. User commands remain serialized on the same connection,
including protocol changes, transactions, RESP2/RESP3 Pub/Sub, and `MONITOR`.
This raw stream is needed because redis-rs's async multiplexer does not expose
arbitrary streaming replies on an existing connection. Dropping a session
aborts its reader; Ctrl-C still kills the exact server client to cancel a
blocking command. Completion, estimation, and metadata use separate
synchronous connections with timeouts on their worker thread.

## Configuration and saved instances

Settings are read once at startup from the first available file, in this order:

1. `$XDG_CONFIG_HOME/rmycli.toml`, or `~/.config/rmycli.toml` when
   `XDG_CONFIG_HOME` is unset, empty, or relative.
2. `rmycli.toml` in each directory in `$XDG_CONFIG_DIRS`, in order (default:
   `/etc/xdg`). Relative directory entries are ignored.

Use `--config /path/to/settings.toml` to select a file explicitly, or
`--no-config` to use built-in settings. Files are not merged. Missing discovered
files are skipped; a missing explicit file, unreadable file, malformed TOML,
unknown setting, or invalid value causes an error before connecting. No file is
created during normal startup. `--help` and `--version` work without loading
settings.

See [rmycli.dist.toml](rmycli.dist.toml) for a commented reference covering every
supported setting, defaults, color formats, and authentication examples. Copy
it to `~/.config/rmycli.toml` (or `$XDG_CONFIG_HOME/rmycli.toml`); the distribution
file is not loaded automatically.

For a shorter example, create `~/.config/rmycli.toml`:

```toml
[theme]
inline-hint = "dark_grey"

[theme.commands]
GET = "cyan"
LRANGE = "#89b4fa"
XGROUP = "green"
"XGROUP CREATE" = "yellow"

[instances.qa]
host = "redis.qa.example.com"
port = 6379
db = 0
user = "alice"
password-env = "QA_REDIS_PASSWORD"
tls = false
```

Select an instance with `rmycli --instance qa` (also works with a command, such
as `rmycli --instance qa PING`). Authentication happens automatically on connect,
including background completion and reply-estimation connections. Instances
are selected explicitly, not inferred from host/port. Instance names are case
sensitive. An unknown instance is an error.

All instance fields are optional: host and port default to `127.0.0.1:6379`,
database to zero, and TLS to off. Omit `user` for Redis's default user or
password-only authentication. Set `tls = true` for certificate-verified TLS.
Use either `password = "..."` or `password-env = "ENVIRONMENT_VARIABLE"`;
the latter reads the password from that variable when the instance is selected.
A missing password variable is an error unless a higher-priority password is
provided. Protect a file containing passwords with owner-only permissions.

Explicit `-h`, `-p`, `-n`, `--user`, and `-a`/`--pass` override the selected instance's
values. `REDISCLI_AUTH` overrides its password too; `-a` takes precedence over
that environment variable. `--tls` can enable TLS on an instance. `--instance`
cannot be combined with `-u`/`REDIS_URL` or `-s`; unset `REDIS_URL` before
selecting an instance. Without `--instance`, connection behavior is unchanged.
Connection timeouts and discovery/reply limits remain command-line options.

The theme controls the foreground color of inline argument hints. Command
names are case insensitive, with whitespace normalized; a subcommand's color
overrides its parent's color, which overrides `inline-hint`. Matching uses the
resolved metadata command, not key or argument text. Unknown command names
are allowed so the same file can cover module commands on different servers.
Duplicate names after normalization are errors. Popup colors are unchanged.

Colors accept `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`,
`white`, `grey`, `dark_grey`, and the `dark_` variants of red, green, yellow,
blue, magenta, and cyan. Also supported: `reset` (terminal default),
`"#RRGGBB"`, `"rgb_(R,G,B)"`, and `"ansi_(N)"` (components/index 0–255).
For example, `inline-hint = "#ffffff"` selects white; hex digits accept either
case. Keep the quotes because an unquoted `#` starts a TOML comment.
Named colors follow the terminal palette; RGB rendering depends on terminal
support. The built-in hint color remains `dark_grey`.

### Read and edit settings from the command line

Local configuration commands run without connecting to Redis:

```sh
rmycli config get theme.inline-hint
rmycli config set theme.inline-hint cyan
rmycli config set theme.commands.LRANGE '#89b4fa'
rmycli config set 'theme.commands."XGROUP CREATE"' yellow
rmycli config unset theme.commands.LRANGE
rmycli config set instances.qa.host redis.qa.example.com
rmycli config set instances.qa.port 6380
rmycli config set instances.qa.password-env QA_REDIS_PASSWORD
rmycli config get instances.qa.host
```

`get` prints the effective value, including built-in defaults and parent-command
color inheritance. Missing optional settings and unknown instances return an
error. It reads the file's settings, without applying connection flags or
resolving password environment variables. `set` prints the saved value;
password updates print `[redacted]`. An explicit `get` of a stored password
prints it.

For color settings, both `get` and `set` show the value followed by `sample text`
rendered in that color when stdout is a terminal. Redirected output is just the
value and a newline. A nonempty `NO_COLOR` disables automatic previews.
Use `--color always` to force a preview (including when `NO_COLOR` is set), or
`--color never` to suppress it:

```sh
rmycli config get theme.inline-hint --color always
rmycli config get theme.inline-hint --color never
```

Keys use TOML dotted-key syntax; quote a component containing spaces or dots,
as in `'theme.commands."XGROUP CREATE"'` or `'instances."qa.eu".host'`.
String values are literal arguments, requiring only shell quoting. Ports and
databases must be integers; TLS takes `true` or `false`. The complete updated
file must pass validation before it is saved. `unset` removes an explicit value,
restoring its default/inheritance, and fails if that value was not set.

Edits preserve comments and unrelated settings and replace the file atomically.
By default they write `$XDG_CONFIG_HOME/rmycli.toml` (normally
`~/.config/rmycli.toml`), creating directories as needed. If only a system file
exists, its contents are copied into the new user file before editing, so the
other effective settings are retained. System files are not edited implicitly.
`--config PATH`, before or after `config`, selects a specific file; `set` can
create it. Existing symlinks are followed, existing file permissions are retained,
and new files have owner-only permissions on Unix. Changes take effect in new
shell sessions.

Lowercase `rmycli config` is reserved for these local operations. Use uppercase
`rmycli CONFIG GET ...` or `rmycli CONFIG SET ...` for the Redis command.
Interactive Redis commands are unaffected. `rmycli config --help` lists the
local commands; `rmycli --no-config config get ...` reads built-in defaults.

## Live help and editing

On connection, `rmycli` loads [COMMAND][command] and, when available,
[COMMAND DOCS][command-docs]. Remaining arguments appear in grey by default on
the input line, like `redis-cli`: typing `lrange` displays `lrange key start stop`, with
only `key start stop` supplied by the program. Typing the key changes the hint
to `start stop`. Hints never become part of the submitted command. They are
hidden while editing inside the line and clipped at the terminal's right edge.
Redraws are buffered and use synchronized terminal updates where supported,
so clearing hints, printing output, and drawing the next prompt appear together.
F1 opens a dismissible help panel with current command documentation, editor
shortcuts, and connection status. `HELP command` prints full syntax, summary,
version, group, and complexity to the terminal.
Nested alternatives, optional
clauses, repeated arguments, subcommands, and module documentation come from
the server rather than a fixed set of handwritten command signatures.

The complete official Redis command-help catalog is bundled as a fallback.
Live documentation takes precedence, including for module commands. Fallback
help is labeled because it can differ from the connected server's version.
Command names are restricted to the live catalog when `COMMAND` succeeds.

| Key / command | Action |
| --- | --- |
| Tab / Shift-Tab | Start completion / cycle through popup candidates |
| Enter with popup open | Accept the candidate; does **not** execute |
| Enter without popup | Submit the command |
| Ctrl+Enter on `KEYS <pattern>` | Stream matching keys with `SCAN` |
| Hold Ctrl (with modifier-event support) | Show `[Ctrl]` in the prompt |
| Escape | Close the popup or cancel discovery/confirmation |
| Left / Right, Home / End, Ctrl-A / Ctrl-E | Move the cursor |
| Backspace / Delete, Ctrl-U / Ctrl-K | Delete characters or text before/after the cursor |
| Up / Down | Browse in-memory command history |
| F1 | Open or close command help, editor shortcuts, and connection details |
| PageUp / PageDown, Up / Down with help open | Scroll the help panel |
| Enter with help open | Close help without submitting input |
| `CLEAR`, Ctrl-L | Clear the screen locally without sending a Redis command |
| Ctrl-C | Stop alternate `KEYS` scanning; otherwise clear input or close an executing/streaming connection |
| Ctrl-D, `\quit`, `\q`, `QUIT`, `EXIT` | Exit |
| `HELP command`, `HELP @group` | Full command help or commands in a group |
| `HELP`, `\help` | Open editor help and connection details |
| `\reconnect` | Open a new connection and refresh metadata |

Experiment with `KEYS *` or `KEYS foo:*` followed by **Ctrl+Enter** to stream
keys using `SCAN` until cursor zero. The pattern is passed through unchanged:
`KEYS foo:*` uses `SCAN ... MATCH foo:* COUNT 128`. Empty batches do not stop
iteration. Output is streamed with bounded buffering; duplicates are possible
and the listing is not a snapshot. It covers the current database on the
connected node, without completion budgets or a unique-key-count claim.

**Ctrl-C stops further iterations without exiting or disconnecting.** Any
in-flight request finishes first; buffered keys are discarded, and the output
is labeled partial. You can then run another command on the same connection.
Alternate `KEYS` is unavailable during MULTI, WATCH, or streaming sessions.
Normal Enter still sends `KEYS` directly; there is no default-remapping setting
yet. With a completion popup open, either Enter only accepts the candidate.

Ctrl+Enter requires a terminal that distinguishes it from Enter. The editor
requests the Kitty keyboard protocol, including modifier presses and releases.
On supporting terminals, holding either Ctrl key changes `rmycli>` to
`rmycli[Ctrl]>`; releasing it restores the prompt. Modifier events leave the
input, completion popup, and confirmation unchanged. Shift remains available
for typing case-sensitive keys. Ctrl+Enter with a popup open only accepts the
candidate; release events never submit commands.

Terminals or multiplexers without this support may send plain Enter for both,
which executes ordinary `KEYS`. They may also omit standalone Ctrl presses and
releases, so the held-key indicator cannot work there. An ordinary Ctrl chord
alone does not turn on the indicator, since its release may never be reported.

If modified Enter acts like Enter, check the whole terminal path, including
tmux or screen. For example, tmux with `extended-keys off` cannot forward the
distinction. Changing the application shortcut alone cannot fix that. Try a
terminal supporting the Kitty keyboard protocol directly, outside tmux, to
test the held-key indicator. See the [keyboard protocol][keyboard-protocol]
and [tmux modifier-key troubleshooting][tmux-modifiers]. The application does
not modify terminal or tmux configuration.

[keyboard-protocol]: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
[tmux-modifiers]: https://github.com/tmux/tmux/wiki/Modifier-Keys

The prompt starts at the terminal's current cursor row and follows replies,
like a conventional command-line shell. If existing output ends mid-line,
the prompt starts on the next line. There is no startup banner or persistent
connection footer. Startup shows a metadata-loading status only after 200 ms
or when you type, avoiding a flash on fast connections.
The shell uses the normal terminal buffer, so earlier shell output and command
replies remain in your terminal's scrollback, including
after exit. Use your terminal's scrollback controls to review output.

Completion choices appear in a bordered window below the current argument.
The selected choice is highlighted; long menus scroll as you cycle through
them. Short status messages share the input line when it is empty. Help,
completion, and longer messages reserve space below the prompt only when
visible, scrolling as needed at the bottom of the terminal.
Closing a panel removes it and preserves the input. Errors, discovery progress,
and confirmations still appear when needed. The prompt is `rmycli>` for database
zero and `rmycli[N]>` otherwise.

History is bounded to 500 commands and is not saved to disk. Pasting text never
submits a command. Output retention is controlled by your terminal's scrollback
settings; the application does not keep a separate transcript.

## Key completion

For example:

```text
rmycli> LRANGE list:<Tab>
```

starts discovery on an independent connection with:

```text
SCAN 0 MATCH list:* COUNT 128 TYPE list
```

Redis key specifications determine argument positions: fixed indexes,
keyword-based starts, key counts, strides, and ranges. Argument trees help
identify key positions in unfinished commands, including trailing timeouts
and stream IDs that have not been typed yet. Legacy first/last/step metadata
is supported. Unknown or incomplete specifications can fall back to
`COMMAND GETKEYS` when enough of the command has been entered for Redis to
extract its keys; this introspection does not execute the command.

Separate type hints cover lists, hashes, sets, sorted sets, streams, strings,
bitmaps, and HyperLogLogs. Commands accepting multiple types, overwrite
operations, and unknown module types use unfiltered discovery. Existing-key
suggestions never prevent entering a new destination key. If the server
rejects `SCAN TYPE` syntax, discovery retries without that filter and labels
the candidates as unfiltered.

Completion escapes glob metacharacters in the typed prefix before adding
`*`. It preserves key case and bytes, deduplicates batches, and continues past
empty batches until cursor zero or a budget is reached. It never uses `KEYS`.

- Zero matches after a complete scan leaves the input unchanged.
- One match after a complete scan inserts that key.
- Multiple matches open a popup; Tab and Shift-Tab cycle, Enter accepts,
  and Escape closes it.
- Above the display threshold, confirmation is required before showing keys.
- Partial scans say **at least N matches (partial scan)**. Choose `y` to show
  discovered keys, `c` to continue discovery, or Enter/Escape/`n` to cancel.
  A partial scan with one candidate still requires explicit acceptance.

Default limits and their options:

| Limit | Default | Option |
| --- | --- | --- |
| Display confirmation threshold | 100 keys | `--completion-threshold` |
| Retained distinct keys | 10,000 | `--scan-max-keys` |
| Retained raw key bytes | 4 MiB | `--scan-max-bytes` |
| SCAN requests per discovery step | 32 | `--scan-max-batches` |
| Time per discovery step | 750 ms | `--scan-ms` |
| Connection / auxiliary socket timeout | 1,000 ms | `--timeout-ms` |

A continuation permits another threshold-sized page within the hard key/byte
limits. `COUNT` is only a Redis work hint. The client checks limits between
requests; an in-flight request can add up to its socket timeout, and a single
reply must be received before its retained-key budget can be checked. Editing
or pressing Escape cancels further requests; generation IDs discard stale
results. Network work runs outside the terminal input loop.

## Reply-size guidance and session state

Before ordinary `LRANGE` execution, an auxiliary `LLEN` request estimates the
number of elements in the inclusive range, including negative indexes and
bounds. Above `--reply-threshold` (default 10,000), interactive mode asks:

```text
Continue? [y/N] May return about 3000000 elements (not bytes; data can change).
```

Declining leaves the command unexecuted and available for editing. In
noninteractive mode, an oversized estimate causes an error before execution;
`--yes` explicitly bypasses the check. Unknown estimates, unsupported
commands, and failed probes remain unknown and permit manual execution.
These estimates are not memory limits or guarantees.

Discovery and estimation use separate connections with the active database
and credentials. They are paused during `MULTI`, `WATCH`, and streaming modes.
`SELECT` changes are tracked, including successful queued changes at `EXEC`;
`DISCARD` leaves the database unchanged. `AUTH`, `HELLO AUTH`, and `RESET`
update auxiliary connection settings. Probes never enter the user's transaction.
Commands queued inside transactions execute without reply-size confirmation.

Connection errors never trigger automatic command replay. Use `\reconnect`
explicitly; reconnecting starts a fresh session and does not restore transaction,
WATCH, subscription, or other connection-local state. Ctrl-C during execution
uses `CLIENT ID` and `CLIENT KILL ID` through the auxiliary connection to close
only this client's socket. If ACLs forbid cancellation, Ctrl-D exits the client.
Closing the socket cannot undo a command the server has already executed.

## Compatibility and limits

- Redis 7+ provides the richest metadata. Integration tests run against Redis
  8.0.2. Older metadata and ACL-restricted introspection use labeled bundled
  documentation and available legacy fields; they are tested with fixtures.
- A module must expose key specifications or argument documentation for
  completion of an unfinished command. `COMMAND GETKEYS` requires sufficient
  arguments and permission. Missing metadata never prevents manual submission.
- Some partial commands are intrinsically ambiguous: after `XREAD STREAMS k`,
  the next word could be another key or the first ID. Suggestions are advisory,
  not command validation. Unrecognized option ordering may reduce argument hints.
- A scan describes only the connected node and database. `-c` follows command
  redirects; slot-map routing and cluster-wide discovery are not implemented.
  Even a completed SCAN is not a snapshot of a changing keyspace.
- Pub/Sub and MONITOR replies stream to the transcript. In interactive streaming
  mode, Ctrl-C ends the stream; reconnect before submitting further commands.
  Replication protocols and an interactive unsubscribe workflow are not provided.
- History is bounded and displayed output is released by the application, but
  command replies are decoded in memory before display. The `LRANGE` estimate
  is not a general reply-memory cap.
- No persistent history, SSH tunnels, or full `redis-cli` option compatibility
  is claimed by this version. See the option compatibility section above.

## Development

The code separates byte parsing, command metadata and argument grammar, type
hints, scan discovery, estimation, session transport, workers, and the terminal.
Dependencies are `redis` for RESP and auxiliary connections, `tokio` for async
session I/O, and `combine` for the decoder state required by Redis's public
async parser. `tokio-rustls`, `rustls`, and `rustls-native-certs` provide TLS,
its crypto provider, and system trust roots. `crossterm` handles terminal I/O,
`clap` handles options, `serde_json` handles metadata, and `unicode-width`
handles cursor/layout calculations. `toml` and `serde` parse typed configuration
and reject unknown fields and invalid values;
`xdg` resolves configuration directories. Crossterm's Serde support validates
color values. `toml_edit` preserves comments during CLI edits, and `tempfile`
provides temporary files for atomic replacement.

Install `redis-server` for the isolated integration fixtures, which start their
own Unix-socket servers with persistence disabled. Tests never use an existing
Redis database. Before every commit, including documentation-only commits:

```sh
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
```

To exercise the actual executable, terminal editing, completion acceptance,
cancellation, streaming, and noninteractive policy, also install `redis-cli`
and Python 3, then run:

```sh
cargo build --locked
python3 tests/terminal_smoke.py
```

Use the 80-column `rustfmt.toml` configuration and keep `Cargo.lock` tracked.
See [AGENTS.md](AGENTS.md), [CHANGELOG.md](CHANGELOG.md), and the bundled help's
[attribution and license](data/README.md).

### Publishing

For your first release, sign in to [crates.io](https://crates.io), verify your
email address, create an API token with permission to publish, and run
`cargo login`. Paste the token at Cargo's prompt.

Before publishing, move the release's changelog entries from `Unreleased` into
a version heading dated `YYYY-MM-DD`, retaining an `Unreleased` section. Run the
formatting, Clippy, and test checks above and commit the release changes. Then:

```sh
cargo package --list
cargo publish --dry-run --locked
cargo publish --locked
```

Review the package file list before uploading. The manifest explicitly includes
source, bundled help and its license, tests, and project documentation; local
recordings are excluded. The dry run builds the packaged source without
uploading it. After a successful publish, tag the release commit (for example,
`git tag v0.1.0`). A published version cannot be overwritten; corrections need
a new version. See the [Cargo publishing guide][publishing] for details.

## License

The application source code is licensed under the [MIT License](LICENSE).
The bundled Redis command documentation in `data/commands.json` is separately
licensed under CC BY-SA 4.0; see its [attribution and license](data/README.md).

[publishing]: https://doc.rust-lang.org/cargo/reference/publishing.html

[command]: https://redis.io/docs/latest/commands/command/
[command-docs]: https://redis.io/docs/latest/commands/command-docs/

[redis-cli]: https://redis.io/docs/latest/develop/tools/cli/