veks-completion 1.6.1

Dynamic shell completion engine for CLI tools
Documentation
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
# veks-completion

Dynamic shell completion engine for Rust CLI tools, plus a companion
argv parser, help renderer, and tap-cadence model.

Zero non-`std` dependencies. One `CommandTree` declaration drives tab
completion, argv parsing, and `--help` rendering — single source of
truth for every CLI surface.

## What's in here

- **Tab completion** — context-aware candidates, value providers,
  per-flag aliases, dynamic option discovery.
- **Multi-tap rotating tiers** — single tap shows the primary
  commands; rapid follow-up taps reveal cumulative supersets in
  layer-ordered display.
- **Argv parser**`parse_argv(tree, &argv) → ParsedCommand` walks
  the same tree the completer uses to produce a structured parse
  (path, flags, positionals).
- **Help rendering**`render_usage(node, path)` formats a `--help`
  block from the tree.
- **Closed value sets**`ClosedValues` produces both completion
  and validation from one declaration.
- **Subtree-aware completion** — context-sensitive providers can
  take over completion inside any subtree with a structured
  `PartialParse`.
- **Free-form attachment**`Extras` slot lets embedders carry
  handler payloads, parser state, dispatch rules without forcing the
  crate to grow generics.
- **Directive sets** — bundle every surface a flag appears in
  (CLI form, help, value set, repeatability, optional YAML mirror)
  into one declaration; expand a slice of them onto a node in one
  call.

## Why not clap_complete?

Clap provides `clap_complete` for generating static shell completion
scripts. It works well for simple CLIs with a fixed command tree, but
breaks down for tools with dynamic commands, multi-level subgroups, or
context-sensitive value completion. This crate was built for the veks
CLI but is fully generic and reusable. The specific limitations it
addresses:

### 1. Dynamic pipeline commands

Veks has a `pipeline` command group whose subcommands are registered
at runtime from a `CommandRegistry`, not from clap's derive macros.
Clap's completion generator only sees the derive-defined tree.

### 2. Shorthand dispatch

`veks run` is shorthand for `veks prepare run`; `veks config` for
`veks datasets config`. These are hidden top-level subcommand aliases.
Clap either hides them entirely (no completions) or shows them
alongside the originals (duplicates). veks-completion uses tap-tier
levels: hidden shortcuts get `level=2` and surface only on rapid
double-tap.

### 3. One-level-at-a-time completion with rotating tiers

Clap eagerly chains subcommands. veks-completion completes one level
at a time. Within a level, rapid double-tap reveals the next tier as
a cumulative superset, layered in display order so primary commands
always appear first.

### 4. Context-sensitive value completion

Per-option `ValueProvider` functions run arbitrary logic. `ClosedValues`
turns a static value set into a provider with one declaration that's
also queryable by parsers for validation.

### 5. Mixed derive + dynamic tree

The completion engine walks the augmented `clap::Command` tree at
completion time, so it always reflects the actual command structure
regardless of how commands were registered.

## How it works

`Node` is a single struct that carries everything a CLI-tree node *can*
have: subcommand children, flags (value-taking + boolean), value
providers, discovery metadata (category + level), help text, plus
hooks (subtree provider, free-form extras). A node with no children is
"leaf-shaped"; with children it's "group-shaped"; with both it's
hybrid (e.g. a group that itself accepts cross-cutting flags).

A `CommandTree` is a root node + globals. At startup an embedder builds
the tree once. At completion time the engine walks the tree following
the user's typed words and returns candidates.

The bash script the engine emits is minimal — it just calls back into
the binary. Completions never get out of sync with the installed
binary because the binary itself produces them.

## Usage — tab completion

```rust
use veks_completion::{CommandTree, Node, handle_complete_env};

let tree = CommandTree::new("myapp")
    .command("run", Node::leaf_with_flags(
        &["--input", "--output", "--threads"],
        &["--verbose", "--dry-run"],
    ))
    .group("compute", Node::group(vec![
        ("knn",   Node::leaf(&["--base", "--query", "--metric"])),
        ("stats", Node::leaf(&["--input"])),
    ]));

// In main(), before argument parsing:
if handle_complete_env("myapp", &tree) {
    std::process::exit(0);
}

// And register a `completions` subcommand that prints the bash
// activation snippet for `eval "$(myapp completions)"`.
```

## Usage — argv parsing

The same `CommandTree` produces structured parses:

```rust
use veks_completion::{parse_argv, ParsedCommand};

let parsed = parse_argv(&tree, &[
    "compute", "knn", "--metric", "L2", "--verbose", "data.fvecs",
])?;
assert_eq!(parsed.path,        vec!["compute", "knn"]);
assert_eq!(parsed.flags["--metric"],  vec!["L2".to_string()]);
assert_eq!(parsed.flags["--verbose"], vec!["".to_string()]); // boolean
assert_eq!(parsed.positionals, vec!["data.fvecs"]);
```

`parse_argv_lenient` treats unknown flags as positionals — useful for
pass-through CLIs.

## Usage — help rendering

