diff --git a/.claude/skills/bitdefender/references/BUILDOUT.md b/.claude/skills/bitdefender/references/BUILDOUT.md index c03d9c04..0b78f4c0 100644 --- a/.claude/skills/bitdefender/references/BUILDOUT.md +++ b/.claude/skills/bitdefender/references/BUILDOUT.md @@ -30,15 +30,16 @@ Per-module workflow: fetch module doc -> list methods + params -> add to - [x] activateCompany (gated) - [x] deleteCompany (gated) -## network +## network — COMPLETE (all exposed methods) - [x] getNetworkInventoryItems · getEndpointsList · getManagedEndpointDetails - [x] getScanTasksList · createScanTask · moveEndpoints - [x] createCustomGroup · deleteCustomGroup · moveCustomGroup · deleteEndpoint - [x] assignPolicy -- [ ] createReconfigureClientTask (gated) -- [ ] createUninstallTask (gated) -- [ ] setEndpointLabel -- [ ] (enumerate remaining: getEndpointsCounts, getManagedEndpointDetails opts, etc.) +- [x] createReconfigureClientTask (gated) +- [x] setEndpointLabel (gated) +- [x] getEndpointTags (read) +- [DEAD] createUninstallTask (+variants) — does NOT exist under /network this version +- [DEAD] getEndpointsByPolicy / getManagedEndpointDetailsByIp / createScanTaskByMailboxes — not found ## packages - [x] getPackagesList · createPackage · getInstallationLinks · deletePackage diff --git a/.claude/skills/bitdefender/references/api-reference.md b/.claude/skills/bitdefender/references/api-reference.md index e9a60e19..d9d8021f 100644 --- a/.claude/skills/bitdefender/references/api-reference.md +++ b/.claude/skills/bitdefender/references/api-reference.md @@ -95,10 +95,12 @@ In `getNetworkInventoryItems` results, `type == 1` denotes a company node. | `deleteEndpoint` | `endpointId` | VERIFIED (destructive) | Remove an endpoint from inventory. CLI-gated behind `--confirm`. | | `deleteCustomGroup` | `groupId` | VERIFIED (destructive) | Delete a custom group. CLI-gated behind `--confirm`. | | `moveCustomGroup` | `groupId, newParentId` | VERIFIED | Re-parent a custom group. | -| `assignPolicy` | uncertain | UNVERIFIED | Assigns an EXISTING policy to endpoints. Param names not confirmed (likely `policyId` + a targets list). Do NOT use blindly — confirm against archived docs first. `raw` only. | -| `createReconfigureClientTask` | uncertain | UNVERIFIED | Param shape not confirmed. `raw` only. | -| `createUninstallTask` | uncertain | UNVERIFIED | Destructive; param shape not confirmed. `raw` only. | -| `setEndpointLabel` | uncertain | UNVERIFIED | Param shape not confirmed. `raw` only. | +| `assignPolicy` | `policyId, targetIds[], forcePolicyInheritance?, inheritFromAbove?` | VERIFIED (docs+probe) | CLI `assign-policy`, gated. See policies section. | +| `createReconfigureClientTask` | `targetIds[] (req) + reconfigure body` | VERIFIED (probe) | CLI `reconfigure`, gated. STATE-CHANGING. | +| `setEndpointLabel` | `endpointId (req), label (req)` | VERIFIED (probe) | CLI `set-label`, gated. STATE-CHANGING. | +| `getEndpointTags` | `{}` | VERIFIED LIVE | List endpoint tags (returns a list). CLI `endpoint-tags`. | +| `createUninstallTask` + variants | — | DOES NOT EXIST | No uninstall-task method under `/network` in this API version (createUninstallTask / createUninstallClientTask / createUninstallRoleTask / uninstallClientTask all "method not found"). Uninstall via the console. | +| `getEndpointsByPolicy`, `getManagedEndpointDetailsByIp`, `createScanTaskByMailboxes` | — | DOES NOT EXIST | "method not found" on this tenant/version. | ## packages (`/packages`) diff --git a/.claude/skills/bitdefender/scripts/gz.py b/.claude/skills/bitdefender/scripts/gz.py index e679d923..fe425f06 100644 --- a/.claude/skills/bitdefender/scripts/gz.py +++ b/.claude/skills/bitdefender/scripts/gz.py @@ -510,6 +510,38 @@ def cmd_move(client, args): args.json, _print_kv) +def _print_tags(tags) -> None: + items = tags if isinstance(tags, list) else (tags or {}).get("items", []) + print(f"Endpoint tags: {len(items)}") + for t in items: + print(f" {t}") + + +def cmd_endpoint_tags(client, args): + _emit(client.get_endpoint_tags(), args.json, _print_tags) + + +def cmd_set_label(client, args): + if not _gated(f"label endpoint {args.endpoint} = '{args.label}'", args.confirm): + return 3 + result = client.set_endpoint_label(args.endpoint, args.label) + _emit({"labeled": args.endpoint, "label": args.label, "result": result}, + args.json, _print_kv) + return 0 + + +def cmd_reconfigure(client, args): + extra, rc = _load_json_arg(args.extra_json, "extra-json") + if rc: + return rc + if not _gated(f"reconfigure {len(args.targets)} agent(s): {','.join(args.targets)}", + args.confirm): + return 3 + result = client.reconfigure_client(args.targets, extra=extra or None) + _emit({"reconfigured": args.targets, "result": result}, args.json, _print_kv) + return 0 + + def cmd_make_group(client, args): result = client.create_custom_group(args.name, args.parent) _emit({"createdGroup": args.name, "result": result}, args.json, _print_kv) @@ -524,7 +556,7 @@ DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove", "removefromblocklist", "assignpolicy", "setpushevent", "createaccount", "updateaccount", "configurenotif", "createcompany", "suspendcompany", - "activatecompany") + "activatecompany", "setendpointlabel") def _is_destructive_method(method: str) -> bool: @@ -770,6 +802,21 @@ def build_parser() -> argparse.ArgumentParser: sp.add_argument("--name", required=True) sp.add_argument("--parent") + sub.add_parser("endpoint-tags", help="List endpoint tags.", parents=[common]) + + sp = sub.add_parser("set-label", help="Set an endpoint's label (gated).", + parents=[common]) + sp.add_argument("--endpoint", required=True, help="endpointId.") + sp.add_argument("--label", required=True) + sp.add_argument("--confirm", action="store_true") + + sp = sub.add_parser("reconfigure", + help="Reconfigure installed agents (gated).", parents=[common]) + sp.add_argument("--targets", nargs="+", required=True, help="Endpoint ids.") + sp.add_argument("--extra-json", + help="JSON reconfigure body (modules/roles/scanMode).") + sp.add_argument("--confirm", action="store_true") + sp = sub.add_parser("raw", help="Call any method directly (power use).", parents=[common]) sp.add_argument("--module", required=True) @@ -928,6 +975,9 @@ HANDLERS = { "scan": cmd_scan, "move": cmd_move, "make-group": cmd_make_group, + "endpoint-tags": cmd_endpoint_tags, + "set-label": cmd_set_label, + "reconfigure": cmd_reconfigure, "raw": cmd_raw, "delete-endpoint": cmd_delete_endpoint, "delete-package": cmd_delete_package, diff --git a/.claude/skills/bitdefender/scripts/gz_client.py b/.claude/skills/bitdefender/scripts/gz_client.py index 60b80396..42459824 100644 --- a/.claude/skills/bitdefender/scripts/gz_client.py +++ b/.claude/skills/bitdefender/scripts/gz_client.py @@ -721,6 +721,33 @@ class GravityZoneClient: params["status"] = status return self._jsonrpc_request("network", "getScanTasksList", params) or {} + def get_endpoint_tags(self) -> Any: + """List endpoint tags defined in the tenant (network.getEndpointTags). Read.""" + return self._jsonrpc_request("network", "getEndpointTags", {}) + + def set_endpoint_label(self, endpoint_id: str, label: str) -> Any: + """Set an endpoint's label (network.setEndpointLabel). STATE-CHANGING. + Requires `endpointId` and `label` (both verified). Gate at the call site.""" + return self._jsonrpc_request( + "network", "setEndpointLabel", + {"endpointId": endpoint_id, "label": label}, + ) + + def reconfigure_client( + self, target_ids: list[str], extra: Optional[dict] = None + ) -> Any: + """Reconfigure installed agents (network.createReconfigureClientTask). + STATE-CHANGING. Requires `targetIds` (verified); the reconfigure body + (which modules/roles/scanMode to change) is documented and passed via + `extra`. Gate at the call site behind --confirm. + """ + params: dict = {"targetIds": target_ids} + if extra: + params.update(extra) + return self._jsonrpc_request( + "network", "createReconfigureClientTask", params + ) + # ====================================================================== # REPORTS (module `/reports`) — VERIFIED LIVE # ====================================================================== diff --git a/.claude/skills/bitdefender/scripts/selftest.py b/.claude/skills/bitdefender/scripts/selftest.py index 30e974d2..0432aa25 100644 --- a/.claude/skills/bitdefender/scripts/selftest.py +++ b/.claude/skills/bitdefender/scripts/selftest.py @@ -105,6 +105,13 @@ check("push-set no confirm -> rc3", ["push-set", "--status", "1", "--url", "http check("push-set enable no url -> rc2", ["push-set", "--status", "1", "--confirm"], want_rc=2) check("raw assignPolicy no confirm -> rc3", ["raw", "--module", "network", "--method", "assignPolicy", "--params", "{}"], want_rc=3) +# --- network completion --- +check("endpoint-tags", ["endpoint-tags"], want_rc=0) +check("set-label no confirm -> rc3", ["set-label", "--endpoint", "x", "--label", "y"], want_rc=3) +check("reconfigure no confirm -> rc3", ["reconfigure", "--targets", "x"], want_rc=3) +check("raw reconfigure no confirm -> rc3", ["raw", "--module", "network", "--method", "createReconfigureClientTask", "--params", "{}"], want_rc=3) +check("raw setEndpointLabel no confirm -> rc3", ["raw", "--module", "network", "--method", "setEndpointLabel", "--params", "{}"], want_rc=3) + # --- companies module --- check("company (own, no id)", ["company"], want_rc=0) check("company-create no confirm -> rc3", ["company-create", "--type", "1", "--name", "Test Co"], want_rc=3, out_has="Would")