## v2.4.6 — Recheck TTL identity at final deletion lookup Released 2026-11-09. * Bind TTL expiry deletion to the recorded VM instance UUID at the final lookup, before power-off or destruction. A same-name replacement is refused, audited as an error, and its stale TTL entry is removed. ## v2.5.0 (2026-11-10) — Enforce backend resize limits and preserve denial audit Require Policy 1.25.0; enforce configured CPU/memory percentage changes from the final resolved VM configuration and bind ConfigSpec.changeVersion. Refuse missing/invalid required facts before a task. Preserve backend Policy denials through MCP error handling. Extract snapshot/task/VMkernel helpers and guest CLI callbacks without changing the public tool schema. Validation: existing suites, complete CLI/MCP surface comparison, structural guard checks and security scanning; exact results are retained in the private release record. Live 9.1/Supervisor, PAIS, NSX and Horizon validation is not established by synthetic tests. 中文:本轮修复和重构范围见上述说明;保留既有 Policy、审计及确认门。合成测试不代表 9.1/Supervisor、PAIS、NSX 或 Horizon 正向真机验证。 ## v2.4.5 — Security documentation and Gemini extension packaging Released 2026-11-09. Clarify actual capability, credential, local-write and transport boundaries where applicable. Pin documented setup/fallback tools and make credential examples owner-only before writing, without overwriting existing files. Runtime permissions, TLS defaults and approval behavior are unchanged. These edits do not establish a new ClawHub scan verdict. Add the root Gemini CLI extension manifest and discovery metadata, using the existing canonical skill and exact MCP package version. No duplicate skill entrypoint is introduced. Runtime infrastructure operations, permissions, TLS defaults and confirmation behavior are unchanged. No new vSphere/VCF 9.x or live product compatibility is claimed. ClawHub skill and bundle verdicts are checked separately after publication; documentation corrections are not a clean-scan claim. ## v2.4.4 — minimize notification and audit secrets (2026-11-08) * Update public author/project contact metadata to `zhouwei008@gmail.com`. ### notification secret minimization * Webhook status/transport logs and unexpected scan/send audit failures retain HTTP status or exception class without raw response/exception text. Connection-failure notifications no longer forward raw exceptions that can contain credential-bearing URLs or opaque tokens. ### scope declaration * Explicitly declare the existing datastore browsing/image discovery, multi-alarm reset and user-started scanning/TTL daemon capabilities in the canonical skill description and both READMEs. Runtime behavior, permission boundaries and risk disclosures are unchanged. ## v2.4.3 — bind confirmed writes to measured identities; reject ambiguous inventory names (2026-11-08) * **Confirmed MCP writes re-check the identities measured during that call.** The eight confirm-gated VM and snapshot tools pass the current blast-radius measurement's VM instance UUID and snapshot id into execution, refusing a replacement object found by the executor. Each `confirm=True` invocation measures afresh; the separate earlier preview supplies no identity token. The existing `delete_vm` acknowledgement requirement is unchanged, as are the CLI and plan-executor paths (AIops#49). * **Missing objects in delegated investigation tools keep their remedy.** VM, host and datastore lookup errors from vmware-monitor retain the exact-name/listing guidance through AIops MCP and are classified as request errors; unexpected delegate failures remain reduced to their type. * **Ambiguous host, cluster, datastore and datacenter names are refused.** A lookup that finds more than one match raises `AmbiguousNameError` with the matching managed-object ids; plan prechecks and CLI output expose the ambiguity instead of selecting the first result (AIops#48). * The image-cache and TTL cross-process file lock is exercised on a real Windows GitHub Actions runner: four concurrent writers retain every row, a held lock times out with the lock path, and the lock is released after the holder exits (AIops#51). * Require certifi 2026.10.19 or later to exclude the compromised emSign root CA from the TLS trust bundle (GHSA-6q48-6c7w-9gw2). The development lock resolves 2026.10.23 (AIops#53). ## v2.4.2 — a login refused for lack of a role says so; an unreadable host is not called unreachable (2026-10-19) Needs vmware-monitor 2.4.2 (now the floor). Found on a vCenter 8.0.3 lab with two restricted SSO accounts (follow-up to AIops#47). * **A login vCenter refuses with `NoPermission` gets the role error, not the raw fault.** For an account with no permission anywhere, vCenter refuses the login itself (`System.View` on the root folder), so the post-login no-role check never ran and `doctor` and every tool showed the raw fault with a connectivity hint. It now raises the no-role error: the "no vSphere role / Global Permissions" remedy only for a vCenter target and `System.View`; an ESXi target or another privilege gets a neutral text naming the (sanitized) privilege. A wrong password (`InvalidLogin`) is unchanged. Same text as vmware-monitor. * **vmk tools and `cluster info` no longer call an unreadable host unreachable.** When the account cannot read a host's connection state, `list_host_vmks` rows and the vmk write tools' refusal say the account cannot read the host (`reachable: false` means "not read"), instead of advising a reconnect. `cluster info` colours the state yellow, not red. * **From vmware-monitor 2.4.2 (through `get_cluster_health_summary` / `get_cross_vcenter_attention`):** a host this account cannot read is counted `hosts_unreadable` with a warning issue `host_unreadable`, not ranked as a critical `host_down`. ## Unreleased — follow-ups to the 2.4.1 review * **Previews show policy refusals.** `restore_vm_clean_slate`, `provision_vm_guest` and `run_vm_guest_command_with_output` were refused by `rules.yaml` only at `confirm=True`; the preview now lists that refusal in `blast_radius.blockers`, so the user sees it before agreeing. * **A delete that powers the VM off first is checked as `power_off_vm` too:** the CLI `vm delete`, the TTL daemon, and a plan rollback deleting the VM it created (checked when the VM is running or its state cannot be read). The TTL daemon records one `delete_vm` row per change of outcome, as for a deny on `delete_vm`, and keeps the entry. **Behaviour change for custom network roles:** a role that allows `set_vm_ttl` must also allow `power_off_vm`. * **Image cache:** writes take a lock across processes (a CLI and an MCP scan at the same time lost one side's rows); a scan keeps rows on datastores it failed to browse (its own or legacy ones) and drops this target's rows on datastores that no longer exist. A cache that cannot be written no longer fails the scan: the images come back with a `cache_note`, as in vmware-storage. * **Each call is checked for what it will do, at the moment it does it.** `restore_vm_clean_slate` asks about `power_off_vm` only when it actually powers the VM off; `provision_vm_guest` asks about the guest tools its step types use (an exec-only provision is not refused by a deny on `upload_vm_guest_file`); a plan rollback asks about the power-off after confirming it is the VM the plan created, just before powering it off. * **`set_vm_ttl` is checked as the deletion it schedules:** the preview lists a deny on `delete_vm` (and on `power_off_vm` for a running VM), and `confirm=True` refuses it, instead of scheduling a TTL the daemon can never act on. The daemon still asks again at expiry. * **`migrate_vm` previews run vCenter's own vMotion compatibility check** (`CheckMigrate_Task`, read-only): its errors (CPU features, devices, networks, datastores) become blockers, with the CPU-feature remedy where it applies, so a move vCenter would refuse is refused in the preview and before the CLI asks for confirmation; warnings are shown and do not block. On a standalone ESXi host, or when the check cannot run, the preview says "not checked" and does not block (vCenter checks again when the move starts). Plan migrate steps get the same check. * The TTL store takes the same cross-process lock as the image cache (`set_vm_ttl` and the daemon run in separate processes). ## v2.4.1 — fixes from the 2026-10-17 real-hardware read and write rounds * **Tools that run other tools' operations obey `rules.yaml` for those tools**, on the MCP and CLI surfaces. `restore_vm_clean_slate`, the batch tools, `provision_vm_guest`, and the deploy tools' `power_on` / snapshot / CPU-memory follow-ups are refused, with a `denied` audit row, when the policy denies the tool that does that part (a deny on `revert_vm_snapshot` used to leave clean slate working; a deny on `power_on_vm` did not stop `batch_clone_vms(power_on=True)`). A follow-up is checked only when the call asks for it. `run_vm_guest_command_with_output` is checked as `run_vm_guest_command` (and `download_vm_guest_file`) too. * **Behaviour change for custom network roles:** a role check reads no arguments, so a role that allows `deploy_vm_from_template`, `deploy_vm_from_ova` or `deploy_linked_clone` must now also allow `power_on_vm`, `create_vm_snapshot` and `reconfigure_vm` (and one allowing `run_vm_guest_command_with_output` must allow `run_vm_guest_command` and `download_vm_guest_file`), or every call is refused. The built-in `reader` and `admin` roles are unaffected. * **Plan steps obey `rules.yaml`.** Each step of `apply_vm_plan` / `rollback_vm_plan` is authorized by vmware-policy under the tool that does the same thing (a `delete_vm` step as `delete_vm`, `power_off` as `power_off_vm`, the rollback that deletes a VM the plan created as `delete_vm`), at that tool's risk level — in the preview and again just before the step runs, with a `denied` audit row. A deny on `delete_vm` used to stop the tool but not a plan step. * **Failures are failures.** Deploy, clone, linked clone, template conversion, ISO attach, `create_vm`, `migrate_vm`, `reconfigure_vm` and `configure_cluster` with nothing to change returned their refusals as status strings, which the MCP frame and the audit recorded as success; they now raise. A plan that stops part-way, a rollback that leaves a step not undone, and a plan that fails validation now carry the family `error` key. * **`deploy_vm_from_template` works on a vCenter with standalone hosts**: the clone gets the template's resource pool (it failed with `spec.location.pool`), and the tool now takes `target_host`. * **`scan_all_datastore_images` keeps each target's images**: the cache was one list replaced by every scan, so scanning `datastore1` on an ESXi host erased what a vCenter scan had found. Entries now carry `target`; a corrupt cache file is rebuilt by the next scan and explained by the error instead of a bare JSON error. * **`browse_datastore` sees every file** (`.ova`, `.ovf`, `.vmx`, `.log` were filtered out), `ds_path` is well formed for ESXi and vCenter folder paths, and a leading `/` is refused with the corrected path, as vmware-storage does. * Error text names MCP tools and parameters (CLI form in brackets); doctor is suggested only for connectivity errors; a CPU-feature vMotion refusal leads with the remedy; `get_vm_task_status` no longer says an unknown id means the task finished; powering off a VM that is already off is a plain `noop`. ## v2.4.0 — roles for network callers (HLD §18) * Declares the role metadata vmware-policy ≥ 1.24.0 needs to apply **roles** to this server over HTTPS (HLD §18): how a missing target resolves, the guest and host-file tools that escape a role, and what each composite tool can run — `apply_vm_plan` and `rollback_vm_plan` reach every tool a plan step maps to plus `delete_vm` (rollback), `set_vm_ttl` reaches `delete_vm`, the batch tools reach the deploy and clone tools and the follow-up steps their arguments ask for (`reconfigure_vm`, `create_vm_snapshot`, `power_on_vm`, and `attach_iso_to_vm` for a deployment spec); a test derives these from what the batch code calls. A role may allow a composite only if it allows all of them. A test fails when a new plan action is not mapped to a tool. With an older vmware-policy nothing changes. Once an operator runs `vmware-audit roles enable`, a token's role decides which tools and targets it may use here; a refused call is audited as `denied`. See vmware-policy's README, "Roles for network callers". * A target-limited role is checked against the same config the connection uses: the copy the server's connection manager loaded on its first connection. Edit `config.yaml` and restart for both to see the change. * README and setup guide: one endpoint for several people or roles (run `vmware-audit roles enable` first). ## v2.3.2 — tools may not touch this server's own control files * **Security fix (HLD §18 step 0).** guest file download/upload, OVA and deployment-spec paths could name this server's own files, so over HTTPS a token holder could overwrite `$OPS_HOME/mcp-tokens` or `rules.yaml`, or read a skill's `.env`. A path that resolves into `$OPS_HOME`, a `~/.vmware-*` directory, a `VMWARE_*_CONFIG` directory, or the server's token file or TLS key/certificate is now refused before anything is read or written. Over a network transport these tools read and write host files only inside `VMWARE_MCP_FILE_ROOT` (unset: refused). Needs vmware-policy 1.23.1. * `download_vm_guest_file` writes without following a link swapped in during the transfer (`O_NOFOLLOW`, a re-check after the transfer) and refuses a hard-linked destination; the `provision_vm_guest` preview no longer reports the size of a file the run would refuse. Uploads read the source the same way, and a FIFO no longer blocks either direction. * **`plan_id` can no longer name a path.** `apply_vm_plan` / `rollback_vm_plan` / `list`/`delete` built the plan file name from the caller's `plan_id`, and saving used the id stored in the file, so a crafted plan could be written to any `*.json` on the host. Plan ids are now `plan-` plus letters, digits and hyphens, and a plan file whose stored id differs from its name is refused. ## v2.3.1 — an account with no vSphere role is named, not passed (AIops#47) Needs vmware-policy 1.23.0, and the vmware-monitor release with the same fix for the `attention` part. * **A login with no vSphere role fails at connect time with the reason.** vCenter accepts the login but shows no session for it, and every call then fails with `NotAuthenticated`. The connection is now closed at once and `doctor`, the CLI and MCP tools say the account most likely has no vSphere role and to assign one (Read-Only or higher). Reported on vCenter 8.0.3; not yet reproduced on our own hardware. * Refused only when two signals agree: no session, **and** vCenter refuses a view of the root folder (`NotAuthenticated` / `NoPermission`). An account scoped to one folder can also show no session, and still connects; anything the check cannot read lets the login through. * **`NotAuthenticated` later on names both causes**: the session expired (retry), or the account's role was removed. It no longer says "verify connectivity and credentials". * **Every CLI command prints one line for a vSphere fault.** `summary` (and any other command) printed a raw traceback for faults other than a failed login; they now print the fault's message and what to check. * **`attention` never reports OK for targets it could not read** (computed by vmware-monitor): any unread target makes the overall status at least WARN, none read makes it CRITICAL, and the console and HTML say how many were not read. A role-less target is listed as unreachable with reason `NoVSphereRoleError`. ## Unreleased — OVA deploy honours `network_name` * **`deploy_vm_from_ova` / `deploy ova --network` now attach the NICs to the network you name.** The import spec carried no network mapping, so the argument had no effect and each NIC landed on whatever network the OVF descriptor named. Every network the OVF declares is now mapped to the named port group (standard or distributed). A name that matches no network, or more than one (a standard and a distributed port group can share a name), is refused before anything is imported, with the available names in the error. An OVF descriptor the server cannot parse is refused the same way. Found in the 2026-10-16 pre-release review. ## v2.3.0 — calls on one target run side by side; `get_access_info` Needs vmware-policy 1.22.0. * **Calls no longer queue behind each other on a shared server.** Served over `--transport https`/`http`, calls run side by side with up to `VMWARE_MCP_POOL_SIZE` (default 2) sessions per target; each call holds its own session for its whole run, so two calls never share one. Each session is a login on the target. A call that finds every session busy waits at most 300 s, then fails with a message saying so. stdio and the CLI keep one session per target, as before. * **Side-by-side calls no longer race on local state.** Two TTL changes at once could drop each other's entry (and a reader catching the file mid-write saw no entries at all); the TTL store and the image registry are now written atomically and TTL updates are serialized. One plan can no longer be applied or rolled back by two calls at once: the second is refused with a message, and nothing runs twice. * **The daemon's scan and TTL check each use their own session.** They run on APScheduler's thread pool and could overlap on one pyVmomi session. * **New tool `get_access_info`** (CLI `vmware-aiops access-info [--tool X] [--target T]`): which account the server uses on a target, and whether it holds the privileges each tool needs, e.g. `--tool clone_vm`. It asks vCenter about its own session (System.View is enough). Checked on the root folder: `not_granted_at_root` can still be granted on a cluster, folder or VM, and `undetermined` is never read as granted. 61 tools (was 60). ## v2.2.0 — serve MCP over HTTPS `vmware-aiops mcp` can now serve the network as well as stdio (HLD §16, via vmware-policy 1.21.0): ```bash vmware-audit token create alice # once per caller; shown once vmware-aiops mcp --transport https --host 0.0.0.0 --port 8443 \ --cert server.pem --key server-key.pem --public-name mcp.example.com ``` Connect a client and manage callers (Claude Code shown; any MCP client with streamable HTTP and custom headers works): ```bash claude mcp add --transport http vmware-aiops https://mcp.example.com:8443/mcp \ --header "Authorization: Bearer $VMWARE_MCP_TOKEN" vmware-audit token list # names only vmware-audit token revoke alice # refused from its next request, no restart ``` * Every request needs `Authorization: Bearer `; the server will not start without a token, refuses Host or Origin values outside `--public-name`, and checks itself (unauthenticated requests must get 401) before it binds. `--transport http` binds loopback only, for a TLS-terminating reverse proxy. * Audit rows record the caller as `token:` and the surface as `mcp-https`. Calls naming different targets run in parallel; calls naming no target run one at a time. * stdio remains the default; the `vmware-aiops-mcp` console script and `python -m` are unchanged. * Everyone who can reach the endpoint acts with this server's account; run one endpoint per privilege level. * Requires `vmware-policy>=1.21.0` and `mcp>=1.28`. ## v2.1.0 — offer only read tools, plus chosen write tools `vmware-aiops mcp` can now offer a subset of this server's tools (HLD §17 D-16, via vmware-policy 1.20.0): ```bash vmware-aiops mcp --expose read --allow-write acknowledge_vcenter_alarm ``` * `--expose read` (or `VMWARE_AIOPS_MCP_EXPOSE=read`) keeps only tools annotated `readOnlyHint: true`; `--allow-write a,b` (or `VMWARE_AIOPS_MCP_ALLOW_WRITE`) keeps chosen write tools on top. The default is unchanged: every tool. * A bad value, or a name that is not a write tool of this server, stops the server before it serves (exit 2). Flags and environment variables are not mixed; a blank flag counts as not given. Pre-2.0 names are accepted. * This limits what clients are offered; it is not authorization. See "Limiting which tools are offered" in `references/setup-guide.md`, which also notes that an agent with a shell can bypass MCP, so the account itself must be least-privilege. * Requires `vmware-policy>=1.20.0`. ## v2.0.0 — MCP tools renamed verb-first **Breaking for MCP clients: 38 of the 60 tools have new names.** The family now names every MCP tool one way — verb first (`power_on_vm`, `get_cluster_info`, `investigate_vm`) — with a domain word where two skills would otherwise collide (`scan_all_datastore_images` here, because vmware-storage's `scan_datastore_images` scans one datastore). Parameters, annotations and behaviour are unchanged; no tool was added or removed. The six triage/investigation tools that reuse vmware-monitor's code (`get_cluster_health_summary`, `get_cross_vcenter_attention`, `investigate_vm`, `investigate_host`, `investigate_datastore`, `list_vm_snapshots`) carry the same names and parameters as Monitor's. | Old name | New name | |---|---| | `cluster_add_host` | `add_cluster_host` | | `cluster_configure` | `configure_cluster` | | `cluster_create` | `create_cluster` | | `cluster_delete` | `delete_cluster` | | `cluster_health_summary` | `get_cluster_health_summary` | | `cluster_info` | `get_cluster_info` | | `cluster_remove_host` | `remove_cluster_host` | | `cross_vcenter_attention` | `get_cross_vcenter_attention` | | `datastore_investigation_bundle` | `investigate_datastore` | | `host_investigation_bundle` | `investigate_host` | | `vm_investigation_bundle` | `investigate_vm` | | `vm_apply_plan` | `apply_vm_plan` | | `vm_cancel_ttl` | `cancel_vm_ttl` | | `vm_clean_slate` | `restore_vm_clean_slate` | | `vm_clone` | `clone_vm` | | `vm_create` | `create_vm` | | `vm_create_plan` | `create_vm_plan` | | `vm_create_snapshot` | `create_vm_snapshot` | | `vm_delete` | `delete_vm` | | `vm_delete_snapshot` | `delete_vm_snapshot` | | `vm_guest_download` | `download_vm_guest_file` | | `vm_guest_exec` | `run_vm_guest_command` | | `vm_guest_exec_output` | `run_vm_guest_command_with_output` | | `vm_guest_provision` | `provision_vm_guest` | | `vm_guest_upload` | `upload_vm_guest_file` | | `vm_list_plans` | `list_vm_plans` | | `vm_list_snapshots` | `list_vm_snapshots` | | `vm_list_ttl` | `list_vm_ttls` | | `vm_migrate` | `migrate_vm` | | `vm_power_off` | `power_off_vm` | | `vm_power_on` | `power_on_vm` | | `vm_reconfigure` | `reconfigure_vm` | | `vm_revert_snapshot` | `revert_vm_snapshot` | | `vm_rollback_plan` | `rollback_vm_plan` | | `vm_set_ttl` | `set_vm_ttl` | | `vm_task_status` | `get_vm_task_status` | | `vmk_ping` | `ping_from_vmk` | | `scan_datastore_images` | `scan_all_datastore_images` | * **CLI commands are unchanged.** The audit record a CLI command writes carries its MCP twin's new name. * Deny rules in `~/.vmware/rules.yaml` written with old names keep working with vmware-policy ≥ 1.18.0 (it matches both names); `vmware-audit policy` lists any rule that now matches more. * Plan files are unaffected: plan step actions (`cluster_add_host`, `storage_rescan`, …) are action names, not tool names, and keep their spelling. * **Daemon audit rows say `daemon`.** The TTL / scan daemon's audit rows carried no surface and read as MCP stdio calls; they now record `surface=daemon` (vmware-policy 1.18.0 audit column, HLD §16 D-14). * Requires **vmware-monitor 2.0**: the investigation and summary tools reuse Monitor's ops layer, whose drill-down hints now name the 2.0 tools. ## v1.12.1 — `reset_vcenter_alarm` previews before it clears * **`reset_vcenter_alarm` cleared alarms on the first MCP call.** vSphere has no per-alarm clear: `ClearTriggeredAlarms` clears every triggered alarm with the named alarm's status on its entity type (host / VM / all), usually far more than the one named. The tool was published `destructiveHint: False`, so it sat outside the HLD §7 confirmation gate. It is now `destructiveHint: True` and takes `confirm: bool = False`. A bare call returns `blast_radius` with `will_clear_count` and up to 50 of the alarms it would clear, measured the way the filter matches (same status, same entity type, de-duplicated), and changes nothing. `confirm=True` re-measures and clears; it is refused when the alarm list cannot be read. The CLI's `alarm reset` is unchanged (it already confirms twice). Raised by ClawHub's review of 1.12.0. * **The clear uses exactly the filter the preview measured.** A confirmed call no longer looks the alarm up a second time and rebuilds the filter. Alarm names are read only for the 50 listed, so one unreadable alarm definition shows as `` instead of failing the call. * **CLI `alarm reset`** now prints the same list (in `--dry-run` and before its double confirmation), refuses when it cannot read which alarms it would clear, clears with the measured filter, and reports how many it cleared. * Gate inventory: 23 of the 43 write tools now preview by default (was 22); 20 act on the first call. * vmware-pilot 1.12.1 labels the tool `high`, matching the new annotation. ## v1.12.0 — the MCP instructions name the configured targets `initialize` hands the client this server's `instructions`, and for a skill whose every tool takes a target that text is the only place the client learns which targets exist. Until now this one shipped a static description that never named them. The model then called tools with no target, got whatever the default was, and answered confidently about the wrong system — measured in vmware-monitor on 2026-09-15, where a standalone ESXi host was the default, so "how many VMs does the vCenter have" was answered from that host. The instructions now carry, built from the loaded config on every server start: * **`Configured targets:`** — each configured target with its host, and which one answers when none is named. * **`Choosing a target:`** — how to pick one from what the user asked, and to ask rather than guess when the request does not say and the targets would answer differently. **A config that cannot be read is said out loud, not dropped.** On a machine that has not run `init` yet — every new install — this server now reports `Configured targets: could not be read (FileNotFoundError) — run `vmware-aiops doctor`.` instead of falling silent. A client shown no listing cannot tell "this skill has no targets worth naming" from "this skill could not read them", and only the first reading produces a confident answer about a system nobody chose. Only the error's type is included; its text quotes the config path. No tool, parameter or result shape changed. A new family gate (`mcp_instructions_name_targets.py`, in `family_smoke`) probes every server under an empty HOME, so the check runs in the state a new user is in rather than the one the developer happens to be in. ## v1.11.0 — destructive MCP tools preview by default HLD §7, second batch. `vm_delete` (v1.10.0) and the seven host-network/DRS tools already previewed; now every MCP write tool annotated `destructiveHint: true` does. 22 of the 43 write tools take `confirm: bool = False`; the other 21 still act on the first call: creates, clones and deploys (`vm_create`, `vm_clone`, `batch_*`, `deploy_*`, `cluster_create`), `vm_power_on`, `vm_reconfigure`, `vm_create_snapshot`, `cluster_add_host`, `cluster_configure`, `convert_vm_to_template`, `attach_iso_to_vm`, `vm_guest_download`, `vm_cancel_ttl`, `vm_create_plan`, and the two alarm tools. Six of the 22 are not annotated destructive but take `confirm` anyway (`vm_migrate` and the five network/DRS authoring tools). ### Breaking changes (MCP callers) * **14 more tools preview by default**: `vm_power_off`, `vm_migrate`, `vm_revert_snapshot`, `vm_delete_snapshot`, `vm_clean_slate`, `vm_set_ttl`, `vm_guest_exec`, `vm_guest_exec_output`, `vm_guest_upload`, `vm_guest_provision`, `cluster_delete`, `cluster_remove_host`, `vm_apply_plan`, `vm_rollback_plan`. A bare call measures with reads only and returns `{"action": "preview", "blast_radius": {...}}` without changing anything. They used to act on the first call. A vmware-pilot step calling one of them now previews unless it passes `confirm=True`. * **Return type changed from string to dict**: `vm_power_off`, `vm_migrate`, `vm_revert_snapshot`, `vm_delete_snapshot`, `cluster_delete`, `cluster_remove_host`, `vm_set_ttl`, `vm_clean_slate`, `vm_guest_upload`. * **`confirm=True` re-measures and refuses** — a teaching error, audited as a failure — when the measurement found a blocker or could not read a field it needs: * guest tools: VM not powered on; VMware Tools not running; a local upload source missing or unreadable; a malformed `vm_guest_provision` step; a `service` step on a Windows guest (it runs `systemctl`). * VM tools: graceful shutdown without running VMware Tools, or of a suspended VM (`force=True` is a hard power-off — preview it); a migration target host that is missing, disconnected, in maintenance mode, outside a cluster (no resource pool) or cannot reach the VM's storage; a `to_datastore` that is not found; a VM with no current host; a snapshot that is not found or whose name is duplicated. * cluster tools: hosts or VMs still in the cluster; a host that is not in maintenance mode or still runs powered-on VMs. * `vm_clean_slate`: the baseline snapshot missing or ambiguous — refused before anything happens, so a failed Clean Slate no longer powers the VM off first and then fails. * plans: `target` not matching the plan's; a `delete_vm` step without `acknowledge_blast_radius`, refused before step 0 rather than half-applied. Only `vm_delete` additionally requires `acknowledge_blast_radius`. ### Other changes * `vm_apply_plan` is annotated `destructiveHint: true` (a plan can delete VMs). * The gated tools file undo tokens only when the call actually changed something — never for a preview, a no-op, or a shutdown that did not finish. Ungated tools are unchanged (`vm_power_on` still files one on every success). * Requires `vmware-policy>=1.17.0`, which audits a `confirm=False` preview as `dry_run` and redacts long audit text in linear time. * `vm_set_ttl` now connects to vCenter to measure the VM it will delete and refuses when it cannot read it; it used to only write the local TTL file. * Blast radius and blockers live in `vmware_aiops/ops/{vm_gate,guest_gate,cluster_gate,plan_gate,vm_delete_gate}.py` and `ops/ttl.py`. * The CLI keeps its double confirmation and `--dry-run`. It shares the ops layer, so it now also refuses a duplicated snapshot name in revert/delete, `clean-slate` refuses a missing baseline before powering the VM off, and the TTL daemon deletes only a VM whose recorded instance UUID still matches. * **Review fixes (2026-09-19)**: * A plan created without `target` is refused on a named target (the default was treated as a wildcard). * `vm_apply_plan`'s preview shows every step's full parameters, secrets redacted. Each destructive step is measured by its own tool's gate and refuses the plan on that tool's blockers; a step on something an earlier step creates or changes is shown `check: "deferred"`, and every destructive step is re-checked immediately before it runs — a failed check stops the plan. * Guest commands, arguments and paths longer than the preview shows are refused (they used to be shown cut and run whole); previews carry `command_length` / `arguments_length` and `truncated`. * Refusal messages lead with the first blocker and its remedy, count the rest, and stay within 480 characters. * `vm_clean_slate` reports an error when the revert did not happen. A snapshot name now matches its raw or displayed (sanitized) form in the gate and the executors alike, and a name matching two snapshots is refused by `revert_to_snapshot` / `delete_snapshot` (CLI and plans too) instead of taking the first. * TTL entries record `instance_uuid`; the daemon deletes only a VM that still has it (older entries without one behave as before). `vm_set_ttl` keeps every `vm_delete` blocker except the power state. * Plan rollback deletes a created VM only if its instance UUID matches the one recorded when the step ran. * `vm_create_plan` / `vm_apply_plan` responses redact step passwords. * **Narrow-review fixes (2026-09-19)** — a plan is never a way around a tool's gate: * **Breaking for plans**: a plan containing `iscsi_enable`, `iscsi_add_target`, `iscsi_remove_target` or `storage_rescan` is refused at preview, apply and rollback (those tools are gated in vmware-storage, which AIops cannot measure; `iscsi_remove_target` used to run "not measured"). Use `storage_iscsi_*` / `storage_rescan` in vmware-storage. The actions stay loadable so existing plan files still open. * A plan `migrate` step is measured as `vm_migrate` measures it and re-checked just before it runs. * Every executor action is classified (measured, refused, or non-destructive with a reason); the step check fails closed on anything else. A test walks the dispatch table. * `vm_rollback_plan` measures each destructive rollback step in its preview (`check`, `measured`, blockers prefixed `Step N (rollback action):`) and again just before it runs. A refused check stops the rollback: the step is reported `refused`, nothing after it runs, the response carries `stopped_at_step` and a hint, and the plan stays `failed` so rollback can be run again. * Step-parameter redaction matches key tokens (`pwd`, `auth`, `db_pwd`, `authToken` now redacted; `bypass`, `passthrough` no longer hidden). * Network and DRS previews carry `blockers` / `unmeasured`; `remove_host_vmk`, `set_vmk_service` and `delete_drs_rule` report their refusals there instead of raising on a preview (they still raise on confirm). * **Final-review fixes (2026-09-19)** — plan rollback can always undo what the plan itself made: * The rollback of a plan `power_on` step is a **hard** power-off (`force: true`). A graceful one needs VMware Tools, which a VM the plan just created never runs, so a `create_vm` → `power_on` plan used to stop its rollback before the `delete_vm` that removes the VM. * A plan step (forward or rollback) whose measurement says `noop` — a `power_off` of a VM already off, a `migrate` to the host it is on — passes its check, as the tool itself returns `noop`; the preview shows it `check: "measured"` with a `check_note`. * A created VM whose instance UUID was not recorded (or whose name now belongs to a different VM) stops the rollback at that step as `refused`, with `stopped_at_step`; the plan stays `failed`, as the preview said. It used to be reported `failed`, the rollback continued and the plan ended `rolled_back`. ## v1.10.0 — `vm_delete` previews what it destroys, and a duplicated VM name is refused The family security HLD §7 was revised on 2026-09-16: every MCP tool that destroys something not restorable moves to one argument, `confirm`, whose default is a no-write preview. Decision D-2 (2026-07-21), which cut a confirmation handshake as a speed-bump, is superseded. A confirmation is still not authorization — the vCenter account is — but a preview is what stops an agent acting on a guess. `vm_delete` is the first tool on the full design; the other destructive tools follow in later releases. * **`vm_delete` now previews by default** (breaking for MCP callers). A bare call returns `blast_radius` — VM, instance UUID, host, power state, disks, total size, snapshot count, blockers — and deletes nothing. It used to delete on the first call. A vmware-pilot workflow step that calls `vm_delete` now previews rather than deletes; no shipped Pilot template or rollback uses it. * **Deleting takes `confirm=True` plus `acknowledge_blast_radius`**, set to the preview's `acknowledge_with` (instance UUID, disk count, snapshot count — keys that do not drift on their own). The VM is re-measured first and the call refused if any of them changed: another snapshot taken, or a different VM recreated under the same name. The object destroyed is the one measured; there is no second lookup by name. * **Refused outright**: a powered-on or suspended VM (the docstring always said "must be powered off", while the code powered it off for you — power it off with `vm_power_off` first); a VM whose identity, disks or snapshots cannot be read. * **`vm_apply_plan` is not a way around it.** A plan's `delete_vm` step goes through the same gate: it needs `acknowledge_blast_radius` from a `vm_delete` preview and is refused without it. Rolling back a failed plan still deletes the VMs that plan itself created. * The TTL daemon drops an entry whose VM name has become ambiguous, without deleting anything, instead of retrying it every cycle. * A template and a VM with the same name now count as a duplicate too, so `deploy_from_template` / `convert_to_vm` refuse until one is renamed. * **A name that matches more than one VM is refused** everywhere this skill looks a VM up by name (`AmbiguousVMError`), instead of acting on whichever the property collector listed first. vSphere allows duplicate names across folders, and every caller of that lookup goes on to change the VM it gets back. `vm_create_plan` reports it as a step error. Hosts, datastores and clusters still resolve to the first match (follow-up). * **The CLI `vm delete` prints the same blast radius** before its two prompts. It still powers a running VM off before deleting it, and the TTL daemon's deletion is unchanged. * **Guest tools' `risk_level` raised** on both surfaces: `vm_guest_exec`, `vm_guest_exec_output`, `vm_guest_provision` medium → critical; `vm_guest_upload`, `vm_guest_download` medium → high. This changes which `deny` rules match them and what the audit row records, not whether they run. * Documentation that described the old contract — "no confirmation over MCP, by design", "a preview that one `confirm=True` call skips", "a preview, not an approval" — is rewritten in SKILL.md, the references and both READMEs. ## v1.9.7 — structured results say which target answered, and the server says which targets exist Found in a scenario test on 2026-09-16: asked for a VM's investigation bundle *and* to say where the data came from, the answer named `home-vcenter` — but the tool result carried no target at all. The model had read the name off the argument it chose itself. A call that omits `target` reaches whichever target the config lists first, and its result was indistinguishable from one read anywhere else; vmware-monitor fixed the same thing in 1.15.0. * Results from a tool that takes `target` now carry `target: {name, type}` — added by the shared tool registration rather than declared per tool, because a per-tool marker is one some tool always forgets. Dict results carry it at the top level; `batch_*` results carry it on each row. Error payloads that are dicts carry it too: which target was tried is most of the diagnosis. * **The 24 tools that answer in prose do not carry it**, and are not rewritten to: their return value is a sentence for a person (`"Powered on VM 'web-01'."`), and prefixing a label would change the output contract of every write in this skill. For those, the rule now lives in the server instructions below. * **The server instructions list the configured targets** (name, type, host, which is the default), tell the agent to choose one from the question rather than by accident, to ask when the request is ambiguous and the targets would answer differently, and to say in its answer which target replied. Half of vmware-monitor 1.15.0 was the payload and half was this; only the payload half shipped in the first draft of this release (independent review, same day). * **`create_plan` / `apply_plan` already returned a top-level `target`** — the raw argument, `null` when the call omitted it. That is now the resolved `{name, type}` like everywhere else, so the key holds one type across sibling tools; the plan file on disk is unchanged. * **Tool schemas are unchanged.** The label is added to results, never to the call signature: a test pins every registered tool's parameters against its own function signature, and the 60 tools' schemas, output schemas and annotations were diffed against the previous release. * A result that already names its target is left alone, as are non-dict results and tools with no `target` parameter. A config too broken to resolve a name costs the label, not the answer. ## v1.9.6 — a stopped MCP server exits within five seconds, even if its logout hangs > Never published on its own — this fix ships in 1.9.7 (there is no `vmware-aiops==1.9.6` on PyPI). A correction to 1.9.5, from an independent review on 2026-09-15. 1.9.5 made the server log out when Claude Code stops it: the first stop signal ignored further stop signals and ran the `atexit` logout. The logout had no time limit. pyVmomi connects with `httpConnectionTimeout=None`, so a logout to a vCenter that stopped answering, or one waiting on the SOAP connection lock a tool call held when the signal arrived, kept the server running and deaf to every further stop signal until something sent SIGKILL. Before 1.9.5, SIGTERM at least ended it. * The logout now runs on a worker thread and gets 5 seconds. If it has not finished, the server writes one line to stderr without blocking (a full pipe cannot hold the exit) and exits with 128 + signal anyway; vCenter or ESXi ends that session when it idles out. * New test: the real server with stdin held open, an `atexit` callback that blocks for ten minutes, then SIGINT and SIGTERM. The server must exit within 15 seconds and say it gave up on the logout. It failed on 1.9.5. * Lab, on this code: a conversation that called this server's `list_vcenter_alarms` and `vm_list_snapshots` against vCenter 8.0.3 left no session behind; afterwards both session lists held only the counting call's own session. ## v1.9.5 — stopping the MCP server logs out its vCenter session Measured in real Claude Code conversations against the lab vCenter 8.0.3 / ESXi 8.0.3 on 2026-09-15: Claude Code stops a stdio MCP server with SIGINT and then SIGTERM about a millisecond later, with stdin still open. Python's default SIGTERM ended the server before `atexit`, so the vSphere logout the connection layer registers never ran and every conversation left its session open — 13 root sessions on one ESXi host and 11 Administrator sessions on vCenter in about eleven minutes. **The server now logs out when it is stopped.** The first stop signal ignores the rest, runs the `atexit` callbacks (the `Disconnect`), and exits with status 128 + signal. Raising `SystemExit` from the handler was tried first and is not enough: the interpreter then waits on the thread reading stdin and hangs without logging out. The new test starts the real server with stdin held open, completes the MCP handshake and sends the same two signals; it failed on the `SystemExit` version. After the change, conversations against the lab left no session behind. ## v1.9.4 — the scanner daemon's calls are audited, the TTL delete first A family survey on 2026-09-15 found that the scanner daemon deleted VMs whose TTL had expired with no row in either audit trail, on success or failure, and without the `guard()` that stops the same deletion over MCP or the CLI. Its scan cycles against vCenter and its webhook sends left no row either, and `daemon start` was marked `@cli_local`, so the family's CLI gate could not see it. * **TTL delete.** Before deleting, the daemon passes `guard()` as `vm_delete` (risk `critical`), so a deny rule on `vm_delete` now stops an expiry too; the entry is kept for when the rule is lifted. Each attempt writes one `vm_delete` row with `trigger: ttl_expiry`: `ok` when deleted, `denied` when refused, `error` when the delete failed (entry kept for retry) or the VM was already gone (stale entry dropped). A policy check that fails is an `error` and skips the delete. * **Scan cycle.** One `daemon_scan` row per cycle, `error` when a connection or a pass failed, with the failed passes in the result. * **Webhook.** One `webhook_send` row per send, `error` when it was not delivered. The URL is not recorded — it can carry a token. * **No row flood.** The TTL check runs every minute, so a refused or failing entry records its outcome once and again only when it changes (a different status, refusing rule or error class); every successful delete is recorded. Retries still happen every minute. Without this, an entry refused by a deny rule wrote 1,440 rows a day. * **Interrupts are `interrupted`.** Ctrl+C during a scan or a webhook send (the first scan runs before the signal handlers are installed) records `interrupted` instead of `ok`. * `daemon start` is `@audited` (`daemon_start`). Its row is written when the daemon stops. Tests: `tests/eval/regression/test_daemon_calls_are_audited.py` (12 tests, each red before its fix; suite 594 passed). Works with `vmware-policy` 1.15.0: the daemon passes each status explicitly. ## v1.9.3 — CLI reads are audited No CLI read wrote `~/.vmware/audit.db` — only MCP calls and CLI writes (`@guarded`) did. A live `vmware-aria resource list` left the audit row count unchanged while the same read over MCP added a row. Every CLI command that reaches vCenter now passes the same `guard()` as its MCP twin and writes one audit row under that tool's name (17 commands, `@audited`). Commands that reach nothing remote say so, with a reason (8, `@cli_local`). A family gate now fails any CLI command that declares none of `@guarded` / `@audited` / `@cli_local`. **Behaviour change:** a deny rule in `~/.vmware/rules.yaml` whose `operations` name a read tool now stops that CLI command too, as it already stopped the MCP call. Docs that said only MCP calls (or only CLI writes) reach `audit.db` are corrected. Requires `vmware-policy>=1.15.0`. ## v1.9.2 — the health summary names over-committed datastores `cluster_health_summary` and `vmware-aiops summary` delegate to vmware-monitor, and vmware-monitor 1.13.0 adds datastores thin-provisioned past 100% of capacity to `top_issues`, moving the owning row's status with it. This release requires `vmware-monitor>=1.13.0` so that behaviour is the one installed, and the tool description now says datastores are included — it listed hosts, VM power, CPU/memory and alarms only. The SKILL.md sentence about where the service account's password is kept is reworded. ClawHub's static scanner read "Its password: `~/.vmware-aiops/.env`" as a hardcoded secret (`suspicious.exposed_secret_literal`, the only static finding behind the bundle's `suspicious` rating). No secret was ever in the file; the guidance is unchanged. ## v1.9.1 — the event sweep and scanner read and rank real events **The event sweep and the daemon's event scan rank real vCenter events.** AIops compared its bare-name event sets (`HostConnectionLostEvent`) with `type(event).__name__`, which on a real pyVmomi event is `vim.event.HostConnectionLostEvent`; nothing matched, so every event ranked "info" and the event scan, which keeps critical and warning only, found nothing. Found on a lab vCenter 8.0.3; the tests had used stand-ins named with the bare name. **The event sweep and the scanner read the newest events.** `ops/health.get_recent_events` and `scanner/log_scanner.scan_logs` called `QueryEvents`, which on vCenter returns only the oldest 1000 events in the window (measured on vCenter 8.0.3), so a busy window hid its latest hours. Both now use vmware-monitor's shared read, which walks an event history collector newest first. **Release note for packaging:** this needs the vmware-monitor release that ships `read_events` — raise the `vmware-monitor` lower bound in `pyproject.toml` to that version when both are published. ## v1.9.0 — guest operations name their account; CLI writes answer to the same rules **BREAKING: guest operations require `username`.** `vm_guest_exec`, `vm_guest_exec_output`, `vm_guest_upload`, `vm_guest_download` (MCP) and `vm guest-exec/-upload/-download` (CLI) defaulted to `root`, so an omitted argument ran as root without anyone choosing root. A call that relied on the default now fails and says why. The docs now state the security model exactly: MCP write tools act on the first call, and the boundary is the RBAC of the account the server connects with; the CLI's double confirmation and `--dry-run` do not apply to MCP calls. Several claims that overstated a gate were corrected. Requires vmware-monitor >= 1.11.3, which carries the cluster-count fix this skill delegates to: `cluster_health_summary` / `vmware-aiops summary` no longer count the standalone-host row as a cluster. (AIops has no `host_log_scan` tool; the daemon's host-log pass runs AIops's own copy of the scanner, fixed in this release as described next.) **The daemon's host-log pass never read a host log.** AIops keeps its own copy of the host-log scanner for the daemon, and the copy called `BrowseDiagnosticLog` on the host's `configManager.diagnosticSystem` — a type that has no such method. Every read raised, the error was swallowed, and every cycle reported the host logs clean. It now reads through `content.diagnosticManager` (naming the host through vCenter, not on a standalone ESXi). Run live read-only: 228 findings on a vCenter and 227 on a standalone ESXi where the old code found none, with hostd, vmkernel and vpxa all readable on both. A log that cannot be read is written to the scan log as an `info` issue (`host_log:unavailable`, with the reason — e.g. the missing `Global.Diagnostics` privilege) and as a WARNING naming the host, log and fault in the daemon log, instead of disappearing. Only vSphere faults and connection errors count as "could not be read": a bug in the scanner now fails the pass loudly instead of being filed as one more unreadable log, which is how the original bug stayed hidden. **The daemon reports each host-log line once, and pages only critical ones.** Reading for real, every 15-minute cycle re-read the last 500 lines of each log and reported the same lines again — of those 228 lines, 135 were a single routine hostd line — and every warning went to the webhook. The daemon now remembers, in process memory, the last line it read of each log (per target, host and log) and reads only what came after. After a restart, the first cycle reads the last 500 lines again. A log whose line count went down has rotated: the daemon reads its last 500 lines and writes an `info` issue (`host_log:skipped`) saying lines written between its previous read and the rotation were not scanned. More than 500 new lines in one interval: the newest 500 are read and a `host_log:skipped` issue says how many were skipped. Host-log **warnings are written to the scan log only; host-log criticals** (lines containing critical/panic/corrupt) **still go to the webhook**, as do alarm and event warnings, unchanged; `info` issues never do. If you watched host-log warnings in the webhook, read them in the scan log now. The cycle summary counts findings, unreadable host logs and failed passes separately, and a cycle in which any pass raised ends with a WARNING "Scan INCOMPLETE" naming the pass — never "all clear". *With a Read-Only account* (vCenter's Read-Only role lacks `Global.Diagnostics`), no host log can be read: each cycle writes one `host_log:unavailable` issue per host and log to the scan log and a WARNING per log to the daemon log, pages nothing for it, and the summary shows the unreadable count instead of "all clear". **CLI writes are authorised and audited under their MCP tool names.** A deny rule in `~/.vmware/rules.yaml` names an operation, and 23 guarded CLI commands were checked under their Python function names instead — so a rule against `vm_guest_exec` stopped the agent and let `vm guest-exec` do the same thing from a shell. One rule now scopes both surfaces: | CLI command | Operation name (was) | risk | |---|---|---| | `alarm acknowledge` | `acknowledge_vcenter_alarm` (`alarm_acknowledge`) | medium | | `alarm reset` | `reset_vcenter_alarm` (`alarm_reset`) | medium | | `cluster create` | `cluster_create` (`cluster_create_cmd`) | medium | | `cluster delete` | `cluster_delete` (`cluster_delete_cmd`) | high | | `cluster add-host` | `cluster_add_host` (`cluster_add_host_cmd`) | medium | | `cluster remove-host` | `cluster_remove_host` (`cluster_remove_host_cmd`) | medium | | `cluster configure` | `cluster_configure` (`cluster_configure_cmd`) | medium | | `cluster drs-rule-set` | `set_drs_rule_enabled` (`cluster_drs_rule_set_cmd`) | medium | | `cluster drs-rule-create` | `create_drs_rule` (`cluster_drs_rule_create_cmd`) | medium | | `cluster drs-rule-delete` | `delete_drs_rule` (`cluster_drs_rule_delete_cmd`) | high | | `deploy ova` | `deploy_vm_from_ova` (`deploy_ova_cmd`) | medium | | `deploy template` | `deploy_vm_from_template` (`deploy_template_cmd`) | medium | | `deploy linked-clone` | `deploy_linked_clone` (`deploy_linked_clone_cmd`) | medium | | `deploy batch` | `batch_deploy_from_spec` (`deploy_batch_cmd`) | high | | `deploy batch-clone` | `batch_clone_vms` (`deploy_batch_clone_cmd`) | medium | | `deploy mark-template` | `convert_vm_to_template` (`deploy_mark_template`) | medium | | `deploy iso` | `attach_iso_to_vm` (`deploy_iso_cmd`) | medium | | `vm snapshot-create` | `vm_create_snapshot` (`vm_snapshot_create`) | medium | | `vm snapshot-revert` | `vm_revert_snapshot` (`vm_snapshot_revert`) | high | | `vm snapshot-delete` | `vm_delete_snapshot` (`vm_snapshot_delete`) | high | | `vm guest-exec` | `vm_guest_exec` (`vm_guest_exec_cmd`) | medium | | `vm guest-upload` | `vm_guest_upload` (`vm_guest_upload_cmd`) | medium | | `vm guest-download` | `vm_guest_download` (`vm_guest_download_cmd`) | medium | The other ten guarded CLI writes (`vm power-on`, `vm delete`, `vm clone`, …) already carried their MCP tool's name and are unchanged. Risk levels were already equal to the MCP tools' and are unchanged. **A rule you wrote against an old name no longer matches** — a deny rule naming `vm_guest_exec_cmd` or `deploy_ova_cmd` now stops nothing; rewrite it to the MCP tool name in the middle column. **Audit rows for these commands carry the new names from this release on**; rows written before it keep the old ones, so a query over `~/.vmware/audit.db` that spans the upgrade needs both. A regression test derives each command's MCP twin from the ops function both call and fails if the names or risks drift. **Environment-scoped deny rules now apply to CLI writes.** The skill's environment resolver was registered only when the MCP server was imported, which the CLI never does — so a `freeze-production-writes` rule stopped the MCP tool and not the CLI command doing the same thing. It now lives in `policy_environment.py`, imported by both surfaces. (With vmware-policy 1.13.1 the CLI's `--config` file is the one whose labels are judged.) **OpenClaw could not show this skill to the model.** `metadata.openclaw.requires` listed config *file paths* under `requires.config`, which OpenClaw reads as `openclaw.json` keys that must be truthy — so the skill was "needs setup / not visible to the model" whatever was on disk (verified on OpenClaw 2026.6.35). `requires.env` named an optional override and `requires.bins` demanded a CLI that a plugin install (uvx) never has. `requires` is now `anyBins: [, "uvx"]`; the variables are still declared, under `optional.env`. **Install commands in the skill pin this release.** ClawHub reviews SKILL.md and references/, not the package they install, so an unpinned `uv tool install` vouched for code nobody reviewed. Every install command for this package in the skill now names this version. **A config path written as `~/…` now resolves.** Every MCP example config and setup-guide snippet sets `VMWARE_AIOPS_CONFIG` to `~/.vmware-aiops/config.yaml`, but MCP clients pass env values verbatim and the path was used unexpanded, so copying the snippet gave "Config file not found" for a file that was there. `~` is now expanded in the variable and in `--config`. ## v1.8.22 — a dropped connection no longer keeps itself alive Every `connect()` registered an `atexit` cleanup that closes over the ServiceInstance, and `atexit` held that closure — and so the SI, its stub and its socket — until the process exited. Nothing ever unregistered it. A long-running MCP server that reconnects after each session expiry therefore pinned one dead connection per reconnect, and at exit would run a `Disconnect` against every session it had ever opened. Measured before the fix: 20 evict-and-reconnect cycles left all 20 evicted ServiceInstance objects reachable. The `id(si)` side stores were correctly down to one entry throughout — the side-store discipline was never the leak, the registration was. `_release_si()` now takes the handler back off at both points that drop a connection: the eviction inside `connect()` and `disconnect()`. Five repos had the identical shape, so `family_smoke` gained a gate for it (154 → 155). Not `WeakKeyDictionary`, which looks like the obvious fix and is a regression: pyVmomi's `ManagedObject.__eq__` compares moId, class and serverGuid, and every ServiceInstance carries moId `'ServiceInstance'` with serverGuid `None`. Two vCenters collapse into one entry — connecting to the second silently hands the first one's `verify_ssl` to both. Keying by `id()` is right precisely because it is identity. ## v1.8.21 — snapshot-delete gains the flag its docs already promised The reference documented `vm snapshot-delete --name [--remove-children]`. The option did not exist, so following the documentation got "No such option" — while the capability was real all along: the ops layer takes `remove_children` and the MCP tool exposes it. Only the CLI never passed it through. It reaches the `--dry-run` preview too, so the difference is visible before the delete rather than after. ## v1.8.20 — `vm_cancel_ttl` is not destructive — it prevents a deletion The annotation was copied from `vm_set_ttl` directly above it, where `destructiveHint=True` is right and carefully argued: set_ttl schedules an unattended deletion that a daemon carries out later. cancel_ttl calls that off. No behaviour changes; the CLI already asked nothing, correctly. What changes is that a client reading the annotations is no longer told that preventing a deletion is a destructive act. ## v1.8.19 — one answer per .env, on every platform `.env` permissions are decided by `vmware_policy.fsperms` instead of POSIX mode bits. On Windows a single command printed both a red "has permissions 0o666 (should be 600). Run: chmod 600" from this hot path — where `chmod` does nothing — and a green "this platform does not express file permissions as POSIX mode bits ... run: icacls" from `doctor`, about the same file in the same run. `doctor` had been moved to the three-state check and the path that runs on *every* command had not. An unmeasurable platform is now silent here rather than loudly wrong. The `vmware-monitor` floor moves to 1.10.0: the investigation bundles this skill re-exports had a cluster check that never matched on real hardware, and the fix ships in Monitor. ## v1.8.18 — the test suite runs on a non-UTF-8 machine, and the guardrail tests with it **The suite now runs on a cp936 machine.** Round 3 of the VCF 9 field testing ran on Windows Server 2025 with locale cp936. Across the family four repos' suites -- 1687 tests -- never executed at all, dying at collection reading our own UTF-8 sources, and 101 more failed the same way. Most of those were the tests that verify the destructive-operation guardrails: the guardrails were fine, the tests that check them could not open a file. On the UTF-8 CI every one of them was green. A security test that cannot run is not a security test. Every text read and write here names its encoding now, `tests/` included -- the previous round fixed only the package, which is why this came back. A gate in `family_smoke` scans both trees by AST, and the whole family's suites were re-run under an ASCII locale to confirm: 15 of 15 green, from 1 of 15. **`--help` no longer dies on a console that cannot encode it.** On any console whose encoding cannot carry the characters in our own help text, `--help` exited with a `UnicodeEncodeError` traceback -- unavailable exactly on the machines where it is most needed. Four repos were affected; the handler is now relaxed in all fifteen so a glyph degrades instead of killing the command. **Its environment resolver no longer answers for other skills.** `set_environment_resolver` wrote one process-global slot and twelve servers registered into it at import time, so the last one won for all of them -- measured taking a `freeze-production-writes` rule from DENY to ALLOW on another skill's production target. Registration is keyed by skill now (requires vmware-policy 1.12.0). **The `.env` permission check stopped being permanently red on Windows.** It was POSIX-only, and `chmod 600` there exits 0 without changing any bits -- so `doctor` printed a failure on every run with a remedy that could not clear it. Three states now, via `vmware_policy.fsperms`: only a demonstrated exposure fails, and "this platform cannot answer" says so and offers `icacls`. **Unknown tool arguments are refused instead of dropped.** The schema declared `additionalProperties: false` and the runtime accepted them anyway, so a filter argument whose name a model guessed wrong returned the *unfiltered* result with nothing to indicate anything had been discarded. Fixed in vmware-policy 1.12.0 and in force here. Requires vmware-policy 1.12.0. ## v1.8.17 — closed-value parameters now reach the MCP schema `drs_behavior` (cluster_create, cluster_configure), `rule_type` (create_drs_rule) and `binding` (create_dvs_portgroup) were typed `str`, with their valid values named only in prose. An MCP client sees the schema, not the prose, so a model had to guess the spelling. They are now `Literal[...]`, and a new regression test pins each enum against the set the ops layer actually validates against (`_VALID_DRS_BEHAVIORS`, `_CREATABLE_RULE_TYPES`, `_VALID_BINDINGS`), so the two cannot drift apart. ## v1.8.16 — three MCP annotations that were lying, and errors invisible on the wire `vm_guest_download` claimed `readOnlyHint: true` while overwriting an arbitrary caller-supplied path — and that hint is what a client consults before asking for confirmation, so the annotation was a safety control that silently did not apply. It refuses an occupied destination now unless `overwrite=true`, refuses directories, and refuses symlinks even with it. `vm_set_ttl` claimed `destructive: false` while scheduling an unattended auto-delete. `vm_create_snapshot` claimed `idempotent: true` while calling `CreateSnapshot_Task` unconditionally — the field a client reads before *retrying*, and this family retries transient failures once. Exceptions were caught inside tools and returned as ordinary values, so `isError` was always false: over stdio a client could not tell "the target does not exist" from "succeeded". The frame is marked now, with the authored teaching message intact. The documentation claimed double confirmation with no bypass on a surface that has neither. The git history and the design record show that removal was a decision, not an accident — `vm_delete` never had a confirmation parameter — so the documents were corrected rather than the code, and each now names which surface it is describing. **The `vmware-policy` floor moves to >=1.11.0.** Policy 1.11.0 stops the engine failing open: on a host whose locale is not UTF-8, reading `rules.yaml` raised a decode error that was swallowed, and a `freeze-production-writes` rule came back ALLOW. No new API is used here, so the floor could have stayed — it is raised because leaving it low means a user resolving 1.10.0 keeps the permissive engine and the fix never reaches them. One behaviour travels with it: on a host whose rules file cannot be read, operations move from all-allowed to all-denied. `VMWARE_POLICY_DISABLED=1` is checked above the rules, so the escape hatch does not itself depend on them loading. Also in this release: the suite no longer appends to the operator's real `~/.vmware/audit.db`. It held over 30,000 rows dominated by tool names nobody had invoked, including 1,400 entries for a destructive operation that never happened — an audit trail carrying test fiction cannot answer the question it is kept for. ## v1.8.15 — the schema an agent reads now carries the descriptions Parameter descriptions reach the JSON schema for the first time. An MCP client sees the schema, not the docstring, and this repo's coverage of `description` and `additionalProperties` was 0% — while nearly every parameter was already described in an `Args:` block no client ever receives. Measured on a real VCF 9.1 estate, the gap produced a silent failure with no error at any stage: a parameter name guessed wrong is discarded and the tool returns the full unfiltered result; a value guessed wrong (`power_state= "running"`) returns 0 rows where there were 11. vmware-policy 1.10.0's `describe_tool_parameters` copies what is already written, so the docstring is now load-bearing and the two cannot drift apart. It removes the `Args:` block from the description once copied — both travel in every `tools/list` response, so leaving it bills the same sentences twice against the manifest's token budget. `additionalProperties` is closed: an open schema is room for a model to invent arguments that are then silently discarded, which is the other half of the same failure. **The `vmware-policy` floor moves to >=1.10.0.** Older releases have no `describe_tool_parameters`, and resolving one gives an ImportError at server start rather than a missing feature. Also in this release: three MCP annotations that were lying, and errors that were invisible at the protocol level. `vm_guest_download` claimed `readOnlyHint: true` while overwriting an arbitrary caller-supplied local path — and `readOnlyHint` is what a client consults to decide whether a call needs confirmation, so the annotation was a safety control that silently did not apply. It is now a write, and it refuses an occupied destination unless `overwrite=true`, refuses directories, and refuses symlinks even with `overwrite=true`, because consenting to replace a path is not consenting to replace what it points at. `vm_set_ttl` claimed `destructive: false` while scheduling an unattended auto-delete. `vm_create_snapshot` claimed `idempotent: true` while calling `CreateSnapshot_Task` unconditionally — that is the field a client reads before *retrying*, and this family retries transient failures once, so a timeout on a snapshot that had in fact succeeded would be retried into a second delta-disk chain. Correcting the first one disarmed a second control by itself: the guarded-CLI test derives its write set from `readOnlyHint`, so `vm guest-download` had been the one file-writing command with no `@guarded`. It has one now. And exceptions were caught inside tools and returned as ordinary values, so `isError` was always false: over stdio a client could not tell "the target does not exist" from "succeeded". The frame is now marked, with the authored teaching message intact — verified in a live JSON-RPC session showing a failing call and a succeeding one in the same transcript. ## v1.8.14 — a host nobody could reach is not a host with no adapters Found against a real VCF 9.1 estate where four of eight ESXi hosts were `notResponding`. vCenter keeps answering for such a host out of its own cache, with no error and no marker, so a read "succeeds" and looks authoritative. **`list_host_vmks` dropped unreachable hosts and still said the list was complete.** Asked for one, it raised a bare `AttributeError`; enumerating all of them, it skipped the four silently and reported `truncated: false` — the envelope positively certifying a list that was missing half the estate. Unread hosts now appear as rows with `reachable: false`, null facts and the `connectionState` in the note, and the envelope carries `hosts_unreachable`. Three sibling write tools carried the identical bare dereference and were found by grepping for the shape rather than waiting for them to be reported; they now raise a teaching error, because reading the adapter list in order to *change* it is a different question from listing it. The read and the writes deliberately answer differently, and the reasoning is in the docstrings. The distinction that makes it work: PropertyCollector *omits* a property it could not read, so an absent `config.network.vnic` means "not read" while `[]` means "read, and empty". `p.get(...) or []` collapsed the two, which is exactly how four hosts disappeared. **`doctor` cleared an estate it had not checked**, authenticating only the default target — so five targets with three wrong passwords came back "All checks passed", and the failure that followed told the user to run the doctor that had just cleared them. **The CLI, the doctor and the MCP server opened different config files.** With `VMWARE_AIOPS_CONFIG` set, the server read that file while `load_config()` read the default: the agent and the human on different vCenters, with the doctor reporting on the human's. The precedence now lives in one `resolve_config_path`. **`server.json` never started the MCP server** — it carried only the package identifier, so a registry client composed `uvx vmware-aiops`, which runs the CLI and exits. The floor on `vmware-monitor` moves to 1.8.13. The cluster-health triage this skill exposes is delegated to that package, and on anything older it reports unreachable hosts as healthy ones. ## v1.8.13 — list tools that agents could not read, and a documented key the code ignores - **Three list tools answered in a shape the family contract does not use.** `list_dvs_portgroups` returned `{total, returned, portgroups}`, `list_host_vmks` returned `{total, returned, vmks}`, and `scan_datastore_images` returned `{images, last_scan}` with no count at all. Each is self-consistent; each is wrong, because an agent that has learned this family reads `items` and gets nothing. The absent keys are the ones that carry meaning: `truncated` and `hint` exist because of issue #31, where a model handed a bare list "incorrectly states that no data was returned". All three now return the envelope, with the old keys kept as deprecated aliases so nothing that reads them breaks. - **Offset paging claimed there was more at the end of the list.** Truncation was derived from `returned < total`, which is wrong once `offset` is involved — the last page of five rows fetched with `offset=4` returns one and reported `truncated: true`, advising the caller to raise a limit that cannot help them. Both offset-supporting tools now share one helper. - **The SSL config key in every doc was one the code has never read.** Docs said `disableSslCertValidation: true`; `config.py` reads `verify_ssl`. Following the instruction produced a config that is ignored. Found by running against a real vCenter rather than a mock. ## v1.8.12 — two wrong numbers: the server's own version, and the advertised tool count Both defects were invisible to the test suites and both were user-facing. - **The MCP server reported the SDK's version as its own.** `FastMCP` accepts no `version` argument and leaves the lowlevel server's at `None`; with it `None` the SDK answers `initialize` with its OWN version. Every skill in the family therefore told its client it was mcp 1.29.1 — a number that exists for no package here, and one that would change with an SDK bump and no code change of ours. Verified end to end rather than by reading: unset the field and a probe server reports the installed SDK's version; set it and it reports ours. - **server.json advertised a stale tool count.** That number is what MCP Registry publishes and what the plugin manifest and marketplace copy, so one stale integer was wrong in three public places. Corrected against the registered tools: 31 advertised, 60 real. README and SKILL.md were already right. Also new: this repo is installable as a Claude Code plugin (`/plugin install vmware-aiops@vmware-skills`). The skill and its MCP server arrive in one step; nothing is duplicated, the manifest points at the existing `skills/` tree. family_smoke gained three gates — the server's reported version, the plugin manifest's agreement with pyproject, and the advertised tool count against the live registration. ## v1.8.11 — moved to vmware-skills org + MCP Registry namespace io.github.vmware-skills/vmware-aiops Repo transferred from github.com/zw008 to github.com/vmware-skills (redirects preserve old links). MCP Registry server renamed to `io.github.vmware-skills/*`; the old `io.github.zw008/*` entry is deprecated. All in-repo links updated. No functional code change on this line beyond the org move. ## v1.8.10 — DRS VM-VM rules + vmk service tagging + UTF-8 I/O (55 → 60 tools) Three community contributions by @wright-bench (PRs #36, #37, #38), plus CLI parity for the DRS rule surface. - **DRS rule suite (PR #37)** — four MCP tools for VM-VM affinity/anti-affinity rules, closing the day-2 gap `cluster_configure` left (cluster-wide DRS toggled, individual rules had no surface): - `list_drs_rules` [READ] — VM-VM and VM-Host rules (key, name, type, enabled, mandatory, member VMs / group names). The verify pair for the writes. - `set_drs_rule_enabled` [WRITE] — flip a rule's enabled flag; idempotent. - `create_drs_rule` [WRITE] — VM-VM affinity/anti-affinity, ≥2 distinct VMs, all validated as cluster members. - `delete_drs_rule` [WRITE] — VM-VM only; **refuses** VM-Host and other rule types (they can carry licensing/compliance placement constraints); preview and result both record the full definition for recreate-from-audit. - **CLI parity added**: `cluster drs-rules`, `cluster drs-rule-set`, `cluster drs-rule-create`, `cluster drs-rule-delete` — `@guarded`, double-confirm on writes, `--dry-run` on all three writes. - **`set_vmk_service` (PR #36)** — tag/untag a host service (vmotion, management, vsan, vSphereProvisioning, …) on an existing vmk, completing `add_host_vmk` (adapters are created serviceless by design). Idempotent, preview/confirm gated. **Fail-closed**: refuses both directions when the service map is unreadable, and refuses (no override) to untag `management` from the host's only management-enabled vmk — the call rides the interface it would untag. Remains MCP-only, consistent with the rest of the host-network suite. - **UTF-8 I/O pin (PR #38)** — `encoding="utf-8"` pinned on every text-mode `read_text`/`write_text`/`open` across source and tests (46 sites). Fixes `UnicodeDecodeError` on Windows/cp1252 checkouts where unpinned reads land on files containing emoji/arrow bytes, and locale-encoded write round-trips. Mechanical only — no logic touched. Tool count 55 → 60 (18 read, 42 write). Thanks again to @wright-bench. ## v1.8.9 — Network authoring: dvSwitch portgroups + host VMkernel adapters (49 → 55 tools) Six new MCP tools land the family's first network-authoring surface (community contribution, PR #35 by @wright-bench): - `list_dvs_portgroups` / `create_dvs_portgroup` — distributed-switch portgroups. Create is preview/confirm gated and supports `earlyBinding` and `ephemeral` (ephemeral attaches from the ESXi host client with vCenter down — the self-hosted-VCSA case). `lateBinding` is deprecated by vSphere and not offered. - `list_host_vmks` / `add_host_vmk` / `remove_host_vmk` — host VMkernel adapters. `add` is the throwaway-test-vmk shape (static IP, no gateway, no services). `remove` is **fail-closed**: it refuses when the vmk is selected for a host service (management/vMotion/vSAN), lives on a non-default netstack (NSX TEPs, dedicated vMotion/provisioning stacks), carries a default route, or when any of that cannot be verified — `force_unprotected=True` overrides all but the only-management-vmk absolute (the call rides the interface it would delete). - `vmk_ping` — DF-bit-capable ping sourced from a vmk via esxcli-over-API (no host SSH). `df=True size=1572` proves a ≥1600 overlay floor; `size=8972` proves full jumbo. Oversized DF'd packets report the esxcli fault structurally, not as an error — that failure *is* the MTU diagnostic. This is the underlay surface (VLAN-backed DVS portgroups, host kernel interfaces); for NSX overlay segments/gateways/NAT use vmware-nsx. Writes are preview/confirm gated, risk-tiered, and audit-logged like every other AIops write. Tool count 49 → 55 (17 read / 38 write). No dependency or breaking changes. ## v1.8.8 — CLI writes now route through policy + audit, exactly like the MCP tools Every state-changing CLI command is now wrapped by `@guarded`, the CLI counterpart to the MCP `@vmware_tool` decorator: it runs the same vmware-policy `guard()` authorization and writes the same `audit_call()` row to `~/.vmware/audit.db`. A `delete`/`disable`/destructive command run through a shell is now authorized and recorded exactly like the equivalent MCP tool — closing the gap where CLI writes bypassed policy and landed only in the legacy per-skill log (HLD I-1/I-8). - a policy `deny` rule now refuses the operation on the CLI with a teaching line naming the rule that fired, not a traceback - the legacy per-skill audit log is still written this release (dual-write); it is removed at 2.0 - **requires vmware-policy >= 1.8.8** (the release that adds the shared `guarded` core) - a regression test derives the write-command set from the MCP `[WRITE]` markers and asserts every one is `@guarded`, so a new write command cannot ship unguarded Also carries the environment-field docstring correction (an optional label a `deny` rule may scope to — there is no "warn now / refuse next major" gate). ## v1.8.7 (2026-07-21) — the skill-level read-only switch is removed; read/write authorization is the vCenter account's job (RBAC) ### Removed: `VMWARE_READ_ONLY` / `read_only:` — give the agent a read-only service account instead The skill-level read-only switch is gone. It was enforced only on the MCP tool registry, and any agent with a shell (every SKILL.md grants `allowed-tools: Bash`) could reach the same change one CLI command away — so it withheld the *tool*, not the *capability*. It was never a real boundary. To run an agent read-only, give it a **read-only vCenter/NSX service account (RBAC)**. Writes are then refused at the platform, un-bypassably, regardless of surface or shell — the one place read/write control cannot be stepped around. A config still carrying `read_only: true` is ignored, with a one-time warning that names the replacement (no silent behavior change). ### Removed: approval tiers and the declared-environment gate (via vmware-policy) The graduated-autonomy approval tiers (`confirm`/`dual`/`review`) and the "declare an environment or be refused" baseline are removed — they only ever fired on the rarest configuration while carrying the family's most complex machinery. Opt-in `deny` rules and the maintenance window remain, and apply identically wherever a tool runs. ### Added: offline / air-gapped install docs The README now covers installing from source without editable mode (for older `pip`) and building wheels to carry onto an air-gapped host — the modern PEP 517 layout has no `setup.py` by design, which is expected, not a missing file. This release also carries the accumulated fixes staged since 1.8.5. ## v1.8.5 (2026-07-20) — the two fixes v1.8.4 announced now actually work Four adversarial reviews of v1.8.4 found that both of its headline fixes were incomplete in ways the release notes did not reflect. This release makes them real. If you are on 1.8.4, this is the one to take. ### Fixed — a failure that was *returned* was still audited as a success vmware-policy 1.8.4 added `report_tool_failure()` for tools that catch an exception and return an error payload instead of raising. **No skill called it.** Every string-returning tool therefore kept doing exactly what 1.8.4 said it had stopped doing: writing `status=ok` to `~/.vmware/audit.db` for an operation that failed, recording an undo token for a change that never happened, and telling the circuit breaker the call succeeded so repeated failures never tripped it. The surface this covered is not marginal: | Skill | What was mis-audited | |---|---| | vmware-aiops | 25 of 49 tools, including **every undo-bearing write** — a failed `vm_power_on` left an undo token saying "power it back off" | | vmware-avi | all 28 tools, including `vs_toggle` and `ako_restart` | | vmware-storage | all 4 write tools | | vmware-nsx | the 5 delete tools | vmware-avi is worth calling out: before 1.8.4 its exceptions propagated and the audit was correct. 1.8.4 caught them and returned a string, so **that release made its audit trail worse than it had been.** Skills whose tools already return dict payloads (vmware-monitor, vmware-vks, vmware-aria, vmware-log-insight, vmware-harden, vmware-debug, vmware-pilot) were already detected correctly. They gained a test proving it rather than a redundant call. ### Fixed — narrowing `OSError` did not close the leak it was meant to close 1.8.4 narrowed the `_safe_error` passthrough because bare `OSError` let TLS and DNS failures reach the agent with hostnames and certificate subjects in them. That narrowing had no effect on the error it was written for: ``` ssl.SSLCertVerificationError → ssl.SSLError → OSError, ValueError ``` `ValueError` has been on every allowlist since long before 1.8.4, so a certificate failure kept passing through — the commonest self-signed-certificate failure in this family, carrying the hostname it was checked against. An allowlist structurally cannot express "not this one". Where `ssl.SSLError` can actually surface — the pyVmomi skills — it is now reduced *ahead* of the allowlist. In the httpx skills TLS arrives wrapped as `httpx.ConnectError`, and in vmware-avi as `requests.exceptions.SSLError`, so the guard cannot fire there; in those skills the leak was the raw exception interpolated into an already-allowlisted `*ApiError`, and that is now authored text naming the config target and `verify_ssl` instead of the exception. The missing-password error — this family's most common first-run failure, whose entire remedy is the environment variable name it carries — keeps its message through a narrow `ConfigError(OSError)` rather than the base class. Connection failures are translated at the connection layer into an authored remedy that names the target and the setting to change, with the raw detail left on `__cause__` for the server log. ### Also fixed - **vmware-vks**: the quickstart documented a password variable the code never reads — following `README.md` verbatim produced "Password not found". Five places, plus six references to a `doctor` command this CLI has never had, two descriptions promising fields the tools do not return, and eight teaching messages that `RuntimeError` was masking. - **vmware-nsx**: an error cited `--route-advertisement`; the flag is `--advertise`. - **vmware-pilot**: `get_workflow_status` told the model to call `approve` — a tool the read-only gate withholds — as the required next step; and a hint pointed at a filename that could never appear in that message. - **vmware-aiops**: `vm_task_status` polling a *failed task* returned `{"state": "error", "error": ...}` from a successful read, which the new detection read as the call itself failing. The field is now `task_error`. **This is a breaking change for anything parsing that payload.** - Several remedies that were still being cut by the 300-character cap the 1.8.4 notes claimed to have addressed. ### Known and not fixed `ConnectionError` remains one type from two sources in several skills — a skill's own authored message and urllib3's `HTTPSConnectionPool(host=..., port=...)` share it, and an allowlist cannot separate them. vmware-vks is converted; the rest need their own domain type and are deferred rather than half-done. ## v1.8.4 (2026-07-20) — errors that teach, and tool descriptions a small model can route from A capability eval was rolled out across the family and asked two open questions: when a call fails, is the model told enough to fix it, and can it pick the right tool from the description alone? Both answers were worse than anyone thought, and in several places the reason was that the measurement was looking somewhere other than where the model reads. ### Fixed — teaching messages were being discarded on the way to the agent `_safe_error` reduces unrecognised exceptions to `": operation failed."` so raw API text, credentials in URLs and internal paths cannot reach an agent. Its allowlist held only the builtin validation errors — so this skill's **own** domain exceptions, the ones that exist precisely to carry a corrected next step, had their messages replaced by their class names. The effect was invisible from the CLI, which prints those messages in full. The worst case was shared by nine skills: `config.py` raises exactly one `OSError`, the missing-password error, whose entire remedy is the environment variable name it names. An agent hitting an unconfigured target received `OSError: operation failed.` and had nothing to act on. That is the family's most common first-run failure, and it landed one release after the documented variable names were corrected — so the message that would have unstuck the operator was the one being thrown away. The rule is now the property it always meant: **every exception this skill raises on purpose passes through**, and only genuinely unplanned ones are reduced. `RuntimeError` stays reduced — it is the generic catch-all and in several skills carries raw upstream text. ### Fixed — error messages now carry the correction Every message that reported a failure without saying how to recover was rewritten: it names the offending value, gives an imperative remedy, and names something concrete to act on — a tool that exists, a real CLI command, a config file, an environment variable. Recovery becomes an instruction-following problem rather than an inference one, which is what a weak model can still do. Three classes of defect surfaced while doing it: - **Remedies that were never delivered.** `_safe_error` truncates with no ellipsis, so a message longer than the cap loses its closing sentence silently. One message had been shipping at 396 characters against a 300-char cap — its remedy had never once reached an agent. Messages now lead with the remedy so a long interpolated value truncates the expendable detail instead. - **Commands that do not exist.** One skill's error hints named a `doctor` subcommand it does not have. - **Tools that do not exist.** A tool description pointed at two sibling-skill tools that had been renamed, and another named a tool that had moved to a different skill entirely. ### Improved — tool descriptions state when to use them and what to call next The description is the API for a small model: an unstated routing rule is a routing rule that does not exist, and a tool with no stated next hop is one the model stops at. Descriptions now say when to prefer this tool over a sibling, what shape comes back, the caveat that bites, and which tool to call after. **Manifest size did not grow.** Descriptions load into every session, so the routing clauses were paid for by cutting duplicated reference material — repeated boilerplate, examples that restated the parameter list, and prose copies of the pagination contract. ### Note Every tool and CLI command named anywhere in this release was verified against the live MCP registry and the live command tree, not against documentation. ## v1.8.3 (2026-07-20) — credentials resolve as a pair; documented env vars now exist ### Added — the per-target username can come from the environment Adapted from [VMware-AIops#33](https://github.com/vmware-skills/VMware-AIops/pull/33) by @wright-bench, with thanks. The password already resolved from an env var; the username did not, so a deployment injecting credentials from a secret store (systemd `EnvironmentFile`, container secrets, a vault sidecar) could externalise only half of the pair — and a config-file username paired with an env password from a different account logs in as nobody. `_USERNAME` now overrides the `username:` in config.yaml, using that skill's own password-key convention. Absent, config.yaml still wins; nothing changes for anyone not setting it. **Resolved on every access, like the password.** The contributed version read the username once at load time while the password stayed a property, which reintroduces exactly the split the override exists to prevent: a sidecar rotating both halves mid-process moves the password and leaves the username behind. A test pins that both halves resolve at the same moment. ### Fixed — documented credential variables that the code never read Rolling the above across the family surfaced a separate defect: four skills documented a password variable their own loader does not look up. An operator following the documentation exactly — correct file, correct place, correct-looking name — got "Password not found". | Skill | Documented | Actually read | |---|---|---| | vmware-nsx | `VMWARE_NSX__PASSWORD` for target `nsx-prod` → `VMWARE_NSX_PROD_PASSWORD` | `VMWARE_NSX_NSX_PROD_PASSWORD` | | vmware-nsx-security | `VMWARE__PASSWORD` | `VMWARE_NSX_SECURITY__PASSWORD` | | vmware-aria | `VMWARE__PASSWORD` | `VMWARE_ARIA__PASSWORD` | | vmware-vks | `VMWARE__PASSWORD` | `VMWARE_VKS__PASSWORD` | | vmware-avi | three different forms across three files | `_PASSWORD` | The prefixes genuinely differ per skill, so nothing could be fixed by standardising a pattern — each repo's docs were corrected against its own code. The code was left alone: changing a key would break every existing deployment. `family_smoke.sh` now compares the credential variables named in each repo's docs against the ones that repo's code builds, so the two cannot drift apart again. ## v1.8.2 (2026-07-20) — the MCP server moves into the package namespace ### Fixed — co-installing two skills broke all but the last one Every skill shipped its MCP server as a **top-level `mcp_server` package**. Python has one top-level namespace, so installing any two of them into one environment let the second overwrite the first — silently, with no error and no warning. uv tool install vmware-aiops -> 49 tools (correct) uv pip install vmware-aiops -> 27 tools (Monitor's read-only server) vmware-aiops depends on vmware-monitor, so this was not an edge case: **every pip install hit it**, and the operator got 27 read-only tools where 49 were expected, with all 35 write tools missing. Docker images, shared MCP hosts and CI runners that install more than one skill were affected the same way. The server now lives at `vmware_/mcp_server/`, a name only this package can claim. Introduced 2026-02-26; it survived 70 releases because every test ran against a single package in its own repo, where the local directory shadows site-packages — the conflict was invisible by construction. **Migration.** Console scripts are unchanged: `vmware-` and `vmware--mcp` work exactly as before, as does `"command": "vmware-", "args": ["mcp"]` in an MCP client config. Only a direct `python -m mcp_server` breaks; use `python -m vmware_.mcp_server`. ### Added — `references/agent-guardrails.md` in every skill The operating rules for local and small models (Llama 3.3 70B, Qwen, Mistral via Goose / Ollama / OpenShift AI) existed in two skills. They now ship in all 13, each with its own tool counts and failure modes, and are linked from every SKILL.md. ### Changed — 46 error messages rewritten to teach recovery A large model reads `VM 'web-99' not found` and recovers on its own. A small model either surfaces it as a dead end or smooths it into a confident, wrong summary — the failure [VMware-AIops#31](https://github.com/vmware-skills/VMware-AIops/issues/31) reported. The difference is entirely in the message text. error_actionability 43.5% -> 90.5% teaching_error_rate 10.2% -> 77.6% (5/49 -> 38/49) dead-end errors 35/49 -> 2/49 Two shipped bugs surfaced while doing it: `_safe_error` silently swallowed `InventoryError`, `HostNotFoundError` and `ISCSIError` (their text became `"InventoryError: operation failed."` over MCP), and two error messages named commands that do not exist — `vmware-aiops storage iscsi-enable` (there is no `storage` command here) and `vm guest-exec` for host maintenance mode (that runs inside a guest, not on a host). The three raises left alone are the ones where no advice would be true: a vCenter-supplied non-HTTPS lease URL, and a malformed inventory graph. Inventing a remedy scores better and helps nobody. ### Fixed — documentation that pointed at files which do not exist 93 install instructions across Monitor and AIops told operators to copy `codex-skill/AGENTS.md`, `gemini-extension/GEMINI.md`, `trae-rules/project_rules.md` or `kimi-skill/SKILL.md`. None of those directories exist in any repo, and CLAUDE.md forbids recreating them — so every one of those commands failed. Where a platform reads plain markdown (Codex, Kimi, Trae, Aider, Continue) they now point at the real `skills/vmware-/SKILL.md`; Gemini CLI, which needs a manifest none of the repos ship, was downgraded to the context-file and MCP paths that do work. `/plugin marketplace add` was removed from both repos and from three setup guides: the `plugins/` source directory it needs was deleted in March and is on CLAUDE.md's forbidden list, so that path could not be made to work either. ### Removed — `.claude-plugin/marketplace.json` and the stale `.agents/` copy The manifest's `source: "./plugins/vmware-ops"` pointed at a directory deleted on 2026-03-22; it had been broken for four months and was still at version 1.0.16. The `.agents/` copy was a 2026-04 snapshot that had diverged 314 lines from the maintained SKILL.md, hidden from `git status` by its own `.gitignore` entry. ## v1.8.1 (2026-07-19) — read-only mode reaches the surfaces that teach it v1.8.0 put read-only mode in the code and documented it in the README only. Every other layer was empty, and each serves a different reader: SKILL.md is what the agent loads, setup-guide is what an operator reads while configuring, `doctor` is where they verify it took. The gap had two concrete costs. An agent read SKILL.md, called a write tool the gate had withheld, and got nothing back — with no way to learn that the absence was a deliberate lockdown rather than a fault. It reads as a broken tool, so the model retries or hunts for a workaround. An operator who set the switch had no way to confirm it. The only signal was a line in the MCP server's start-up log. ### Added — the feature is now documented where each reader looks - **SKILL.md** — a short section telling the agent that a missing write tool is a lockdown, not a fault: name the blocked operation, do not retry, do not route around it. - **references/setup-guide.md** — the operator's view: how to enable it, the precedence chain, and how to verify. - **references/capabilities.md** — which tools the gate withholds. ### Added — `doctor` reports the read-only state `vmware-aiops doctor` now shows whether read-only mode is on, **which** of the three switches decided it, and the value as written. A typo'd value (`ture`) is called out as a typo rather than reported as a confident ON — it resolves to on, which is fail-closed but almost never what was meant. The resolution runs through `vmware_policy.read_only_status()` rather than a local copy of the precedence chain: a doctor that disagrees with the gate it reports on is worse than no doctor. Requires `vmware-policy>=1.8.1`. ## v1.8.0 (2026-07-18) — read-only mode, working policy defaults, declared environments Family release driven by [VMware-AIops#31](https://github.com/vmware-skills/VMware-AIops/issues/31), where an operator running Llama 3.3 70B (Goose / OpenShift AI, on-prem H100) had to hand-write 17 prompt guardrails to make tool calling reliable. A prompt is advisory — a model can ignore it. Every guardrail that could move into the harness has. ### Added - **Read-only mode.** Set `VMWARE_READ_ONLY=true` (or `VMWARE__READ_ONLY`, or `read_only: true` in config.yaml) and every write tool is removed from the MCP registry at start-up. `list_tools()` never offers them, so the model cannot call what it cannot see. **Off by default** — nothing changes unless you turn it on. Fail-closed: if the mode is requested but cannot be guaranteed, the server refuses to start rather than running open. - **`environment:` on each config target**, declaring which environment it is (production / staging / lab). Policy rules scope by this value. ### Added — list results now state whether they are complete Every `[READ]` list tool returns the family envelope instead of a bare array: {"items": [...], "returned": 50, "limit": 50, "total": 213, "truncated": true, "hint": "Showing 50 of 213. Raise limit or narrow the query..."} This closes the reported failure where long responses were summarised as "no data returned": a bare list gives a model no way to tell a complete answer from page one, so it guessed. `truncated: false` now positively states completeness — including when `items` is empty, which means "checked, found none", not "the call failed". - **5 tool(s) converted** across ops, MCP and CLI. All five report a real `total`; each enumerates its collection fully before limiting. The three `batch_*` write tools keep returning a bare list and are pinned that way: a batch result is complete by construction, so a `truncated` field there would be noise. ### Changed — migration, read this - **Approval tiers now actually run.** They shipped in v1.6.0 but the engine only ever read `~/.vmware/rules.yaml`, and a fresh install has no such file — so every deny rule, maintenance window and approval tier had been inert on every install that never hand-authored one. A packaged baseline now loads when you have written no rules of your own. Writes at medium risk and above are stamped with their tier in the audit log; irreversible work and guest execution against a target declared `production` require a named approver via `VMWARE_AUDIT_APPROVED_BY`. - **`environment:` will become required for writes.** Today a state-changing operation against a target that declares none still runs and logs a warning. **The next major release refuses it.** Declare it now and that upgrade is a no-op: targets: prod-vc01: host: vc01.corp.local environment: production Read-only operations are never affected, in this release or the next. Check what applies to your targets before upgrading: `vmware-audit policy --operation vm_delete --env `. ### Fixed - **Policy glob patterns with a leading wildcard silently matched nothing.** A rule written `operations: ["*_delete"]` parsed fine, read correctly, and never fired — only a trailing `*` was honoured. Now full glob matching, for operations and environments alike. - Config-path overrides (`VMWARE__CONFIG`) are honoured when reading `read_only` and `environment`, so a setting in a custom config file is no longer silently ignored. ### Notes - Requires `vmware-policy>=1.8.0`; publish that package first. - `vmware-audit policy` reports which rules are in force and where they came from — including the case where your rules file exists but failed to parse, which previously looked identical to "policy is working". ### Fixed — pre-release review (2026-07-19) - **`vm_list_plans` was marked `[READ]` but deleted plan files.** `list_plans()` opened with a stale-plan sweep, so the one plan tool that survives read-only mode was the one that destroys plans — while `vm_create_plan`, `vm_apply_plan` and `vm_rollback_plan` were all withheld by the gate. Opening a read-only server to review a failed apply could unlink the only on-disk record of which steps landed. Expiry now runs on `vm_create_plan` alone, so plans are still swept on every write. ## v1.7.7 (2026-07-17) — session-probe eviction fix + mcp 1.28.1 Family fix pack — no new tools, no schema changes. ### Fixed - **Dead cached vCenter sessions were never evicted** (external fork report, VMware-AIops PR #32). The liveness probe's handler was `except (vmodl.fault.NotAuthenticated, Exception)` — but `vmodl.fault.NotAuthenticated` does not exist in pyVmomi (the real class lives under `vim.fault`), and except-tuples are evaluated at catch time, so the handler raised `AttributeError` instead of evicting. A long-running MCP server whose session idled out then permafailed every call until restart, surfacing the misleading `AttributeError: NotAuthenticated` instead of the real error. The probe now also treats a `None` `currentSession` (expired-token shape) as dead. Three regression tests pin the probe shapes (raise → evict + reconnect, None → evict + reconnect, live → cache reuse), and family_smoke gained a static check banning the nonexistent class. ### Security - Lockfile bumps `mcp` to **1.28.1**, clearing three GHSA HIGH advisories against the MCP Python SDK (WebSocket Host/Origin validation, HTTP transport principal verification, experimental task-handler cross-client access). stdio-only servers are not directly exposed, and installs resolve `mcp` fresh from PyPI — this mainly matters for from-source checkouts. ## v1.7.6 (2026-07-14) — object investigation bundles + cross-vCenter attention (from the AIops entry point) The object-centered drill-down from issue #31 — "what is happening around this VM / host / datastore?" — plus a cross-vCenter "what needs attention now?" view, reachable from an AIops conversation so triage → investigate → act stays in one place. ### Added - **Object investigation bundles** (read-only; surface 45 → 49). Four new MCP tools — `vm_investigation_bundle`, `host_investigation_bundle`, `datastore_investigation_bundle`, and `cross_vcenter_attention` — each correlates an object (or the whole estate) with its surrounding infrastructure and recent event history and returns one high-signal, aggregated result. All four delegate to the vmware-monitor library using AIops's own vCenter connection; no logic is duplicated. - **CLI**: `vmware-aiops investigate vm|host|datastore ` and `vmware-aiops attention`, each with `--hours` and `--html` / `--html-path` (offline, self-contained snapshot; drill-down sections collapse with zero JavaScript). ### Changed - New dependency floor: `vmware-monitor>=1.7.6` (the delegated bundle functions live there). ### Notes - Read-only; point-in-time snapshots. Requires `vmware-monitor` installed (delegation). ## v1.7.5 (2026-07-13) — cluster-health triage from the AIops entry point ### Added - **`cluster_health_summary` MCP tool** (read-only; surface 44 → 45) and **`vmware-aiops summary` CLI** — the one-glance cross-cluster triage (ranked top-N issues + per-cluster status + offline HTML snapshot) is now reachable from an AIops conversation. Both delegate to the vmware-monitor library using AIops's own vCenter connection; no logic is duplicated. - New dependency: `vmware-monitor>=1.7.5`. ### Fixed - Removed a leftover duplicate `import json` in `cli/mcp_config.py` (dead code). ## v1.7.4 (2026-07-13) — family version alignment ## v1.7.3 (2026-07-03) — family version alignment ## v1.7.2 (2026-07-02) — alarm & health scale (issue #31 follow-up) ### Fixed - **Alarm & health read paths at scale.** The v1.7.1 PropertyCollector fix covered inventory only. `list_alarms` / `acknowledge_alarm` / `reset_alarm` still walked every VM + host + cluster + datacenter + datastore reading lazy `.name` / `.triggeredAlarmState` per object, and host hardware status, host services, and host-log scan swept all hosts with per-host lazy reads — each a separate SOAP round-trip that timed out on large vCenters. All now batch the needed properties in a single `PropertyCollector.RetrievePropertiesEx` call. Output shape unchanged. ## v1.7.1 (2026-07-02) — large-inventory scale fix (PropertyCollector, issue #31) ### Fixed - **Large-inventory scale (GitHub issue #31).** Inventory list/find operations (`list_vms`, `list_hosts`, `list_datastores`, `list_clusters`, `list_networks`, and the `find_*_by_name` helpers) walked a container view and then read pyVmomi *lazy* properties per object (`vm.config.hardware.numCPU`, `vm.runtime.host.name`, `len(host.vm)` …) — each a separate SOAP round-trip. On large vCenters (thousands of VMs / hundreds of hosts) this meant tens of thousands of round-trips, so even `limit=20` queries timed out. All of these now fetch every needed property in a single `PropertyCollector.RetrievePropertiesEx` call (paged via continuation tokens). Output shape is unchanged. Reported by juanpf-ha against an ~8,000-VM / ~340-host environment. ## v1.7.0 (2026-06-27) — guided onboarding + teaching auth errors ### Added - **`vmware-aiops init` — interactive first-run setup wizard.** Prompts for host / username / password and writes `config.yaml` + `.env` for you. The password is stored grep-safe (`b64:`, never plaintext on disk) and `.env` is locked to 0600, then the connection is verified. Replaces the manual "mkdir + cp config.example.yaml + edit YAML + chmod 600" dance. ### Changed - `doctor` now points to `vmware-aiops init` when config/credentials are missing (previously suggested a command that did not exist), keeping the manual steps as a fallback. - Authentication and TLS failures now print a teaching message naming the exact file and env var to fix (`~/.vmware-aiops/.env` password var, `config.yaml` username) plus a `verify_ssl: false` hint for self-signed labs. ## v1.6.1 (2026-06-24) ### Added - **`.env` passwords are auto-obfuscated to a grep-safe `b64:` form** on first load and decoded transparently at runtime — plaintext no longer sits in `~/./.env` for a casual `grep` to find. Values are read/written through python-dotenv's own parser, so the stored secret never drifts from the configured one (handles quotes, inline comments, trailing whitespace, and a password that literally starts with `b64:`). **Obfuscation, not encryption** — for real at-rest secrecy, inject the password from a secret manager instead of storing `.env`. New regression suite (10 cases) covers dotenv parity, the `b64:`-prefixed edge case, idempotency, and 0600 preservation. ## v1.6.0 (2026-06-22) — trust architecture: undo tokens + governed harness ### Added - **Undo-token recording** on reversible write tools (via vmware-policy 1.6.0 `@vmware_tool(undo=...)`): `vm_power_on`↔`vm_power_off`, `vm_create`→`vm_delete`, `vm_clone`→`vm_delete`, `vm_create_snapshot`→`vm_delete_snapshot`, `vm_set_ttl`→`vm_cancel_ttl`. Each successful write records an inverse descriptor (`_undo_id`); query/replay via the audit/undo tooling. - Inherits the harness trust-architecture upgrades: token/runaway budget guard, audit accountability fields (rationale/approved_by/risk_tier), and graduated-autonomy risk tiers. ### Changed - Requires **vmware-policy >= 1.6.0** (the `undo=` parameter lives there). Dependency pinned accordingly. ## v1.5.39 (2026-06-22) — snapshot delete: async + honest timeout (token-burn fix) ### Fixed - **Snapshot delete no longer burns the agent's context on slow consolidations.** `vm snapshot-delete` used the 300s wait meant for metadata ops while clone/migrate already used 600s — old/large delta disks (e.g. a ~3-year EVE-NG snapshot) always blew 300s and raised, so the agent thought the delete FAILED and improvised foreground polling, costing tens of thousands of tokens. Now: - default wait budget is 1800s (snapshot consolidation is the slowest write op); - timeout is honest — `_wait_for_task` raises `TaskStillRunning` carrying the task id (not a bare `TimeoutError`), and `delete_snapshot(wait=True)` returns a "still running, NOT failed — poll with vm task-status " message instead of raising; - async mode — `vm snapshot-delete --no-wait` (CLI) and `vm_delete_snapshot(wait=False)` (MCP, now the default) fire the delete and return a task id immediately, so the operation never blocks the context. ### Added - `vm task-status ` CLI command and `vm_task_status` MCP tool — poll a long-running async task (e.g. an async snapshot delete) by id; a garbage-collected task degrades to state `gone`, not an error. MCP tool count 43 → 44. ## v1.5.38 (2026-06-12) — backlog finish: MCP create/reconfigure, server split ### Added - `vm_create` and `vm_reconfigure` MCP tools (CLI had them, MCP didn't). Tool count 41 → 43. (#23) ### Changed - Refactored the oversized MCP server and OVA deploy module under the 800-line cap (split into `mcp_server/tools/*` + `ops/ova_deploy.py`); collapsed ~41 duplicated tool error-handlers into one decorator. Behavior-preserving — the 41 prior tools are byte-for-byte identical. (#22) ## v1.5.37 (2026-06-12) — backlog: OVA deploy robustness, multi-DC, snapshot/TTL safety ### Fixed - **OVA upload** now streams the VMDK in 8 MiB chunks (no whole-disk read into RAM) and posts `HttpNfcLeaseProgress` periodically, so large/slow uploads no longer hit the ~5-min lease abort. (#18) - **Multi-disk OVA** disks are mapped to device URLs by `importKey`/OVF File identity, not pop-order, so contents can't land on the wrong device. (#19) - **`create_snapshot`** no longer forces `quiesce` for memory-less snapshots (failed on Tools-less / freshly-deployed VMs); `quiesce` is now an explicit param defaulting to False. (#20) - **Datacenter/compute resolution** searches explicitly for `vim.Datacenter`/`vim.ComputeResource` instead of `childEntity[0]`, fixing wrong-DC selection and crashes on multi-DC / foldered inventories. (#21) - **`vm set-ttl`** (schedules an unattended auto-delete) now requires confirmation and supports `--dry-run`, and is listed in the destructive-ops docs. (#25) - MCP `_safe_error` now passes `ConnectionError` through so dropped connections show their hint. (#24) ## v1.5.36 (2026-06-12) — code-quality fix pack: teaching errors reach agents, TTL safety, CLI error translation ### Fixed - **MCP `_safe_error` now passes domain teaching exceptions through** (VMNotFoundError, GuestOpsError, TaskFailedError, ClusterNotFoundError, ClusterError, TimeoutError) — agents previously got a generic "operation failed" instead of "VM 'web-99' not found…". - **Scheduled TTL auto-delete no longer drops the entry on a transient failure** — the VM is retried instead of silently never being deleted (entry removed only on success / VMNotFound). - **Active-alarm listing deduplicated** — alarms propagated to ancestor objects were counted up to 4×. - **Guest file transfer / OVA upload `urlopen` calls now time out** (300s) and close cleanly, so a stalled connection can't hang the MCP stdio server. - **`create_vm` plan action** no longer overrides the default network with `None`. - **Alarm container-view double-Destroy** fixed (try/finally). ### Added - CLI error-translation decorator: bad VM name / missing password env / unreachable vCenter now print one teaching line + exit 1 instead of a raw traceback. ## v1.5.35 (2026-06-10) — security hardening: safe errors, path validation, tighter file perms ### Fixed - **MCP tools no longer return raw exception text / tracebacks** to the agent — a central `_safe_error()` logs full detail server-side and returns a sanitized message. - **Guest file transfer** validates paths: upload source must be a real readable file; download refuses to write through a symlink. - **Audit dir/log** 0700/0600; TTL store, plans, and image registry are written 0600. - **Webhook** response bodies are CR/LF-stripped before logging (no log injection). This release aligns the whole family back to a single version (1.5.35); vmware-policy and vmware-pilot return to the shared number after sitting at 1.5.22. ## v1.5.32 (2026-06-08) — Invented pyVmomi methods fixed + alarm/sensor/migrate corrections A pyVmomi introspection audit found two invented SDK methods (passed import, lint, and --help; crashed at runtime) and two silent-logic bugs. ### Fixed - `cluster remove-host`: `Folder.MoveIntoFolder_Task([host])` — the previously called `Folder.MoveInto_Task` does not exist in pyVmomi (AttributeError on every invocation). - `alarm reset`: rewritten on `AlarmManager.ClearTriggeredAlarms` with an AlarmFilterSpec (`SetAlarmStatus` never existed in pyVmomi). Note the real semantics: clears ALL triggered alarms matching the named alarm's entity type and status — documented, and the CLI now requires double confirmation. - VM migrate: shared-datastore access check compares `HostMount.key` (a HostSystem was compared against HostMount objects, so shared-storage vMotion was always refused). - Hardware sensors: health from `healthState.key` (green/yellow/red); the previous code reported the sensor *category* as its status, so degraded sensors could never be detected. Sensor type kept as a separate column. - Event severity sets: `DVPortgroupReconfiguredEvent` casing (never matched). ### Defense - New vim-attribute conformance regression: 121 property chains + ~50 method names validated against pyVmomi metadata, plus a source scan that fails the build if the invented names reappear. ## v1.5.30 (2026-06-07) — Tool description quality (Glama TDQS) ### Improved - Rewrote 11 MCP tool descriptions flagged by Glama's Tool Description Quality Score review (cluster_add_host, attach_iso_to_vm, snapshot tools, deploy tools, and more): per-parameter semantics, return fields, sibling-tool routing, prerequisites, and behavioral transparency. Also covered the two tools added after the last Glama scan (deploy_linked_clone, cluster_info). - Annotated guest-side `/tmp` path in guest_ops with `# nosec B108` (false positive: path is inside the remote guest VM, uuid-randomized). - No functional changes. ## v1.5.29 (2026-05-29) — Documentation Sync for v1.5.26 Tools ### Documentation - SKILL.md, capabilities.md, cli-reference.md now reflect the 7 VM lifecycle MCP tools added in v1.5.26 (`vm_clone`, `vm_migrate`, `vm_delete`, `vm_create_snapshot`, `vm_revert_snapshot`, `vm_delete_snapshot`, `vm_list_snapshots`). - MCP tool count corrected from 34 → **41** (8 read / 33 write) based on `[READ]`/`[WRITE]` markers in `mcp_server/server.py`. - `vm_guest_exec_output` and `vm_create_plan` reclassified Read → Write to match their docstring markers. - CLI Quick Reference now includes new flags (`--to-host`, `--to-datastore`, `--power-on`) and 4 snapshot subcommands. ### No code changes This is a documentation-only release closing the v1.5.26 doc gap. Family v1.5.29 alignment release. ## v1.5.28 (2026-05-20) **Fix `subclass() arg 1 must be a class` in goose/old mcp environments** — v1.5.25–1.5.27 replaced `X | None` with `Optional[X]` but kept `from __future__ import annotations` at the top of `mcp_server/server.py`. Under mcp 1.10–1.13 (which Goose and some sandboxes pin), `Tool.from_function` calls `issubclass(param.annotation, Context)` without resolving forward refs, so string annotations crash the entire server load. Removed `from __future__ import annotations` from `mcp_server/server.py` so annotations are real classes; verified all tools load under mcp 1.10 and 1.14. Traceback location: `mcp/server/fastmcp/tools/base.py:67`. CLAUDE.md 踩坑 #33 updated. family_smoke.sh Check 4b now installs `mcp==1.10.0` to catch this regression class. ## v1.5.27 (2026-05-20) **Loosen Python requirement: now supports Python >= 3.10** — v1.5.25/26 fixed the PEP 604 root cause in MCP tool signatures (Optional[X] instead of X | None), but kept `requires-python = ">=3.11"` and a 3.11 hard guard in `mcp_cmd`. Both relaxed to 3.10 so users on Python 3.10 (e.g. Goose default sandbox, Ubuntu 22.04 system python) can install and run directly without a Python upgrade. - `pyproject.toml`: `requires-python = ">=3.10"` (was `>=3.11`; VMware-VKS was `>=3.12`, now also `>=3.10` for family alignment). - `/cli.py` `mcp_cmd()`: version guard now triggers on `< (3, 10)`. - Behavior on Python 3.10 matches 3.11/3.12 — the Optional[X] fix from v1.5.25 is what actually enables this; this release just stops blocking installs. --- ## v1.5.26 **Family-wide MCP server fix — Python 3.10 compatibility (踩坑 #33)** — `vmware-aiops mcp` crashed at decorator time on Python 3.10 with `subclass() arg 1 must be a class`. Root cause: `mcp_server/server.py` used PEP 604 `X | None` in tool signatures plus `from __future__ import annotations`; on Python 3.10 + older mcp/pydantic combos, `typing.get_type_hints()` evaluates `"str | None"` to a `types.UnionType` instance, which FastMCP/Pydantic then feeds to `issubclass()`. Reported by a goose user (qwen3.6:27, Python 3.10). - `mcp_server/server.py`: all `X | None` → `Optional[X]`; ops layer untouched. - `/cli.py` `mcp_cmd()`: hard guard — exits with installation fix command if Python < 3.11 (defense in depth, our actual lower bound). - `pyproject.toml`: `mcp[cli]>=1.10,<2.0` (was `>=1.0`) so uv doesn't pick an ancient version that has the same issubclass bug. - **fix — clone falls on template's host (real user incident)** — `clone_vm()` built an empty `vim.vm.RelocateSpec()`, so vCenter placed the clone on the source VM/template's host+datastore. CLI `vmware-aiops vm clone` now takes `--to-host` and `--to-datastore`; same for `deploy_from_template`, `linked_clone`, batch variants. - **fix — migrate fails when no shared storage** — `migrate_vm()` only set host+pool; cross-host vMotion in homelab setups (Office NAS vs Home SSD) hit `destination host has no access to the source datastores`. Now pre-flights storage accessibility and gives a teaching error pointing to `--to-datastore`. - **mcp — 7 new write tools** — `vm_clone` / `vm_migrate` / `vm_delete` / `vm_create_snapshot` / `vm_revert_snapshot` / `vm_delete_snapshot` / `vm_list_snapshots`. CLI exposed these but MCP didn't — agents using vmware-aiops via MCP could not clone, migrate, snapshot, or delete VMs. - **fix — `cli/mcp_config.py` NameError** — used `json.loads` without importing json. `mcp-config install` crashed on first invocation when merging into an existing config file. - **fix — fault chain preserved in `_wait_for_task()`** — previously dropped `faultCause` and `faultMessage`, so users got "Task failed: A specified parameter was not correct" with no way to tell whether host, datastore, or pool was the offender. **Tooling — family smoke gains MCP schema-build check** — `scripts/family_smoke.sh` new Check 4b runs `asyncio.run(mcp.list_tools())` per skill, forcing FastMCP to build Pydantic models for every declared tool. Supports both module-level `mcp` and `build_server()` factory patterns. **Docs — CLAUDE.md gains 踩坑 #33 (PEP 604 / Python 3.10) and #34 (CLI/MCP exposure parity).** --- ## v1.5.24 (2026-05-19) **Fix — pyVmomi 8.x compatibility (踩坑 #32)** — `connection.py` previously set `si._vmware__verify_ssl = ...` on the pyVmomi `ServiceInstance`. pyVmomi 8.x rejects attribute writes on `ManagedObject` with `Managed object attributes are read-only`, which surfaced as `vmware- doctor` → `vSphere authentication: Auth failed: Managed object attributes are read-only` on vCenter 8.0U3 even though raw `SmartConnect()` worked fine. - `connection.py`: introduce module-level `_SI_VERIFY_SSL: dict[int, bool]` keyed by `id(si)` plus `get_verify_ssl(si)` helper. Cleanup is wired into the same `atexit` hook that runs `Disconnect`. - Downstream consumers (`ops/guest_ops.py`, `ops/vm_deploy.py`, `ops/supervisor.py`) switched from `getattr(si, "_vmware_*_verify_ssl", True)` to `get_verify_ssl(si)`. - `scripts/family_smoke.sh`: new cross-skill check forbids `setattr` on pyVmomi ManagedObjects across the entire family (catches the same regression in future). ## v1.5.23 (2026-05-19) **VCF 9.0 / 9.1 compatibility declared** — family-wide docs sync. - **docs:** README version-compatibility table now explicitly lists vSphere 9.0 / 9.0U1 / 9.1 as ✅ Full. pyVmomi 8.0.3+ (currently pinned `<10.0`) continues to work against vSphere 9 SOAP API; no code changes required. - **docs:** Added `Official Broadcom References` pointer to [VCF Python SDK](https://developer.broadcom.com/sdks) (the new unified SDK in VCF 9+ that bundles pyVmomi + vSAN SDK) and [Developer Portal Tools](https://developer.broadcom.com/tools) (PowerCLI 9.1, ESXCLI, OVF Tool). - **chore:** `tests/fixtures/token_corpus/` added to `.gitignore` (local-only test data). - **align:** Family v1.5.23 — all 9 skills tracking VCF 9.0 / 9.1 compatibility declaration. ## v1.5.22 (2026-05-08) **Family alignment** — no source changes in this skill. - **align:** Tracks v1.5.22 family bump driven by Smithery onboarding for vmware-avi / vmware-harden / vmware-pilot. ## v1.5.21 (2026-05-08) **Family alignment** — no source changes in this skill. - **deps:** Bumped `python-multipart` 0.0.26 → 0.0.27 (transitive, fixes GHSA HIGH DoS via unbounded multipart headers). - **align:** Tracks v1.5.21 family bump driven by vmware-monitor folder_path feature (community PR #11). ## v1.5.20 (2026-05-08) **Family alignment** — no source changes in this skill. - **align:** Tracks v1.5.20 family bump driven by vmware-nsx-security and vmware-aria PyPI README `mcp-name:` ownership marker fix required by MCP Registry validation. Other 7 skills already had the marker; this release re-publishes them to keep the family version aligned per CLAUDE.md policy. - **registry:** All 9 skills now registered on registry.modelcontextprotocol.io as `isLatest=true`. ## v1.5.19 (2026-05-06) **Family alignment** — no source changes in this skill. - **build:** Bumped `requires-python` from `>=3.10` to `>=3.11` (regression eval suite uses `tomllib`, a Py3.11+ stdlib module). - **smoke:** Family `scripts/family_smoke.sh` now adds Check 3b — recursive `--help` on every Typer subcommand to trigger lazy imports. This catches the `import re`-style bug class without needing live infrastructure (yjs review 2026-05-06; CLAUDE.md 踩坑 #27). - **align:** Tracks v1.5.19 fixes in vmware-nsx (CLI import bug, CRITICAL), vmware-vks (delete_tkc_cluster ApiClient leak), vmware-harden (snapshot_id indexes + LEFT JOIN report), and vmware-policy (approval gate AND→OR + singleton lock). ## v1.5.18 (2026-05-02) **Family alignment + tooling normalization** — no source changes in this skill. - **dev:** Migrated `[project.optional-dependencies] dev` → `[dependency-groups] dev` (PEP 735) so `uv sync --group dev` works uniformly across the family. Canonical set: `pytest>=8.0,<10.0`, `pytest-cov`, `ruff`. - **test:** New `tests/eval/regression/test_release_blockers.py` (5 evals) catches the v1.5.x release blockers — missing `mcp_server` in wheel, AST-detected unimported runtime names (e.g. `re.match()` without `import re`), Typer app load failure, module import errors. Run via `pytest tests/eval/regression/`. - **align:** Family version bump to v1.5.18. ## v1.5.17 (2026-05-01) **Family alignment** — no source changes in this skill. This release tracks vmware-pilot v1.5.17 (new `investigate_alert` template + `review_workflow` MCP tool + `parallel_group` step type) and vmware-policy v1.5.17 (L5 pattern matcher integrated into `@vmware_tool`). Both work with the existing skill MCP surface unchanged. - **align:** Family version bump to v1.5.17. ## v1.5.16 (2026-04-30) **Enterprise Harness Engineering alignment** — adapted from the Linkloud × addxai framework articles ([part 1](https://mp.weixin.qq.com/s/hz4W7ILHJ1yz_pG0Z1xP-A), [part 2](https://mp.weixin.qq.com/s/F3qYbyB3S8oIqx-Y4BrWNQ)). - **docs:** New `references/investigation-protocol.md` — causal-chain root cause analysis protocol with 4 completeness criteria (falsifiability/sufficiency/necessity/mechanism) and up-to-3-rounds deepening loop. Common Workflows now point to it before any diagnostic remediation. - **docs:** "Automation Level Reference" section in `references/capabilities.md` — every operation tagged L1-L5 per the EHE framework. - **docs:** Common Workflows in `SKILL.md` rewritten from step-by-step API call lists into judgment-encoded expert decision logic — pre-flight checks, decision rules, safety gates surfaced explicitly (deploy lab env, batch clone, vMotion). - **align:** Family version bump to v1.5.16. ## v1.5.15 (2026-04-29) **UX improvements from real user feedback** - **feat:** New top-level CLI subcommand `vmware-aiops mcp` starts the MCP server. Single command, single binary on PATH after `uv tool install vmware-aiops` — no more `uvx --from`, no PyPI re-resolve, no TLS-proxy issues. - **feat:** Default `verify_ssl: true` on new targets (was `false`). Self-signed cert environments must now opt in explicitly with `verify_ssl: false` in `config.yaml`. Strengthens default security posture and addresses VirusTotal "disableSslCertValidation" finding. - **docs:** README, SKILL.md, setup-guide.md, and all `examples/mcp-configs/*.json` switched to `command: "vmware-aiops"`, `args: ["mcp"]`. uvx form moved to fallback section with TLS-proxy troubleshooting note. - **compat:** Legacy `vmware-aiops-mcp` console script kept — existing user configs continue to work unchanged. ## v1.5.14 (2026-04-21) - Align with VMware skill family v1.5.14 (code review follow-up fixes by @yjs-2026) ## v1.5.13 (2026-04-21) **Bug fixes from code review 2026-04-20** - **fix(P0):** `vm_deploy.py` — SSL verification on OVA upload now correctly reads `_vmware_aiops_verify_ssl` from ServiceInstance instead of non-existent attribute on HttpNfcLease; self-signed cert environments no longer fail during VMDK upload - **fix(P0):** `vm_deploy.py` — disk upload loop no longer mutates dict during iteration; uses explicit list extraction instead - **fix:** `log_scanner.py` — `BrowseDiagnosticLog` now probes total line count first, then reads last N lines correctly (was passing line count as start offset) ## v1.5.12 (2026-04-17) **Bug fixes from code review by @yjs-2026** - **fix:** `_count_children` — guard `childSnapshotList` against None (pyVmomi may return None instead of empty list), preventing `TypeError` on snapshot count - **fix:** `migrate_vm` — return clear error when `vm.runtime.host` is None (VM provisioning / detached), instead of proceeding with confusing API failure - **fix(security):** `_upload_disk` — SSL verification now respects target config instead of unconditionally disabling cert checks for all VMDK uploads ## v1.5.11 (2026-04-17) - Align with VMware skill family v1.5.11 (AVI 22.x fixes from @timwangbc) ## v1.5.10 (2026-04-16) - Security: bump python-multipart 0.0.22→0.0.26 (DoS via large multipart preamble/epilogue) - Align with VMware skill family v1.5.10 ## v1.5.8 (2026-04-15) - Fix: Security — `guest_ops.py` shell-wrapped commands for guest_exec_with_output did not quote the temp output path. Now uses `shlex.quote()` on both the temp path and the wrapped shell string to prevent command injection via special chars in either. - Fix: Security — OVA/tar extraction had no size limit (tar-bomb vulnerability). Added per-member cap of 2 GiB and aggregate cap of 20 GiB. Also rejects symlinks pointing outside the destination directory and rejects device/block/FIFO files. - Fix: Guest file upload/download SSL verification now honours `target.verify_ssl` config (previously hardcoded `CERT_NONE`). Connection manager tags `si._vmware_aiops_verify_ssl` at connect time for downstream use. - Refactor: monolithic `cli.py` (1726 lines, 47 commands) split into `cli/` package (12 focused modules: vm, deploy, cluster, scan, plan, alarm, hub, mcp_config, doctor + `_common`/`_root`/`__init__`). Entry point `vmware-aiops = "vmware_aiops.cli:app"` unchanged. - Align with VMware skill family v1.5.8 ## v1.5.7 (2026-04-15) - Align with VMware skill family v1.5.7 (Pilot `__from_step_N__` fix + VKS SSL/timeout fix) ## v1.5.6 (2026-04-15) - Fix: CRITICAL — `mcp_server` module missing from PyPI wheel due to missing hatch packages config. `vmware-aiops-mcp` failed with `ModuleNotFoundError: No module named 'mcp_server'`. Added `[tool.hatch.build.targets.wheel] packages = ["vmware_aiops", "mcp_server"]` - Align with VMware skill family v1.5.6 ## v1.5.5 (2026-04-15) - Align with VMware skill family v1.5.5 (NSX critical `import re` hotfix) ## v1.5.4 (2026-04-14) - Security: bump pytest 9.0.2→9.0.3 (CVE-2025-71176, insecure tmpdir handling) - Deps: update rich version constraint from <15.0 to <16.0 (rich 15.0 drops Python 3.8, project requires >=3.10) ## v1.5.0 (2026-04-12) ### Anthropic Best Practices Integration - **[READ]/[WRITE] tool prefixes**: All MCP tool descriptions now start with [READ] or [WRITE] to clearly indicate operation type - **Read/write split counts**: SKILL.md MCP Tools section header shows exact read vs write tool counts - **Negative routing**: Description frontmatter includes "Do NOT use when..." clause to prevent misrouting - **Broadcom author attestation**: README.md, README-CN.md, and pyproject.toml include VMware by Broadcom author identity (zhouwei008@gmail.com) to resolve Snyk E005 brand warnings ### AIops-specific - **limit parameter**: list_vcenter_alarms now supports limit parameter ## v1.4.9 (2026-04-11) - Fix: require explicit VMware/vSphere context in skill routing triggers (prevent false triggers on generic "clone", "deploy", "alarms" etc.) - Fix: clarify vmware-policy compatibility field (Python transitive dep, not a required standalone binary) ## v1.4.8 (2026-04-09) - Security: bump cryptography 46.0.6→46.0.7 (CVE-2026-39892, buffer overflow) - Security: bump urllib3 2.3.0→2.6.3 (multiple CVEs) [VMware-VKS] - Security: bump requests 2.32.5→2.33.0 (medium CVE) [VMware-VKS] ## v1.4.7 (2026-04-08) - Fix: align openclaw metadata with actual runtime requirements - Fix: standardize audit log path to ~/.vmware/audit.db across all docs - Fix: update credential env var docs to correct VMWARE__PASSWORD convention - Fix: declare .env config and vmware-policy optional dependency in metadata # Release Notes / 版本发布历史 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.4.5 — 2026-04-03 - **Security**: bump pygments 2.19.2 → 2.20.0 (fix ReDoS CVE in GUID matching regex) - **Infrastructure**: add uv.lock for reproducible builds and Dependabot security tracking ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.4.0 — 2026-03-29 ### Architecture: Unified Audit & Policy - **vmware-policy integration**: All MCP tools now wrapped with `@vmware_tool` decorator - **Unified audit logging**: Operations logged to `~/.vmware/audit.db` (SQLite WAL), replacing per-skill JSON Lines logs - **Policy enforcement**: `check_allowed()` with rules.yaml, maintenance windows, risk-level gating - **Sanitize consolidation**: Replaced local `_sanitize()` with shared `vmware_policy.sanitize()` - **Risk classification**: Each tool tagged with risk_level (low/medium/high) for confirmation gating - **Agent detection**: Audit logs identify calling agent (Claude/Codex/local) - **New family members**: vmware-policy (audit/policy infrastructure) + vmware-pilot (workflow orchestration) ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.3.0 — 2026-03-26 ### Slimdown: Remove duplicate tools / 瘦身去重 **Breaking change**: 13 MCP tools and corresponding CLI commands removed to eliminate overlap with companion skills. **Removed tools (→ use companion skill instead)**: - Inventory: `list_virtual_machines`, `list_esxi_hosts`, `list_all_datastores`, `list_all_clusters` → **vmware-monitor** - Health: `get_alarms`, `get_events`, `vm_info` → **vmware-monitor** - Datastore cache: `list_cached_images` → **vmware-storage** - Storage/iSCSI: `storage_iscsi_enable`, `storage_iscsi_status`, `storage_iscsi_add_target`, `storage_iscsi_remove_target`, `storage_rescan` → **vmware-storage** **Kept in aiops**: `browse_datastore`, `scan_datastore_images` (basic datastore browsing for deployment workflows). **Security fix**: Added `_sanitize()` prompt injection defense to `datastore_browser.py` (backported from vmware-storage). **MCP tool count**: 44 → 31 (13 removed, zero new). ### Docs / Skill optimization - SKILL.md restructured with progressive disclosure (3-level loading) - Created `references/` directory: cli-reference.md, capabilities.md, setup-guide.md - Added trigger phrases to YAML description for better skill auto-loading - Added Common Workflows section (Deploy lab, Batch clone, Migrate VM) - Added Troubleshooting section (5 common issues) - README.md and README-CN.md updated with Companion Skills, Workflows, Troubleshooting ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.2.3 — 2026-03-22 ### Docs / SKILL.md restructure - Reorder SKILL.md: "What This Skill Does" table and Quick Install first, routing table last — improves Skills.sh/ClawHub page readability. ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.2.2 — 2026-03-22 ### Security / 安全修复 - Fix: webhook URLs (`SLACK_WEBHOOK_URL`, `DISCORD_WEBHOOK_URL`) moved from `required` to `optional` in OpenClaw metadata — resolves ClawHub "Suspicious" security flag. - 修复:将 webhook URL 从 OpenClaw metadata 的 `required` 移至 `optional`,消除 ClawHub 安全告警。 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.2.1 — 2026-03-22 ### Skill Routing / Skill 智能路由推荐 - SKILL.md 新增 **Related Skills — Skill Routing** 路由表:遇到存储相关请求推荐 vmware-storage,遇到只读监控需求推荐 vmware-monitor,减少 Agent 工具数量和上下文占用。 - Added **Related Skills** routing table to SKILL.md: recommends vmware-storage for storage tasks, vmware-monitor for read-only monitoring — keeps tool count and context usage minimal. ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.2.0 — 2026-03-21 ### Guest Exec with Output Capture / Guest 命令输出捕获 - **`vm_guest_exec_output`** (32nd MCP tool) — Execute a shell command inside a VM and automatically capture stdout + stderr. 在 VM 内执行 shell 命令并自动捕获 stdout + stderr,无需手动重定向和下载。 - Auto-detects OS: Linux/Windows shell selected by `vm.guest.guestFamily` / 自动检测操作系统,无需用户指定 shell - Redirects output to a temp file, downloads it, cleans up automatically / 自动重定向到临时文件、下载、清理,一步返回结果 - Returns `{exit_code, stdout, stderr, timed_out, os_family}` / 返回结构化输出 ### mcp-config install — Auto-write Agent Config / 自动写入 Agent 配置 - **`vmware-aiops mcp-config install --agent `** — Directly writes MCP server config into the target agent's config file. 直接将 MCP server 配置写入目标 Agent 的配置文件,无需手动编辑 JSON/YAML。 - Supports: claude-code, cursor, goose, continue, vscode, localcowork, mcp-agent / 支持 7 种 Agent - JSON merge (non-destructive) + auto-backup on conflict / JSON 合并(非破坏性)+ 冲突时自动备份 - Use `--yes` to skip confirmation prompt / 使用 `--yes` 跳过确认提示 ### Docker One-Command Launch / Docker 一键启动 - **Dockerfile + docker-compose.yml** — Run MCP server without installing Python or venv. 无需安装 Python 或 venv,一条命令启动 MCP Server。 ```bash docker compose up -d ``` Config dir `~/.vmware-aiops` mounted read-only into container. / 配置目录以只读方式挂载到容器。 ### Cursor Integration Guide / Cursor 集成文档 - **`docs/integrations/cursor.md`** — Full guide for using vmware-aiops as a Cursor MCP server. 完整的 Cursor 集成指南,包含自动安装、手动配置、32 个工具说明、使用示例和排障指南。 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v1.1.0 — 2026-03-21 > **Version unification release / 版本统一发布** > All platforms (PyPI, GitHub Release, MCP Registry, Skills.sh, ClawHub, Smithery) now share the same version number starting from v1.1.0. > 所有平台(PyPI、GitHub Release、MCP Registry、Skills.sh、ClawHub、Smithery)从 v1.1.0 起统一版本号。 ### Cluster Management & iSCSI Configuration (Closes #8) / 集群管理与 iSCSI 配置 - **Cluster operations / 集群操作**: List clusters, DRS/HA status, resource pool info. 列出集群、DRS/HA 状态、资源池信息。 - **iSCSI adapter configuration / iSCSI 适配器配置**: Enable iSCSI adapter, add/remove targets, rescan storage — directly from CLI without switching to ESXi Host Client or vCenter UI. 启用 iSCSI 适配器、添加/移除目标、重新扫描存储——无需切换到 ESXi Host Client 或 vCenter UI。 ### Guest Operations API (3 MCP tools + CLI) / Guest Operations API - `vm_guest_exec` — Execute commands inside VMs via VMware Tools / 在 VM 内执行命令 - `vm_guest_upload` — Upload files to VMs / 上传文件到 VM - `vm_guest_download` — Download files from VMs / 从 VM 下载文件 ### Plan → Apply Mode (4 MCP tools) / 计划→执行模式 Terraform-style plan/apply for multi-step operations: 类似 Terraform 的多步骤操作计划/执行模式: - `vm_create_plan` — Validate & generate plan with rollback info / 生成带回滚信息的操作计划 - `vm_apply_plan` — Execute sequentially, stop on failure / 顺序执行,失败即停 - `vm_rollback_plan` — Reverse executed steps / 回滚已执行步骤 - `vm_list_plans` — List pending/failed plans / 列出待执行/失败的计划 ### TTL Auto-Destroy / VM 自动过期销毁 - `vm_set_ttl` / `vm_cancel_ttl` / `vm_list_ttl` — Assign time-to-live to VMs, auto-delete on expiry. 为 VM 设置存活时间,到期自动删除,防止资源泄漏。 ### Clean Slate / 一键重置 - `vm_clean_slate` — Revert VM to baseline snapshot in one command. 一键恢复 VM 到基线快照。 ### VM Deploy & Datastore Browser / VM 部署与数据存储浏览 - `vm_deploy` — Deploy VMs from OVA/OVF templates / 从 OVA/OVF 模板部署 VM - `datastore_browse` — Browse datastore file system / 浏览数据存储文件系统 ### Doctor & MCP Config Generator / 诊断与配置生成 - `vmware-aiops doctor` — 8-check environment diagnostic / 8 项环境诊断 - `vmware-aiops mcp-config generate --agent ` — Generate config for 7 local AI agents / 为 7 种本地 AI Agent 生成配置 ### Inventory Enhancements / 资源清单增强 - `list_vms` with limit/sort_by/power_state/fields filtering / 支持过滤、排序、字段选择 - Auto-tiered response for large inventories (>50 VMs) / 大规模环境自动精简返回 ### Security Hardening / 安全加固 - Prompt injection protection with boundary markers / Prompt 注入防护(边界标记) - Double confirmation for all destructive operations / 所有破坏性操作双重确认 - Dry-run mode for all destructive commands / 所有破坏性命令支持预演模式 - Audit logging (JSONL) for all operations / 全操作审计日志 - `.env` file permission check at startup / 启动时检查 .env 文件权限 - Bandit security scan: 0 issues / Bandit 安全扫描零问题 ### Platform & Integration / 平台与集成 - MCP tools: 9 → 31 - MCP Registry, Skills.sh, ClawHub, Smithery, Glama, mcp.so, Cline Marketplace published - Local agent config templates for 7 agents (Claude Code, Cursor, Goose, LocalCowork, mcp-agent, Continue, VS Code Copilot) - Ollama end-to-end setup guide **PyPI**: `uv tool install vmware-aiops==1.1.0` ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.5.5 — 2026-03-05 ### Usage Mode Optimization / 使用模式优化 - **Platform-aware calling priority / 按平台推荐调用模式**: Claude Code and Cursor users get MCP-first experience (structured tool calls, no interactive confirmation needed). Aider, Codex, Gemini CLI, and local models (Ollama) default to CLI mode for lower context overhead and universal compatibility. Claude Code / Cursor 用户推荐 MCP 优先(结构化调用,无需交互确认)。Aider、Codex、Gemini CLI 及本地模型(Ollama)默认 CLI 模式,上下文开销更低,兼容性更强。 - **Install order update / 安装顺序调整**: Skills.sh (`npx skills add`) is now the primary install method; ClawHub as secondary option. Skills.sh 安装方式提升为首选;ClawHub 作为备选。 - **MCP load tip / MCP 加载提示**: Added tip for MCP-native tools to check MCP server status (`/mcp`) before use. 新增 MCP 原生工具的加载状态检查提示。 **Files updated / 变更文件**: `skills/vmware-aiops/SKILL.md`, `plugins/.../SKILL.md`, `README.md`, `README-CN.md` ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.5.4 — 2026-03-03 ### Security Hardening: Prompt Injection Protection / 安全加固:Prompt 注入防护 - **Boundary markers / 边界标记**: All vSphere-sourced content (event messages, host logs) is now wrapped in explicit boundary markers (`[VSPHERE_EVENT]...[/VSPHERE_EVENT]`, `[VSPHERE_HOST_LOG]...[/VSPHERE_HOST_LOG]`) so downstream LLM agents can distinguish trusted output from untrusted vSphere data. 所有 vSphere 来源内容(事件消息、主机日志)现在用显式边界标记包裹,使下游 LLM Agent 能区分可信输出和不可信的 vSphere 数据。 - **Comprehensive control character sanitization / 全面控制字符清理**: Replaced simple null-byte removal with regex-based stripping of all C0/C1 control characters (except `\n` and `\t`). Prevents prompt injection via embedded control sequences. 用正则替换原来的简单空字节移除,清理所有 C0/C1 控制字符(保留换行和制表符),防止通过嵌入控制序列进行 Prompt 注入。 - **MCP server documentation / MCP 服务文档**: Added comprehensive module docstring to `mcp_server/server.py` with security considerations (credential handling, transport security, Read vs Write tool classification) to resolve Socket "Obfuscated File" audit flag. 为 `mcp_server/server.py` 添加完整模块文档和安全说明,解决 Socket 审计的 "Obfuscated File" 标记。 - **Security section in SKILL.md / SKILL.md 安全段落**: Added explicit Security section covering TLS verification, credential handling, webhook data scope, prompt injection protection, and code review guidance. SKILL.md 新增安全段落,涵盖 TLS 验证、凭据处理、Webhook 数据范围、Prompt 注入防护和代码审查建议。 - **README security context / README 安全上下文**: Updated Safety Features table and Security Best Practices in both English and Chinese READMEs. Removed internal API reference (`ConnectionManager.from_config()`). 更新中英文 README 的安全特性表格和安全最佳实践,移除内部 API 引用。 **Files updated / 变更文件**: `vmware_aiops/scanner/log_scanner.py`, `mcp_server/server.py`, `skills/vmware-aiops/SKILL.md`, `plugins/.../SKILL.md`, `README.md`, `README-CN.md` ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.5.3 — 2026-02-28 ### Dry-Run Mode / 预演模式 - **`--dry-run` for all destructive commands / 所有破坏性命令支持 `--dry-run`**: Add `--dry-run` to any destructive command to preview the exact API call, target, parameters, and current VM state — without executing. Covers: `power-on`, `power-off`, `create`, `delete`, `reconfigure`, `snapshot-create`, `snapshot-revert`, `snapshot-delete`, `clone`, `migrate`. 所有破坏性命令支持 `--dry-run` 参数,预览将要执行的 API 调用、目标、参数和当前 VM 状态,但不实际执行。 ```bash vmware-aiops vm power-off my-vm --dry-run # [DRY-RUN] API Call: vim.VirtualMachine.ShutdownGuest() # [DRY-RUN] Current: {'power_state': 'poweredOn'} # [DRY-RUN] Expected: {'power_state': 'poweredOff'} # [DRY-RUN] Run without --dry-run to execute. ``` - **Dry-run audit logging / 预演审计记录**: Dry-run invocations are logged to audit trail with `result: "dry-run"` for compliance tracking. 预演操作同样记录到审计日志,`result` 为 `"dry-run"`。 ### Other / 其他 - **FQDN recommended / 推荐使用 FQDN**: Config examples updated to prefer FQDN over bare IP addresses. Required for Kerberos authentication; IP still accepted. 配置示例改为推荐 FQDN,Kerberos 认证需要 FQDN;IP 地址仍然支持。 - **Cross-repo documentation / 跨仓库文档**: Added [VMware-Monitor](https://github.com/vmware-skills/VMware-Monitor) cross-references to all skill files and README. 所有 skill 文件和 README 添加了独立 VMware-Monitor 仓库交叉引用。 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.5.2 — 2026-02-28 ### Security Hardening / 安全加固 - **Remove --confirm bypass flag / 移除 --confirm 绕过参数**: The `vm delete --confirm` flag that allowed skipping double confirmation has been removed. All destructive operations now require mandatory double confirmation with no bypass mechanism. 移除了 `vm delete` 的 `--confirm` 跳过确认参数。所有破坏性操作强制双重确认,无法绕过。 - **Double confirmation for all destructive ops / 所有破坏性操作双重确认**: Extended double confirmation to `snapshot-revert`, `snapshot-delete`, `clone`, and `migrate` (previously only `power-off`, `delete`, `reconfigure` were protected). 将双重确认扩展到快照恢复、快照删除、克隆、迁移操作(之前仅关机、删除、配置变更受保护)。 - **Rejected confirmation audit logging / 拒绝操作审计记录**: When a user declines a confirmation prompt, the rejection is now logged to the audit trail with `result: "rejected"`. 用户拒绝确认时,拒绝操作也会被记录到审计日志中。 - **Input validation / 输入参数校验**: VM name (1-80 chars, no leading `-`/`.`), CPU (1-128), memory (128-1048576 MB), disk (1-65536 GB) are now validated before execution. VM 名称(1-80 字符,不以 `-`/`.` 开头)、CPU(1-128)、内存(128-1048576 MB)、磁盘(1-65536 GB)参数校验。 - **`.env` file permission check / `.env` 文件权限检查**: At startup, warns if `~/.vmware-aiops/.env` has permissions wider than `600` (owner-only). 启动时检查 `.env` 文件权限,如果非 owner-only(600)则发出警告。 ### Files Updated / 更新文件 - `vmware_aiops/cli.py` — Removed --confirm bypass, added double confirm + state preview to 4 more operations, added input validation, rejection audit logging - `vmware_aiops/config.py` — Added `.env` permission check at startup - All SKILL.md / AGENTS.md / README files — Updated Safety Features/Rules with new security measures ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.5.1 — 2026-02-28 ### New Features / 新功能 - **Plan → Confirm → Execute → Log workflow / 计划→确认→执行→日志工作流**: All state-modifying operations now follow a structured 4-step workflow. Before executing destructive actions, the CLI shows the current VM state (power, CPU, memory, snapshots), presents a before/after change summary, asks for confirmation, then logs the operation with full audit trail. 所有修改状态的操作现在遵循结构化的 4 步工作流。执行修改操作前,CLI 展示当前 VM 状态(电源、CPU、内存、快照),呈现变更前后对比,请求确认,然后记录完整审计日志。 - **Audit logging / 操作审计日志**: New `AuditLogger` class (`vmware_aiops/notify/audit.py`) writes all operations to `~/.vmware-aiops/audit.log` in JSONL format. Each entry includes: timestamp, target, operation, resource, parameters, before_state, after_state, result, user, and skill (aiops/monitor). Follows the same append-only JSONL pattern as the existing `ScanLogger`. 新增 `AuditLogger` 类,将所有操作写入 `~/.vmware-aiops/audit.log`(JSONL 格式)。每条记录包含:时间戳、目标、操作类型、资源名、参数、操作前状态、操作后状态、结果、用户、技能类型。 - **State preview before destructive operations / 修改操作前状态预览**: Power-off, delete, and reconfigure commands now query and display the current VM state (power state, CPU, memory, snapshot count, host, IP) before asking for confirmation. 关机、删除、调整配置命令现在在请求确认前查询并展示当前 VM 状态。 - **Query audit trail for vmware-monitor / vmware-monitor 查询审计**: The read-only monitoring skill also supports audit logging for compliance — all queries can be recorded with operation type "query". 只读监控技能也支持审计日志记录,用于合规要求——所有查询操作可记录为 "query" 类型。 ### Files Added / 新增文件 - `vmware_aiops/notify/audit.py` — AuditLogger class (JSONL format, append-only) ### Files Updated / 更新文件 - `vmware_aiops/cli.py` — Added state preview, audit logging for all VM operations - `plugins/vmware-ops/skills/vmware-aiops/SKILL.md` — Added "Execution Workflow" section - `plugins/vmware-ops/skills/vmware-monitor/SKILL.md` — Added "Query Audit Trail" section - `skill/SKILL.md` — Synced Execution Workflow - `SKILL.md` (root) — Added Audit Trail to Safety Features table - `skills/vmware-aiops/SKILL.md` — Synced Safety Features - `vmware-aiops/SKILL.md` — Synced Safety Features - `codex-skill/AGENTS.md` — Added Execution Workflow - `.agents/skills/vmware-aiops/AGENTS.md` — Added Execution Workflow - `.agents/skills/vmware-monitor/AGENTS.md` — Added Query Audit Trail - `README.md` — Added Audit Trail to Safety Features table - `README-CN.md` — Same updates in Chinese - `RELEASE_NOTES.md` — Added v0.5.1 release notes ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.5.0 — 2026-02-28 ### New Features / 新功能 - **vmware-monitor skill (read-only) / vmware-monitor 只读监控技能**: Added a new read-only monitoring skill `vmware-monitor` that provides all query and monitoring capabilities without any destructive operations. Safe for daily monitoring — no risk of accidental VM power-off, deletion, or reconfiguration. 新增只读监控技能 `vmware-monitor`,提供所有查询和监控功能,不包含任何修改操作。日常巡检使用更安全——不会误操作关机、删除或修改 VM。 - **Two-skill architecture / 双技能架构**: The plugin now offers two independent skills: 插件现在提供两个独立技能: - `vmware-monitor` — Read-only: inventory, health, alarms, events, VM info, snapshot list, vSAN monitoring, Aria Operations metrics, VKS status, scanning / 只读:资源清单、健康检查、告警、事件、VM 信息、快照列表、vSAN 监控、Aria Operations 指标、VKS 状态、日志扫描 - `vmware-aiops` — Full operations: everything in monitor + power, create, delete, reconfigure, snapshot CRUD, clone, migrate, VKS scaling / 完整运维:监控全部功能 + 开关机、创建/删除、修改配置、快照增删恢复、克隆、迁移、VKS 扩缩容 - **Safety redirect / 安全引导**: When users request destructive operations in vmware-monitor, the skill guides them to switch to vmware-aiops instead of silently failing. 当用户在 vmware-monitor 中请求修改操作时,技能会引导切换到 vmware-aiops,而非静默失败。 - **GitHub community files / GitHub 社区文件**: Added SECURITY.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, LICENSE, issue templates (bug report, feature request), PR template, and Dependabot configuration. 新增安全策略、贡献指南、行为准则、MIT 许可证、Issue 模板、PR 模板、Dependabot 配置。 ### How to Switch Between Skills / 如何切换技能 ```bash # Read-only monitoring (safe) / 只读监控(安全) /vmware-ops:vmware-monitor # Full operations / 完整运维 /vmware-ops:vmware-aiops ``` ### Files Added / 新增文件 - `plugins/vmware-ops/skills/vmware-monitor/SKILL.md` — Read-only monitoring skill - `skills/vmware-monitor/SKILL.md` — Skills.sh index for vmware-monitor - `vmware-monitor/SKILL.md` — Alternative index for vmware-monitor - `.agents/skills/vmware-monitor/SKILL.md` — Agent skill header - `.agents/skills/vmware-monitor/AGENTS.md` — Agent instructions (read-only) - `SECURITY.md` — Security policy and vulnerability reporting - `CONTRIBUTING.md` — Contribution guidelines - `CODE_OF_CONDUCT.md` — Contributor Covenant v2.0 - `LICENSE` — MIT License - `.github/ISSUE_TEMPLATE/bug_report.yml` — Bug report template - `.github/ISSUE_TEMPLATE/feature_request.yml` — Feature request template - `.github/ISSUE_TEMPLATE/config.yml` — Issue template config - `.github/PULL_REQUEST_TEMPLATE.md` — PR template - `.github/dependabot.yml` — Dependabot configuration ### Files Updated / 更新文件 - `README.md` — Added two-skill comparison table, updated install instructions and project structure - `README-CN.md` — Same updates in Chinese - `RELEASE_NOTES.md` — Added v0.5.0 release notes - `.claude-plugin/marketplace.json` — Updated description to mention both skills, version 0.5.0 - `plugins/vmware-ops/.claude-plugin/plugin.json` — Updated description, version 0.5.0 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.4.1 — 2026-02-26 ### Improvements / 改进 - **Secure credential management / 安全凭据管理**: Added `.env.example` template with naming convention (`VMWARE_{TARGET_NAME}_PASSWORD`) and `chmod 600` instructions. Users can now `cp .env.example ~/.vmware-aiops/.env` for quick setup. 新增 `.env.example` 凭据模板,包含命名规则和 `chmod 600` 说明,用户可快速复制使用。 - **First-run configuration guide / 首次配置引导**: SKILL.md now includes a 3-step setup guide (check config.yaml → check .env → verify connection) for new users. SKILL.md 新增 3 步配置引导流程,帮助新用户快速上手。 - **Credential security rules / 凭据安全规则**: Added explicit NEVER/ALWAYS rules to SKILL.md — never hardcode passwords, never display passwords in output, always use `ConnectionManager.from_config()`. SKILL.md 新增明确的安全规则——禁止硬编码密码、禁止在输出中显示密码、始终使用 `ConnectionManager.from_config()`。 - **Output sanitization / 输出脱敏**: Connection info displays only host, username, and type — passwords are never shown in any output or logs. 连接信息仅显示主机、用户名和类型,密码永远不会出现在任何输出或日志中。 - **Security best practices in README / README 安全最佳实践**: Added security best practices section to both English and Chinese READMEs. 中英文 README 均新增安全最佳实践章节。 ### Files Added / 新增文件 - `.env.example` — Credential template with naming convention and security instructions ### Files Updated / 更新文件 - `config.example.yaml` — Added `.env` setup guidance comments - `skill/SKILL.md` — Rewritten with first-run guide, credential security rules, output sanitization - `plugins/vmware-ops/skills/vmware-aiops/SKILL.md` — Synced with `skill/SKILL.md` - `README.md` — Updated password setup to use `.env.example`, added security best practices - `README-CN.md` — Same updates in Chinese ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.4.0 — 2026-02-26 ### New Features / 新功能 - **vSAN Management / vSAN 管理**: Added vSAN health check, capacity monitoring, disk group listing, and performance metrics via pyVmomi 8u3+ integrated vSAN SDK. 新增 vSAN 健康检查、容量监控、磁盘组列表、性能指标(通过 pyVmomi 8u3+ 内置 vSAN SDK)。 - **Aria Operations / VCF Operations 集成**: Added REST API integration for `/suite-api/` — historical metrics, ML anomaly detection, capacity planning, right-sizing recommendations, intelligent alerts with root cause analysis. 新增 Aria Operations REST API 集成——历史指标、ML 异常检测、容量规划、右规格建议、根因分析智能告警。 - **vSphere Kubernetes Service (VKS) / Kubernetes 服务**: Added Tanzu Kubernetes cluster management — list clusters, health checks (InfrastructureReady/ControlPlaneAvailable/WorkersAvailable), scale workers, node status. 新增 Tanzu Kubernetes 集群管理——列出集群、健康检查、扩缩容、节点状态。 ### New CLI Commands / 新增命令 ```bash # vSAN vmware-aiops vsan health|capacity|disks|performance [--target ] # Aria Operations / VCF Operations vmware-aiops ops alerts|metrics|recommendations|capacity [--target ] # VKS vmware-aiops vks clusters|health|scale|nodes ``` - **MCP Server / MCP 服务器**: Added `mcp_server/` package wrapping VMware operations as MCP tools (list VMs/hosts/datastores/clusters, alarms, events, VM power on/off, VM info). Enables registration on Smithery, Glama, and MCP Server Registry. 新增 MCP 服务器,将 VMware 操作封装为 MCP 工具,支持注册到 Smithery、Glama 和 MCP Server Registry。 - **Smithery Integration / Smithery 集成**: Added `smithery.yaml` for one-click install via `npx @smithery/cli install`. 新增 Smithery 配置文件,支持一键安装。 - **Marketplace Publishing / 市场发布**: Prepared for PyPI (`pip install vmware-aiops`), SkillsMP (skills.sh), Smithery, Glama, and MCP Server Registry. 准备发布到 PyPI、SkillsMP、Smithery、Glama 和 MCP Server Registry。 ### Files Updated / 更新文件 - All skill files updated with vSAN, Aria Operations, and VKS sections: `skill/SKILL.md`, `codex-skill/AGENTS.md`, `gemini-extension/GEMINI.md`, `trae-rules/project_rules.md`, `kimi-skill/SKILL.md`, `plugins/vmware-ops/skills/vmware-aiops/SKILL.md` - `README.md` — Added capabilities sections 6-8 (vSAN, Aria Ops, VKS) and CLI commands - `README-CN.md` — Same updates in Chinese - `plugins/vmware-ops/.claude-plugin/plugin.json` — Version 0.3.0 → 0.4.0 - `.claude-plugin/marketplace.json` — Version 0.2.0 → 0.4.0 - `pyproject.toml` — Version 0.1.0 → 0.4.0, added `mcp[cli]` dependency and `vmware-aiops-mcp` entry point - `README.md` / `README-CN.md` — Added MCP server section, updated platform table and project structure ### Files Added / 新增文件 - `mcp_server/__init__.py` - `mcp_server/server.py` — FastMCP server exposing 9 VMware tools - `mcp_server/__main__.py` — `python -m mcp_server` entry point - `smithery.yaml` — Smithery marketplace configuration ### API References / API 参考 - vSAN Management SDK: https://developer.broadcom.com/sdks/vsan-management-sdk-for-python/latest/ - Aria Operations API: https://developer.broadcom.com/xapis/vmware-aria-operations-api/latest/ - VKS API: https://developer.broadcom.com/xapis/vmware-vsphere-kubernetes-service/3.6.0/api-docs.html - VCF 9.0 API Spec: https://developer.broadcom.com/sdks/vcf-api-specification/latest/ ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.3.0 — 2026-02-26 ### New Features / 新功能 - **Trae IDE support / Trae IDE 支持**: Added `trae-rules/project_rules.md` for Trae IDE's Builder Mode. Copy to `.trae/rules/` to use with Claude, DeepSeek, GPT-4o, or Doubao models. 添加 Trae IDE 规则文件,复制到 `.trae/rules/` 即可使用 Claude、DeepSeek、GPT-4o 或豆包模型。 - **Kimi Code CLI support / Kimi Code CLI 支持**: Added `kimi-skill/SKILL.md` for Moonshot Kimi Code CLI. Copy to `~/.kimi/skills/vmware-aiops/`. 添加 Kimi Code CLI 技能文件,复制到 `~/.kimi/skills/vmware-aiops/`。 - **Version compatibility matrix / 版本兼容矩阵**: Documented support for vSphere 6.5, 6.7, 7.0, and 8.0 across all skill files and README. pyVmomi auto-negotiates API version during SOAP handshake. 记录了 vSphere 6.5–8.0 版本兼容性。pyVmomi 在 SOAP 握手阶段自动协商 API 版本。 - **Bilingual README / 中英文 README**: Split into `README.md` (English) and `README-CN.md` (Chinese) with language switcher. 拆分为英文 README.md 和中文 README-CN.md,带语言切换链接。 ### Changes / 变更 - Updated architecture diagram to include Trae IDE and Kimi Code CLI. 更新架构图,加入 Trae IDE 和 Kimi Code CLI。 - Added version-specific notes to all skill/rules files: - vSphere 8.0: `CreateSnapshot_Task` deprecated → use `CreateSnapshotEx_Task` - vSphere 8.0: `SmartConnectNoSSL()` removed → use `SmartConnect(disableSslCertValidation=True)` - vSphere 7.0: All standard APIs fully supported 为所有技能/规则文件添加版本特定说明。 - Plugin version bumped to 0.3.0. 插件版本升级到 0.3.0。 ### Files Added / 新增文件 - `trae-rules/project_rules.md` - `kimi-skill/SKILL.md` - `README-CN.md` - `RELEASE_NOTES.md` ### Files Updated / 更新文件 - `README.md` — English-only, added Trae/Kimi platforms, version compatibility, updated project structure - `skill/SKILL.md` — Added version compatibility section - `codex-skill/AGENTS.md` — Added version compatibility section - `gemini-extension/GEMINI.md` — Added version compatibility section - `plugins/vmware-ops/.claude-plugin/plugin.json` — Version 0.2.0 → 0.3.0 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.2.0 — 2026-02-25 ### New Features / 新功能 - **Claude Code Marketplace plugin / Claude Code 市场插件**: Added `.claude-plugin/marketplace.json` and `plugins/vmware-ops/` for one-click install via `/plugin marketplace add zw008/VMware-AIops`. 新增 Claude Code 市场插件,支持一键安装。 - **Gemini CLI extension / Gemini CLI 扩展**: Added `gemini-extension/` with `GEMINI.md` and `gemini-extension.json` for Google Gemini CLI integration. 新增 Gemini CLI 扩展。 - **Multi-platform support / 多平台支持**: Claude Code, Gemini CLI, OpenAI Codex CLI, Aider, Continue CLI all supported via shared Python backend. 支持 Claude Code、Gemini CLI、OpenAI Codex CLI、Aider、Continue CLI。 - **Chinese cloud models / 国内云端模型**: Documentation for DeepSeek, Qwen (Alibaba), and Doubao (ByteDance). 新增 DeepSeek、通义千问、豆包的配置文档。 - **Local models / 本地模型**: Aider + Ollama workflow for fully offline operation. 新增 Aider + Ollama 离线运行方案。 ### Core Features / 核心功能 - **Inventory**: List VMs, hosts, datastores, clusters, networks (vCenter + ESXi) 资源清单:虚拟机、主机、数据存储、集群、网络 - **Health monitoring**: Active alarms, event/log queries (50+ event types), hardware sensors, host services 健康监控:活跃告警、事件日志查询、硬件传感器、主机服务 - **VM lifecycle**: Power on/off/reset/suspend, create, delete, reconfigure (CPU/memory), snapshots (create/list/revert/delete), clone, vMotion migration VM 生命周期:开关机、创建、删除、调整配置、快照、克隆、迁移 - **Scheduled scanning**: APScheduler daemon, multi-target scan, regex log analysis, JSONL output, webhook notifications (Slack/Discord) 定时扫描:APScheduler 守护进程、多目标扫描、正则日志分析、JSONL 输出、Webhook 通知 - **Safety**: Double confirmation for destructive ops, `.env` password protection, SSL self-signed cert support, async task waiting 安全特性:双重确认、密码保护、自签名证书支持、异步任务等待 ## v1.4.6 — 2026-04-06 - fix: remove suspicious content from SKILL.md for ClawHub clean scan --- ## v0.1.0 — 2026-02-24 ### Initial Release / 初始发布 - Core Python backend (`vmware_aiops/`) with pyVmomi SOAP API integration. 核心 Python 后端,集成 pyVmomi SOAP API。 - CLI tool (`vmware-aiops`) with Typer framework. 基于 Typer 框架的 CLI 工具。 - Claude Code skill file (`skill/SKILL.md`). Claude Code 技能文件。 - OpenAI Codex CLI / Aider / Continue shared instructions (`codex-skill/AGENTS.md`). OpenAI Codex CLI / Aider / Continue 共用指令文件。 - Multi-target configuration via `~/.vmware-aiops/config.yaml`. 多目标配置。 - Environment variable password management. 环境变量密码管理。