```rust
use veks_completion::render_usage;

let leaf = Node::leaf_with_flags(&["--metric"], &["--verbose"])
    .with_help("Compute KNN over base vectors")
    .with_flag_help("--metric",  "Distance metric: L2 / IP / COSINE")
    .with_flag_help("--verbose", "Print per-step progress");

println!("{}", render_usage(&leaf, &["myapp", "compute", "knn"]));
// → USAGE: myapp compute knn
//
//   Compute KNN over base vectors
//
//   FLAGS:
//     --metric   Distance metric: L2 / IP / COSINE
//     --verbose  Print per-step progress
```

## Usage — closed value sets (completion + validation)

```rust
use veks_completion::{ClosedValues, Node};

let metrics = ClosedValues::Static(&["L2", "IP", "COSINE"]);
assert!(metrics.validate("L2"));
assert!(!metrics.validate("bogus"));
assert_eq!(metrics.complete("CO"), vec!["COSINE"]);

let leaf = Node::leaf(&["--metric"])
    .with_value_provider("--metric", metrics.clone().into_provider());
```

## Usage — directive sets

When you have many flags that share a vocabulary shape, declare them
once as a `&[Directive]` and apply onto any node:

```rust
use veks_completion::{Directive, apply_directives, Node};

const DIRS: &[Directive] = &[
    Directive::closed("--metric", &["L2", "IP", "COSINE"])
        .with_help("Distance metric"),
    Directive::value("--name").with_help("Run name"),
    Directive::boolean("--verbose").with_help("Verbose output"),
];

let leaf = apply_directives(Node::leaf(&[]), DIRS);
// → flags + flag-help + value-providers + validation, all wired.
```

## Usage — multi-tap rotating tiers

Tag commands with `with_level(n)` to control which tap-tier reveals
them:

```rust
let tree = CommandTree::new("nbrs")
    .command("run",          Node::leaf(&[]).with_level(1))
    .command("--inspector",  Node::leaf(&[]).with_level(2))
    .command("--summary",    Node::leaf(&[]).with_level(2))
    .command("describe",     Node::leaf(&[]).with_level(3))
    .command("bench",        Node::leaf(&[]).with_level(3));
```

Behavior:

- **Tap 1 (cold or after pause):** layer 1 only — `run`
- **Tap 2 (within 200 ms):** cumulative layers 1+2 — `run`,
  `--inspector`, `--summary`
- **Tap 3 (within 200 ms):** cumulative layers 1+2+3 — all five.
  At max, the persistent state resets, so a fourth rapid tap
  cycles back to layer 1.
- **Pause > 200 ms:** any tap starts fresh at layer 1.
- **Within each tap's result:** sorted in *layer order* (layer 1
  first, then layer 2, …); within a layer, `--`-flags last,
  alphabetical otherwise.

The cadence rule is also exposed as a pure function for embedders
that want their own clock/state:

```rust
use veks_completion::{TapState, next_tap_state, TAP_ADVANCE_MS};

let (tap_count, next) = next_tap_state(
    Some((prev_state, "current_input_key")),
    now_ms,
    "current_input_key",
    max_level,
);
```

`TAP_ADVANCE_MS` is the public window constant (200 ms).

## Built-in options for downstream adopters

When you build a `CommandTree`, two opt-in builders enable common
capabilities without writing the boilerplate yourself. Both are
fully additive — they walk the tree once at finalisation time:

### `with_auto_help`

```rust
let tree = CommandTree::new("myapp")
    .command("compute", Node::group(vec![
        ("knn", Node::leaf(&["--metric"])),
    ]))
    .command("run", Node::leaf(&["--input"]))
    .with_auto_help();
```

Walks every node and adds `--help` (boolean) plus a default help line
("Show usage information for this command."). Idempotent — nodes that
already declare `--help` are left alone. Once attached, `--help`:

- shows up in tab completion at every level,
- is recognised by `parse_argv` as a known boolean flag (so users
  can type `--help` anywhere without "unknown flag" errors), and
- can be paired with `render_usage(node, path)` in your handler to
  print the help block:

  ```rust
  let parsed = parse_argv(&tree, &argv)?;
  if parsed.flags.contains_key("--help") {
      let node = walk_to(&tree.root, &parsed.path);
      println!("{}", render_usage(node, &parsed.path));
      return Ok(());
  }
  ```

### `with_metricsql_at`

```rust
use std::sync::Arc;
use veks_completion::providers::{MetricsqlCatalog, metricsql_provider};

let catalog: Arc<dyn MetricsqlCatalog> = Arc::new(MyCatalog::new());
let tree = CommandTree::new("nbrs")
    .command("query", Node::leaf(&[]))
    .with_metricsql_at(&["query"], catalog);
```

Attaches a built-in `metricsql_provider` `SubtreeProvider` at the
specified node path. Equivalent to manually `walk_path_mut(...)`-ing
to the node and calling `with_subtree_provider(metricsql_provider(
catalog))`, but surfaces the intent at tree-construction time.

The MetricsQL provider understands:

