vcf-super-cli 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/CHANGELOG.md +36 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/PKG-INFO +3 -2
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/README.md +2 -1
- vcf_super_cli-0.3.0/docs/commands.md +112 -0
- vcf_super_cli-0.3.0/docs/superpowers/specs/2026-06-05-vcf-super-cli-v0.3-ergonomics-design.md +232 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/usage.md +19 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/pyproject.toml +15 -1
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_builder.py +1 -1
- vcf_super_cli-0.3.0/tests/test_complete.py +100 -0
- vcf_super_cli-0.3.0/tests/test_filter_pagination_cli.py +174 -0
- vcf_super_cli-0.3.0/tests/test_filters.py +66 -0
- vcf_super_cli-0.3.0/tests/test_paginate.py +54 -0
- vcf_super_cli-0.3.0/tests/test_pyvmomi_events.py +138 -0
- vcf_super_cli-0.3.0/tests/test_pyvmomi_inventory.py +96 -0
- vcf_super_cli-0.3.0/tests/test_pyvmomi_perf.py +138 -0
- vcf_super_cli-0.3.0/tests/test_pyvmomi_tasks.py +62 -0
- vcf_super_cli-0.3.0/tests/test_vmomi.py +131 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/uv.lock +1 -1
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/_version.py +1 -1
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/cli/app.py +14 -1
- vcf_super_cli-0.3.0/vsc/connect/vmomi.py +102 -0
- vcf_super_cli-0.3.0/vsc/gen/builder.py +417 -0
- vcf_super_cli-0.3.0/vsc/gen/complete.py +62 -0
- vcf_super_cli-0.3.0/vsc/gen/filters.py +88 -0
- vcf_super_cli-0.3.0/vsc/gen/paginate.py +40 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/output/errors.py +26 -0
- vcf_super_cli-0.3.0/vsc/pyvmomi/__init__.py +7 -0
- vcf_super_cli-0.3.0/vsc/pyvmomi/events.py +98 -0
- vcf_super_cli-0.3.0/vsc/pyvmomi/inventory.py +91 -0
- vcf_super_cli-0.3.0/vsc/pyvmomi/perf.py +118 -0
- vcf_super_cli-0.3.0/vsc/pyvmomi/runner.py +51 -0
- vcf_super_cli-0.3.0/vsc/pyvmomi/tasks.py +41 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/skill/assets/SKILL.md +15 -0
- vcf_super_cli-0.2.0/docs/commands.md +0 -64
- vcf_super_cli-0.2.0/vsc/gen/builder.py +0 -243
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/.github/workflows/docs.yml +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/.github/workflows/e2e.yml +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/.github/workflows/lint.yml +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/.github/workflows/release.yml +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/.github/workflows/test.yml +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/.gitignore +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/AGENTS.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/LICENSE +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/design.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/index.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/install.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/profiles.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/superpowers/specs/2026-06-05-vcf-super-cli-design.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/superpowers/specs/2026-06-05-vcf-super-cli-v0.2-writes-design.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/docs/writes.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/mkdocs.yml +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/e2e/README.md +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/e2e/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/e2e/conftest.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/e2e/test_read_smoke.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_cli.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_config.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_discover.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_errors.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_params.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_preview.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_render.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_session.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_skill.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_version.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/tests/test_write_errors.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/cli/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/cli/profiles.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/cli/skill.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/config/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/config/schema.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/config/store.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/connect/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/connect/session.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/connect/targets.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/gen/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/gen/discover.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/gen/model.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/gen/params.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/gen/preview.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/logging_config.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/output/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/output/exit_codes.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/output/render.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/skill/__init__.py +0 -0
- {vcf_super_cli-0.2.0 → vcf_super_cli-0.3.0}/vsc/skill/export.py +0 -0
|
@@ -7,6 +7,42 @@ versions may include breaking changes.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## v0.3.0 — 2026-06-05
|
|
11
|
+
|
|
12
|
+
Ergonomics: friendlier filtering and paging, offline shell completion, and a
|
|
13
|
+
pyVmomi fallback surface for gaps the REST/vAPI layer doesn't cover. The
|
|
14
|
+
agent-facing contract is unchanged — stable JSON, error envelope, exit codes,
|
|
15
|
+
and writes still dry-run by default.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Static shell completion** (#32), fully offline — never opens a connection.
|
|
20
|
+
Completes enum option choices, output formats, configured profile names, and
|
|
21
|
+
`list` filter enum values. Install with `vsc --install-completion`. (Live
|
|
22
|
+
resource-id completion is intentionally deferred to a later release.)
|
|
23
|
+
- **Per-field filter flags** (#33). `list` commands flatten the SDK filter spec
|
|
24
|
+
into typed `--<field>` options (repeatable for list/set fields; enum choices
|
|
25
|
+
validated and completed), e.g. `vsc vsphere vm list --power-states POWERED_ON
|
|
26
|
+
--names web-1`. The raw `--filter '<json>'` blob stays as a base layer that
|
|
27
|
+
per-field flags override.
|
|
28
|
+
- **Pagination helpers** (#33): `--all` follows the NSX cursor across pages;
|
|
29
|
+
`--max-items N` caps the total; `--limit N` is a client-side cap for
|
|
30
|
+
non-paginated (vSphere) lists. Without `--all`, a paginated `list` returns one
|
|
31
|
+
page and surfaces its `cursor` for manual paging.
|
|
32
|
+
- **pyVmomi fallback commands** (read-only) under `vsc vsphere`, for areas the
|
|
33
|
+
REST/vAPI surface lacks — same JSON / error-envelope / exit-code contract:
|
|
34
|
+
- `perf vm|host --metric <group.name>` — performance counters via the
|
|
35
|
+
PerformanceManager (#34).
|
|
36
|
+
- `events list [--vm|--host] [--since 1h]` and `tasks list` — recent events
|
|
37
|
+
and recent/running tasks (#35).
|
|
38
|
+
- `inventory vm|host [--props <path>]…` — a PropertyCollector property walk
|
|
39
|
+
(#36).
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Documentation and the bundled agent `SKILL.md` describe completion, the filter
|
|
44
|
+
and paging flags, and the pyVmomi fallback surface.
|
|
45
|
+
|
|
10
46
|
## v0.2.0 — 2026-06-05
|
|
11
47
|
|
|
12
48
|
First PyPI release. Adds the **write** surface on top of the v0.1 read-only
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vcf-super-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Modern, agent-friendly CLI for VMware Cloud Foundation 9, with a command tree generated dynamically from the vcf-sdk vAPI bindings.
|
|
5
5
|
Project-URL: Homepage, https://github.com/thomaschristory/vcf-super-cli
|
|
6
6
|
Project-URL: Documentation, https://thomaschristory.github.io/vcf-super-cli/
|
|
@@ -73,7 +73,8 @@ $ vsc vsphere power stop vm-42 --apply # writes are dry-run without --app
|
|
|
73
73
|
| vSphere / vCenter read (`vsc vsphere …`) | ✅ v0.1 |
|
|
74
74
|
| NSX **Policy API** read (`vsc nsx …`) | ✅ v0.1 |
|
|
75
75
|
| Writes — dry-run by default + `--apply` (`vsc vsphere …` / `vsc nsx …`) | ✅ v0.2 |
|
|
76
|
-
|
|
|
76
|
+
| Ergonomics — offline shell completion, per-field filter flags + paging, pyVmomi fallback (`perf`/`events`/`tasks`/`inventory`) | ✅ v0.3 |
|
|
77
|
+
| Live resource-id completion | planned |
|
|
77
78
|
| NSX Manager / Global-Manager, SDDC Manager, Operations, LCM | deferred |
|
|
78
79
|
|
|
79
80
|
## Install
|
|
@@ -41,7 +41,8 @@ $ vsc vsphere power stop vm-42 --apply # writes are dry-run without --app
|
|
|
41
41
|
| vSphere / vCenter read (`vsc vsphere …`) | ✅ v0.1 |
|
|
42
42
|
| NSX **Policy API** read (`vsc nsx …`) | ✅ v0.1 |
|
|
43
43
|
| Writes — dry-run by default + `--apply` (`vsc vsphere …` / `vsc nsx …`) | ✅ v0.2 |
|
|
44
|
-
|
|
|
44
|
+
| Ergonomics — offline shell completion, per-field filter flags + paging, pyVmomi fallback (`perf`/`events`/`tasks`/`inventory`) | ✅ v0.3 |
|
|
45
|
+
| Live resource-id completion | planned |
|
|
45
46
|
| NSX Manager / Global-Manager, SDDC Manager, Operations, LCM | deferred |
|
|
46
47
|
|
|
47
48
|
## Install
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Commands
|
|
2
|
+
|
|
3
|
+
The `vsphere` and `nsx` command trees are **generated from the `vcf-sdk` vAPI
|
|
4
|
+
bindings**, so `vsc --help` (and each sub-`--help`) is always the authoritative,
|
|
5
|
+
version-accurate reference. This page is an overview.
|
|
6
|
+
|
|
7
|
+
Generated leaves expose both **read** and **write** verbs:
|
|
8
|
+
|
|
9
|
+
- `list` — list resources (optional `--filter '<json>'`)
|
|
10
|
+
- `get <id>` — fetch one resource by id, **where the SDK provides a by-id GET**
|
|
11
|
+
(e.g. `vm`, `cluster`, `datacenter`, `datastore`; some leaves such as `host`,
|
|
12
|
+
`folder`, and `network` are `list`-only)
|
|
13
|
+
- writes — `create`, `delete`, `set` (PUT upsert), `patch`, and action verbs
|
|
14
|
+
(e.g. `start`/`stop`/`reset` under `power`). **Writes are dry-run by default and
|
|
15
|
+
require `--apply`** — see [Writes](writes.md).
|
|
16
|
+
|
|
17
|
+
## `vsc vsphere …` (vCenter)
|
|
18
|
+
|
|
19
|
+
Generated from `com.vmware.vcenter`:
|
|
20
|
+
|
|
21
|
+
| Group | Examples |
|
|
22
|
+
|-------|----------|
|
|
23
|
+
| `vm` | `vsc vsphere vm list`, `vsc vsphere vm get <vm>`, `vsc vsphere vm delete <vm> --apply` |
|
|
24
|
+
| `host` | `vsc vsphere host list`, `vsc vsphere host disconnect <host> --apply` |
|
|
25
|
+
| `cluster` | `vsc vsphere cluster list` |
|
|
26
|
+
| `datacenter` | `vsc vsphere datacenter list` |
|
|
27
|
+
| `datastore` | `vsc vsphere datastore list` |
|
|
28
|
+
| `folder` | `vsc vsphere folder list` |
|
|
29
|
+
| `network` | `vsc vsphere network list` |
|
|
30
|
+
| `resource-pool` | `vsc vsphere resource-pool create --spec '<json>' --apply` |
|
|
31
|
+
| `power` | `vsc vsphere power start\|stop\|reset\|suspend <vm> --apply` |
|
|
32
|
+
| `cpu` / `memory` / `disk` / `ethernet` | VM hardware reads + writes, e.g. `vsc vsphere cpu update <vm> --spec '<json>' --apply` |
|
|
33
|
+
| `perf` / `events` / `tasks` / `inventory` | **pyVmomi fallback** (read-only) — perf counters, recent events, recent/running tasks, and a property walk the REST/vAPI surface lacks (see below) |
|
|
34
|
+
|
|
35
|
+
### pyVmomi fallback commands
|
|
36
|
+
|
|
37
|
+
A few inventory/performance areas are only reachable over the older pyVmomi SOAP
|
|
38
|
+
API. Those commands live under `vsc vsphere` alongside the generated ones, are
|
|
39
|
+
**read-only** (no `--apply`), and emit the same JSON / error envelope / exit codes.
|
|
40
|
+
|
|
41
|
+
- `vsc vsphere perf vm <vm> [--metric group.name]… [--max-samples N]` — performance
|
|
42
|
+
counters via the PerformanceManager. Metrics are `group.name` (e.g. `cpu.usage`)
|
|
43
|
+
or `group.name.rollup` (e.g. `cpu.usage.average`); repeat `--metric` for several.
|
|
44
|
+
- `vsc vsphere perf host <host> …` — the same for an ESXi host.
|
|
45
|
+
- `vsc vsphere events list [--vm <vm> | --host <host>] [--since 1h] [--max-count N]`
|
|
46
|
+
— recent events via the EventManager. `--since` takes a duration (`30s`/`15m`/
|
|
47
|
+
`2h`/`1d`); scope to at most one entity.
|
|
48
|
+
- `vsc vsphere tasks list [--max-count N]` — recent and running tasks via the
|
|
49
|
+
TaskManager.
|
|
50
|
+
- `vsc vsphere inventory vm <vm> [--props path]…` / `inventory host <host> …` —
|
|
51
|
+
managed-object properties the REST list ops omit (device tree, custom attributes),
|
|
52
|
+
via the PropertyCollector. `--props` is repeatable (e.g. `--props config.hardware`);
|
|
53
|
+
with none, a small per-type summary set is returned.
|
|
54
|
+
|
|
55
|
+
## `vsc nsx …` (NSX Policy)
|
|
56
|
+
|
|
57
|
+
Generated from `vcf.nsx.policy`:
|
|
58
|
+
|
|
59
|
+
| Group | Examples |
|
|
60
|
+
|-------|----------|
|
|
61
|
+
| `segments` | `vsc nsx segments list`, `vsc nsx segments set <id> --segment '<json>' --apply` |
|
|
62
|
+
| `tier0s` / `tier1s` | `vsc nsx tier1s list`, `vsc nsx tier1s set <id> --tier1 '<json>' --apply` |
|
|
63
|
+
| `services` | `vsc nsx services list` |
|
|
64
|
+
| `groups` | `vsc nsx groups list`, `vsc nsx groups delete <domain> <group> --apply` |
|
|
65
|
+
| `security-policies` | `vsc nsx security-policies list` |
|
|
66
|
+
| `gateway-policies` | `vsc nsx gateway-policies list` |
|
|
67
|
+
| `ip-pools` | `vsc nsx ip-pools set <id> --ip-address-pool '<json>' --apply` |
|
|
68
|
+
| `dhcp-server-configs` / `dhcp-relay-configs` | DHCP config reads + writes |
|
|
69
|
+
| `locale-services` | Tier-1 locale services reads + writes |
|
|
70
|
+
|
|
71
|
+
## Curated commands
|
|
72
|
+
|
|
73
|
+
- `vsc profiles …` — manage connection profiles (see [Profiles](profiles.md))
|
|
74
|
+
- `vsc skill export <dir> [--apply]` — export the bundled agent Skill
|
|
75
|
+
- `vsc --version`
|
|
76
|
+
|
|
77
|
+
## Filtering
|
|
78
|
+
|
|
79
|
+
`list` commands expose each field of the SDK filter spec as its own typed flag.
|
|
80
|
+
List-valued fields are repeatable; enum fields validate their choices and
|
|
81
|
+
tab-complete:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
vsc --profile prod vsphere vm list --power-states POWERED_ON --names web-1 --names web-2
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The raw JSON spec is still accepted as a base layer / escape hatch; per-field
|
|
88
|
+
flags merge **over** it (a flag wins over the same key in the blob):
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
vsc --profile prod vsphere vm list --filter '{"clusters": ["domain-c1"]}' --power-states POWERED_ON
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Pagination
|
|
95
|
+
|
|
96
|
+
`list` commands accept paging flags:
|
|
97
|
+
|
|
98
|
+
| Flag | Effect |
|
|
99
|
+
|------|--------|
|
|
100
|
+
| `--all` | follow the cursor and return **every** page (paginated backends, e.g. NSX) |
|
|
101
|
+
| `--max-items N` | cap the total number of items returned |
|
|
102
|
+
| `--limit N` | client-side cap for non-paginated (vSphere) lists |
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
vsc --profile prod nsx segments list --all # every page, concatenated
|
|
106
|
+
vsc --profile prod nsx segments list --page-size 50 # one page (cursor surfaced for manual paging)
|
|
107
|
+
vsc --profile prod vsphere vm list --limit 20 # first 20 only
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Without `--all`, a paginated `list` returns one page and surfaces the `cursor` so
|
|
111
|
+
an agent can drive pagination itself. On non-paginated backends (vSphere) `--all`
|
|
112
|
+
is a no-op — the output stays a plain array.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# vcf-super-cli v0.3 — Ergonomics (design)
|
|
2
|
+
|
|
3
|
+
**Status:** approved 2026-06-05. Milestone: `v0.3 — ergonomics` (#3).
|
|
4
|
+
**Predecessors:** [v0.1 design](2026-06-05-vcf-super-cli-design.md), [v0.2 writes design](2026-06-05-vcf-super-cli-v0.2-writes-design.md).
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Make the dynamically-generated CLI pleasant to drive by hand and still trivially
|
|
9
|
+
scriptable by an agent. Three independent feature areas, each shipped as its own
|
|
10
|
+
issue/PR under milestone #3:
|
|
11
|
+
|
|
12
|
+
1. **Static shell completion** — offline tab-completion of enums, output formats,
|
|
13
|
+
profile names, and per-field filter choices.
|
|
14
|
+
2. **Filter & pagination helpers** — per-field filter flags (augmenting today's
|
|
15
|
+
`--filter '<json>'` blob) and friendlier paging (`--all`, `--max-items`,
|
|
16
|
+
`--limit`).
|
|
17
|
+
3. **pyVmomi fallback commands** — hand-written read commands (perf, events/tasks,
|
|
18
|
+
inventory walk) for gaps the vAPI/REST surface doesn't cover.
|
|
19
|
+
|
|
20
|
+
The agent contract is unchanged and non-negotiable: stable JSON output, the
|
|
21
|
+
documented error envelope, the frozen exit codes, and **writes stay dry-run by
|
|
22
|
+
default**. The pyVmomi additions are all reads, so they need no `--apply` gate.
|
|
23
|
+
|
|
24
|
+
## Locked decisions (from brainstorm, 2026-06-05)
|
|
25
|
+
|
|
26
|
+
- **Completion is static/offline only.** No feature in v0.3 hits the API during
|
|
27
|
+
`<TAB>`. Live resource-ID completion (e.g. completing `<vm>` from a live list)
|
|
28
|
+
is explicitly **deferred to v0.4**; the param model already carries
|
|
29
|
+
`resource_types` so it remains a clean future extension.
|
|
30
|
+
- **Filter flags augment, never replace, `--filter`.** Raw `--filter '<json>'`
|
|
31
|
+
stays as the base layer / escape hatch; generated per-field flags merge over it.
|
|
32
|
+
- **Pagination:** `--all` auto-follows the NSX cursor; `--max-items` caps total;
|
|
33
|
+
`--limit` is a client-side cap for non-cursor (vSphere) lists. NSX
|
|
34
|
+
`--page-size`/`--cursor` already exist as generated query options.
|
|
35
|
+
- **pyVmomi covers all three families** (perf, events/tasks, inventory walk),
|
|
36
|
+
built on a small shared `SmartConnect` foundation.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Feature 1 — Static shell completion
|
|
41
|
+
|
|
42
|
+
### Behaviour
|
|
43
|
+
|
|
44
|
+
Completion values are derived entirely from the introspected `Param` model plus
|
|
45
|
+
local config — never from a network call. `--help` and completion both stay fully
|
|
46
|
+
offline.
|
|
47
|
+
|
|
48
|
+
Completed surfaces:
|
|
49
|
+
|
|
50
|
+
| Surface | Source | Example |
|
|
51
|
+
|---------|--------|---------|
|
|
52
|
+
| enum option | `param.enum_values` | `--power-state POWERED_<TAB>` → `POWERED_ON`, `POWERED_OFF` |
|
|
53
|
+
| `--output` / `-o` | `OutputFormat` members | `-o <TAB>` → `json`, `table` |
|
|
54
|
+
| global `--profile` / `-p` | profile names from `load_config()` (offline file read) | `-p <TAB>` → `prod`, `lab` |
|
|
55
|
+
| per-field filter enum flags | reuse enum completer (feature 2) | `--filter-power-state <TAB>` |
|
|
56
|
+
|
|
57
|
+
Resource-ID args (the `<vm>` positionals) are **not** completed in v0.3.
|
|
58
|
+
|
|
59
|
+
### Implementation
|
|
60
|
+
|
|
61
|
+
- New module **`vsc/gen/complete.py`** — pure completer factories:
|
|
62
|
+
- `enum_completer(values: list[str]) -> Callable[[str], list[str]]`
|
|
63
|
+
- `profile_completer() -> Callable[[str], list[str]]`
|
|
64
|
+
Each takes the Click `incomplete` string and returns the prefix-filtered
|
|
65
|
+
candidate list. No `ctx`/`param` dependency → unit-testable without a shell.
|
|
66
|
+
- **`vsc/gen/builder.py`** `_build_signature`: when a non-path option is built for
|
|
67
|
+
an `ENUM` param, pass `autocompletion=enum_completer(param.enum_values)` to
|
|
68
|
+
`typer.Option`. When building `--output`, attach an `OutputFormat` completer.
|
|
69
|
+
- **`vsc/cli/app.py`** main callback: attach `profile_completer()` to `--profile`.
|
|
70
|
+
- Root app already sets `add_completion=True`; document `vsc --install-completion`
|
|
71
|
+
and `vsc --show-completion` in `docs/usage.md`.
|
|
72
|
+
|
|
73
|
+
### Tests
|
|
74
|
+
|
|
75
|
+
`tests/test_complete.py` — completer factories return correctly prefix-filtered
|
|
76
|
+
lists, empty `incomplete` returns all, no match returns `[]`. A smoke test asserts
|
|
77
|
+
generated enum options carry an `autocompletion` callback.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Feature 2 — Filter & pagination helpers
|
|
82
|
+
|
|
83
|
+
### Per-field filter flags
|
|
84
|
+
|
|
85
|
+
vCenter list operations take a single `filter` parameter that is a `STRUCT`
|
|
86
|
+
(`VM.FilterSpec` etc.). Today the user must pass it as `--filter '<json>'`. v0.3
|
|
87
|
+
additionally flattens that struct's fields into typed options.
|
|
88
|
+
|
|
89
|
+
- **Detection:** on a read/list op, a parameter that is `ParamKind.STRUCT` **and
|
|
90
|
+
named `filter`** is flattened. Write-body structs are *not* flattened — they
|
|
91
|
+
stay JSON (`--spec`, `--segment`, …).
|
|
92
|
+
- **Generated flags:** each struct field → `--<field>` (kebab-cased). `list`/`set`
|
|
93
|
+
fields are **repeatable** (`list[str]` annotation, multiple uses accumulate).
|
|
94
|
+
`enum` fields carry choices in help + the enum completer.
|
|
95
|
+
- **Precedence / merge:** raw `--filter '<json>'` provides the base dict; per-field
|
|
96
|
+
flags merge **over** it (a flag wins over the same key in the blob). The merged
|
|
97
|
+
dict is coerced into the FilterSpec via the existing
|
|
98
|
+
`coerce_struct`/`coerce_value` path — no new coercion logic.
|
|
99
|
+
- **Collisions:** a flattened field whose flag would collide with a reserved option
|
|
100
|
+
(`--output`, `--apply`) or another option is suffixed using the existing
|
|
101
|
+
`_sig_name` mechanism.
|
|
102
|
+
|
|
103
|
+
New helper **`vsc/gen/filters.py`**:
|
|
104
|
+
- `flatten_filter(param: Param) -> list[Param]` — child params for each struct field
|
|
105
|
+
(built with `param_from_type` on `struct_type.get_field(name)`).
|
|
106
|
+
- `assemble_filter(base_json, field_values, param) -> Any` — merge base + per-field
|
|
107
|
+
values and coerce into the struct. Pure; unit-testable.
|
|
108
|
+
|
|
109
|
+
`builder.py` consumes these: `_build_signature` emits the child options (tracking
|
|
110
|
+
them so `_collect_kwargs` knows to reassemble rather than pass them through), and
|
|
111
|
+
`_collect_kwargs` calls `assemble_filter` to rebuild the single `filter` kwarg.
|
|
112
|
+
|
|
113
|
+
### Pagination
|
|
114
|
+
|
|
115
|
+
NSX Policy list ops return a `*ListResult` struct (`.results`, `.cursor`,
|
|
116
|
+
`.result_count`) and already expose `cursor`/`page_size` as generated query
|
|
117
|
+
options. vSphere REST list ops return a plain list and do not paginate.
|
|
118
|
+
|
|
119
|
+
Injected options on **list verbs** (`op.cli_verb == "list"`):
|
|
120
|
+
|
|
121
|
+
| Flag | Applies to | Effect |
|
|
122
|
+
|------|-----------|--------|
|
|
123
|
+
| `--all` | cursor lists (NSX) | follow `.cursor` across pages, concatenate `.results`, stop at `--max-items` |
|
|
124
|
+
| `--max-items N` | all lists | hard cap on returned items (post-fetch for vSphere, loop-stop for `--all`) |
|
|
125
|
+
| `--limit N` | non-cursor lists (vSphere) | client-side slice of the returned list |
|
|
126
|
+
|
|
127
|
+
Without `--all`, a cursor list returns one page **including** its `cursor`, so an
|
|
128
|
+
agent can paginate manually. `--all` and `--max-items` interact: the follow loop
|
|
129
|
+
stops once `--max-items` items are collected.
|
|
130
|
+
|
|
131
|
+
New helper **`vsc/gen/paginate.py`**:
|
|
132
|
+
- `follow_cursor(fetch_page, *, max_items) -> list` — `fetch_page(cursor) ->
|
|
133
|
+
(results, next_cursor)`; loops until `next_cursor` is empty/repeated or the cap
|
|
134
|
+
is hit. Pure given the `fetch_page` callable (the callable closes over the SDK
|
|
135
|
+
method in `builder.py`); unit-testable with a fake pager.
|
|
136
|
+
|
|
137
|
+
`builder.py` `make_command` detects a cursor-bearing result and, when `--all` is
|
|
138
|
+
set, drives `follow_cursor`; otherwise applies `--limit`/`--max-items` slicing
|
|
139
|
+
before `emit()`.
|
|
140
|
+
|
|
141
|
+
### Tests
|
|
142
|
+
|
|
143
|
+
`tests/test_filters.py` — flatten produces one child per field with correct
|
|
144
|
+
kind/required/repeatable; assemble merges blob + flags with flags winning; enum
|
|
145
|
+
field carries choices. `tests/test_paginate.py` — `follow_cursor` concatenates,
|
|
146
|
+
respects `max_items`, terminates on empty/duplicate cursor. Builder integration
|
|
147
|
+
tests assert a vCenter list op grows `--<field>` options and an NSX list op grows
|
|
148
|
+
`--all`/`--max-items`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Feature 3 — pyVmomi fallback commands
|
|
153
|
+
|
|
154
|
+
Hand-written read commands for gaps the vAPI/REST surface doesn't cover, mounted
|
|
155
|
+
into the existing `vsc vsphere` tree and emitted through the existing
|
|
156
|
+
`emit()`/error-envelope contract.
|
|
157
|
+
|
|
158
|
+
### Foundation
|
|
159
|
+
|
|
160
|
+
- New module **`vsc/connect/vmomi.py`**:
|
|
161
|
+
- `connect_vmomi() -> ServiceInstance` via `pyVim.connect.SmartConnect`, reusing
|
|
162
|
+
`resolve_target("vsphere")` for server/user/password and an `ssl` context that
|
|
163
|
+
honours `verify` (unverified context when `insecure`). Cached like the vAPI
|
|
164
|
+
connections (`reset_cache()` clears it); `Disconnect` registered at process
|
|
165
|
+
exit.
|
|
166
|
+
- `vmomi_jsonable(obj) -> Any` — convert managed-object / data-object trees into
|
|
167
|
+
plain JSON-able dicts (`moref` → `{"type", "value"}`, data objects → field
|
|
168
|
+
dicts, leave scalars/datetimes alone) so `emit()` renders them like any other
|
|
169
|
+
result.
|
|
170
|
+
- New package **`vsc/pyvmomi/`**, one module per family, each exposing a
|
|
171
|
+
`typer.Typer`. `vsc/cli/app.py` adds them onto the vsphere group
|
|
172
|
+
(`vsphere_group.add_typer(perf_app, name="perf")`, …). pyVmomi errors
|
|
173
|
+
(`vim.fault.*`, connection failures) map into the existing envelope/exit codes
|
|
174
|
+
(auth→3, not-found→4, connection→5) via a small adapter reusing
|
|
175
|
+
`vsc/output/errors.py` patterns.
|
|
176
|
+
|
|
177
|
+
### Commands
|
|
178
|
+
|
|
179
|
+
- **`vsc vsphere perf`** — real-time/historical counters via `PerformanceManager`.
|
|
180
|
+
e.g. `perf vm <vm> --metric cpu.usage [--interval 20s]`,
|
|
181
|
+
`perf host <host> --metric mem.usage`. Resolves the counter id, queries
|
|
182
|
+
`QueryPerf`, emits timestamped samples.
|
|
183
|
+
- **`vsc vsphere events`** — recent events via `EventManager` with `--since`/entity
|
|
184
|
+
filters. **`vsc vsphere tasks`** — running + recent tasks via `TaskManager`.
|
|
185
|
+
- **`vsc vsphere inventory`** — `PropertyCollector` walk for properties the REST
|
|
186
|
+
list ops omit (device tree, custom attributes, relationships), e.g.
|
|
187
|
+
`inventory vm <vm> --props config.hardware`.
|
|
188
|
+
|
|
189
|
+
All are reads → no `--apply`. They accept the same `--output json|table` and obey
|
|
190
|
+
`--profile`.
|
|
191
|
+
|
|
192
|
+
### Tests
|
|
193
|
+
|
|
194
|
+
pyVmomi has no offline-introspection trick like the vAPI bindings, so tests mock
|
|
195
|
+
`SmartConnect`/`ServiceInstance` and the relevant managers. `tests/test_vmomi.py`
|
|
196
|
+
covers `vmomi_jsonable` conversion (moref/data-object/scalar) purely;
|
|
197
|
+
`tests/test_pyvmomi_*.py` drive each command against a fake `ServiceInstance`
|
|
198
|
+
asserting the emitted JSON shape and error mapping. No live vCenter required.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Cross-cutting
|
|
203
|
+
|
|
204
|
+
- Each feature PR updates the relevant `docs/` page(s) and the bundled
|
|
205
|
+
`vsc/skill/assets/SKILL.md` for any new surface.
|
|
206
|
+
- `ruff`, `mypy --strict`, `pytest`, and `mkdocs --strict` stay green; CI green
|
|
207
|
+
before merge.
|
|
208
|
+
- Every PR gets an adversarial **refute-before-accept** review pass; findings are
|
|
209
|
+
fixed before merge.
|
|
210
|
+
|
|
211
|
+
## Issues (milestone #3)
|
|
212
|
+
|
|
213
|
+
| Issue | Title | Depends on |
|
|
214
|
+
|-------|-------|-----------|
|
|
215
|
+
| Epic | v0.3 — ergonomics | — |
|
|
216
|
+
| A | Static shell completion (enums, `--output`, `--profile`, filter choices) | — |
|
|
217
|
+
| B | Filter & pagination helpers (per-field flags + `--all`/`--max-items`/`--limit`) | — |
|
|
218
|
+
| C | pyVmomi fallback foundation + `vsc vsphere perf` | — |
|
|
219
|
+
| D | pyVmomi events & tasks | C |
|
|
220
|
+
| E | pyVmomi inventory walk | C |
|
|
221
|
+
| F | v0.3 docs/SKILL roundup + release prep | A–E |
|
|
222
|
+
|
|
223
|
+
Build order: A and B in parallel; C → (D, E); F last. The release tag (`vX.Y.Z`)
|
|
224
|
+
is pushed **manually by the maintainer** after F merges — `release.yml` keeps **no
|
|
225
|
+
`environment:` block** (the PyPI trusted publisher is registered with a blank
|
|
226
|
+
environment; they must stay matched).
|
|
227
|
+
|
|
228
|
+
## Out of scope (v0.3)
|
|
229
|
+
|
|
230
|
+
- Live resource-ID completion (hits the API) — v0.4 candidate.
|
|
231
|
+
- pyVmomi *writes* — the fallback surface is read-only this milestone.
|
|
232
|
+
- NSX Manager / Global-Manager APIs (still Policy-only).
|
|
@@ -14,6 +14,25 @@ vsc --profile prod vsphere vm list -o table # table
|
|
|
14
14
|
|
|
15
15
|
Only `json` and `table` are accepted; anything else is rejected with exit code `2`.
|
|
16
16
|
|
|
17
|
+
## Shell completion
|
|
18
|
+
|
|
19
|
+
Install completion for your shell once:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
vsc --install-completion # bash, zsh, fish, PowerShell
|
|
23
|
+
vsc --show-completion # print the script instead of installing
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Completion is **fully offline** — it never opens a connection. It suggests:
|
|
27
|
+
|
|
28
|
+
- enum option choices (e.g. `--power-states <TAB>` → `POWERED_ON`, `POWERED_OFF`),
|
|
29
|
+
- output formats (`-o <TAB>` → `json`, `table`),
|
|
30
|
+
- configured profile names (`--profile <TAB>`),
|
|
31
|
+
- and per-field `list` filter enum choices.
|
|
32
|
+
|
|
33
|
+
Completing a live resource id (e.g. `<vm>` from a real inventory) would require a
|
|
34
|
+
network call and is deliberately **not** done in this release.
|
|
35
|
+
|
|
17
36
|
## Error envelope
|
|
18
37
|
|
|
19
38
|
Errors are written to **stderr** as a stable JSON object:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vcf-super-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Modern, agent-friendly CLI for VMware Cloud Foundation 9, with a command tree generated dynamically from the vcf-sdk vAPI bindings."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -89,10 +89,16 @@ ignore = [
|
|
|
89
89
|
"vsc/cli/**" = [
|
|
90
90
|
"B008", # typer.Option/Argument in defaults is the Typer idiom
|
|
91
91
|
]
|
|
92
|
+
"vsc/pyvmomi/**" = [
|
|
93
|
+
"B008", # typer.Option/Argument in defaults is the Typer idiom
|
|
94
|
+
]
|
|
92
95
|
"vsc/gen/**" = [
|
|
93
96
|
"PLR0911", # type dispatch tables legitimately have many returns
|
|
94
97
|
"PLR0912", # ...and many branches
|
|
95
98
|
]
|
|
99
|
+
"vsc/connect/vmomi.py" = [
|
|
100
|
+
"PLR0911", # vmomi_jsonable is a type-dispatch table (many returns)
|
|
101
|
+
]
|
|
96
102
|
"tests/**" = [
|
|
97
103
|
"PLR2004", # magic values in tests are fine
|
|
98
104
|
"PLR0915", # lifecycle tests legitimately need many statements
|
|
@@ -112,6 +118,14 @@ plugins = ["pydantic.mypy"]
|
|
|
112
118
|
module = ["com", "com.*", "vcf", "vcf.*", "vmware", "vmware.*", "pyVmomi.*", "pyVim.*"]
|
|
113
119
|
ignore_missing_imports = true
|
|
114
120
|
|
|
121
|
+
[[tool.mypy.overrides]]
|
|
122
|
+
# pyVmomi/pyVim are dynamically typed (attributes synthesized at runtime); skipping
|
|
123
|
+
# their analysis treats their surface as Any rather than flagging every vim.* access
|
|
124
|
+
# and untyped SmartConnect/Disconnect call.
|
|
125
|
+
module = ["pyVmomi", "pyVmomi.*", "pyVim", "pyVim.*"]
|
|
126
|
+
follow_imports = "skip"
|
|
127
|
+
follow_imports_for_stubs = true
|
|
128
|
+
|
|
115
129
|
[tool.pytest.ini_options]
|
|
116
130
|
addopts = "-ra --strict-markers --strict-config --ignore=tests/e2e"
|
|
117
131
|
testpaths = ["tests"]
|
|
@@ -278,7 +278,7 @@ def _synthetic_write_with_param(param_name: str) -> Operation:
|
|
|
278
278
|
def test_injected_flags_do_not_collide_with_reserved_param_names() -> None:
|
|
279
279
|
# A write op with a body param literally named 'apply' must not produce two
|
|
280
280
|
# --apply option declarations; the user param is renamed.
|
|
281
|
-
sig, _spec = _build_signature(_synthetic_write_with_param("apply"))
|
|
281
|
+
sig, _spec, _fp = _build_signature(_synthetic_write_with_param("apply"))
|
|
282
282
|
decls: list[str] = []
|
|
283
283
|
for p in sig.parameters.values():
|
|
284
284
|
info = p.default
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Static (offline) shell-completion helpers and their wiring into commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import ClassVar
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from vsc.gen.builder import _OUTPUT_PARAM, _build_signature
|
|
11
|
+
from vsc.gen.complete import enum_completer, output_format_completer, profile_completer
|
|
12
|
+
from vsc.gen.model import Operation, Param, ParamKind
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_enum_completer_prefix_filters() -> None:
|
|
16
|
+
complete = enum_completer(["POWERED_ON", "POWERED_OFF", "SUSPENDED"])
|
|
17
|
+
assert complete("POWERED_") == ["POWERED_ON", "POWERED_OFF"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_enum_completer_empty_incomplete_returns_all() -> None:
|
|
21
|
+
values = ["A", "B", "C"]
|
|
22
|
+
assert enum_completer(values)("") == values
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_enum_completer_no_match_returns_empty() -> None:
|
|
26
|
+
assert enum_completer(["A", "B"])("Z") == []
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_enum_completer_none_incomplete_returns_all() -> None:
|
|
30
|
+
# Typer's wrapper types incomplete as ``str | None``; a None must not crash
|
|
31
|
+
# completion — treat it like an empty prefix.
|
|
32
|
+
assert enum_completer(["A", "B"])(None) == ["A", "B"] # type: ignore[arg-type]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_output_format_completer_offers_formats() -> None:
|
|
36
|
+
assert output_format_completer()("") == ["json", "table"]
|
|
37
|
+
assert output_format_completer()("t") == ["table"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_profile_completer_filters_names(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
41
|
+
class _Cfg:
|
|
42
|
+
profiles: ClassVar[dict[str, object]] = {"prod": 1, "prod-eu": 2, "lab": 3}
|
|
43
|
+
|
|
44
|
+
monkeypatch.setattr("vsc.gen.complete.load_config", _Cfg)
|
|
45
|
+
assert profile_completer()("prod") == ["prod", "prod-eu"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_profile_completer_is_failsoft(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
49
|
+
def _boom() -> object:
|
|
50
|
+
raise RuntimeError("no config readable")
|
|
51
|
+
|
|
52
|
+
monkeypatch.setattr("vsc.gen.complete.load_config", _boom)
|
|
53
|
+
assert profile_completer()("anything") == [] # never raises during <TAB>
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# --------------------------------------------------------------------------- #
|
|
57
|
+
# Wiring: generated options carry the right completer
|
|
58
|
+
# --------------------------------------------------------------------------- #
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _op_with(param: Param) -> Operation:
|
|
62
|
+
return Operation(
|
|
63
|
+
backend="vsphere",
|
|
64
|
+
service_cls=object,
|
|
65
|
+
iface_id="com.vmware.vcenter.thing",
|
|
66
|
+
op_id="get",
|
|
67
|
+
method_name="get",
|
|
68
|
+
cli_verb="get",
|
|
69
|
+
http_method="GET",
|
|
70
|
+
url_template="/vcenter/thing",
|
|
71
|
+
path_vars=[],
|
|
72
|
+
path_var_map={},
|
|
73
|
+
params=[param],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _option_for(sig: inspect.Signature, sig_name: str) -> object:
|
|
78
|
+
return sig.parameters[sig_name].default
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_enum_option_gets_autocompletion() -> None:
|
|
82
|
+
enum_param = Param(name="state", kind=ParamKind.ENUM, required=False, enum_values=["ON", "OFF"])
|
|
83
|
+
sig, _spec, _fp = _build_signature(_op_with(enum_param))
|
|
84
|
+
opt = _option_for(sig, "state")
|
|
85
|
+
assert opt.autocompletion is not None
|
|
86
|
+
assert opt.autocompletion("O") == ["ON", "OFF"]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_non_enum_option_has_no_autocompletion() -> None:
|
|
90
|
+
string_param = Param(name="name", kind=ParamKind.STRING, required=False)
|
|
91
|
+
sig, _spec, _fp = _build_signature(_op_with(string_param))
|
|
92
|
+
assert _option_for(sig, "name").autocompletion is None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_output_option_gets_format_completion() -> None:
|
|
96
|
+
string_param = Param(name="name", kind=ParamKind.STRING, required=False)
|
|
97
|
+
sig, _spec, _fp = _build_signature(_op_with(string_param))
|
|
98
|
+
opt = _option_for(sig, _OUTPUT_PARAM)
|
|
99
|
+
assert opt.autocompletion is not None
|
|
100
|
+
assert opt.autocompletion("") == ["json", "table"]
|