| Cursor context                       | Suggestions                              |
|--------------------------------------|------------------------------------------|
| Top of expression / after a binop    | metric names + function names            |
| Inside `{` (label-matcher block)     | label keys for the preceding metric      |
| After `key=` or `key=~` inside `{…}` | label values (in quoted form)            |
| Inside `"…"` after `key=`            | label values (bare; quote already open)  |
| Inside `[` (range selector)          | time units (`5m`, `1h`, etc.)            |
| After `sum`/`avg`/`count`| `by` / `without`                         |
| After `sum by`/`without`             | label keys (any)                         |
| After `offset`                       | time-unit suggestions                    |

Site-specific data (metric names, label keys, label values) comes
from your `MetricsqlCatalog` impl. The built-in vocabulary
(functions, time units, operators, aggregation modifiers) is baked
in — see `providers::METRICSQL_FUNCTIONS`,
`providers::METRICSQL_TIME_UNITS`, etc.

## Usage — context-sensitive subtree completion

When tab completion needs grammar-aware behavior in a subtree (e.g.
inside a query DSL), attach a `SubtreeProvider`:

```rust
use std::sync::Arc;
use veks_completion::{Node, PartialParse, SubtreeProvider};

let metrics_provider: SubtreeProvider = Arc::new(|pp: &PartialParse| {
    // pp.completed = full chain of words to the left of the cursor
    // pp.partial   = the partial word under the cursor
    // pp.tree_path = the resolved tree-node path
    // Return whatever candidates make sense given that state.
    parse_my_dsl_and_complete(pp.partial)
});

let tree = CommandTree::new("app")
    .command("metrics",
        Node::group(vec![("match", Node::leaf(&[]))])
            .with_subtree_provider(metrics_provider));
```

The deepest matching subtree provider on the path takes over
completion. This is the recommended replacement for the pre-walker
hook pattern.

## Usage — handler attachment

Embedders that want to dispatch to handlers from the same tree can
attach arbitrary payloads via `Extras`:

```rust
use veks_completion::{Extras, Node};

struct MyHandler { … }

let leaf = Node::leaf(&["--input"])
    .with_extras(Extras::new(MyHandler { … }));

// Later, after parse_argv resolves `path`:
if let Some(extras) = node.extras() {
    if let Some(handler) = extras.downcast::<MyHandler>() {
        handler.run(parsed);
    }
}
```

`Extras` wraps `Arc<dyn Any + Send + Sync>` — no generic on `Node`,
no hard dependency on tokio or any handler framework.

## Architecture

```
veks-completion/        # This crate — generic completion + parse + help
  src/lib.rs            # Node, CommandTree, complete(), parse_argv(),
                        # render_usage(), ClosedValues, Directive, …

veks/src/cli/
  dyncomp.rs            # Walks clap::Command -> CommandTree;
                        # registers global ValueProviders.
  mod.rs                # Wires `eval "$(veks completions)"` snippet.
```

The completion crate has no dependency on clap, veks, or any pipeline
code. The clap-to-tree conversion lives in `dyncomp.rs` inside the
`veks` binary crate.

## Examples

Two runnable examples in `examples/` show how the pieces fit
together. Read them top-to-bottom — the rustdoc comments narrate
each step.

### `basic.rs` — first-time setup

```bash
cargo run --example basic -p veks-completion
```

Builds a small `CommandTree` with a few subcommands and a value
provider, prints what completion would suggest at sample cursor
positions. The minimum viable adoption.

### `metricsql.rs` — end-to-end coding scenario

A full integration that mirrors how you'd actually adopt
veks-completion in a tool with grammar-aware completion needs. It
shows the four steps of a real adoption:

1. **Implement the catalog** — a small `InMemoryCatalog` that
   provides the site-specific data the MetricsQL provider needs
   (metric names, label keys, label values).
2. **Build the tree** — uses both built-in options:
   `with_auto_help()` (uniform `--help` everywhere) and
   `with_metricsql_at(&["query"], catalog)` (grammar-aware
   completion inside the `query` subcommand).
3. **Wire the entry point**`handle_complete_env` for tab
   callbacks, `print_bash_script` for the activation snippet,
   `parse_argv` + `render_usage` for `--help` handling, dispatch
   on the resolved subcommand path.
4. **Drive the demo** — runs ten realistic MetricsQL cursor
   positions (top-of-expression, inside `{`, after `key=`, inside
   open quote, inside `[`, after `by`, after `offset`, etc.) and
   prints what the completer suggests at each.

```bash
# Build + register completions, then tab around interactively:
cargo build --example metricsql -p veks-completion
eval "$(./target/debug/examples/metricsql completions)"
./target/debug/examples/metricsql query 'up{<TAB>'
./target/debug/examples/metricsql query 'rate(http_requests_total[<TAB>'
./target/debug/examples/metricsql query 'sum by (<TAB>'
./target/debug/examples/metricsql --help
./target/debug/examples/metricsql query --help

# Or skip the shell setup and watch the demo print its results:
cargo run --example metricsql -p veks-completion -- demo
```

The demo's output for each scenario shows the raw input, the cursor
byte position, and the candidates the engine would offer — the
quickest way to sanity-check what your `MetricsqlCatalog` impl
returns at every cursor position the provider knows about. Adapt
this scenario script into your own integration tests by swapping in
your real catalog.