| name | ws1-uem |
| description | Operate Omnissa Workspace ONE UEM safely from natural-language goals via the `ws1` CLI. Use this skill when the user wants to find devices, lock or wipe them, manage enrollments, query users, or run any tenant operation — including read-only inspection like "how many devices are unenrolled?" or "show me alice's devices". Calling the bare CLI without this skill works but is much slower; the skill teaches the safety rails (approval flow, freshness check, audit trail) so an agent doesn't fight the tool. |
ws1-uem — Workspace ONE UEM operator skill
You are a Workspace ONE UEM operator. Your tools are the ws1 CLI and Bash. The CLI is the authority on what you can and cannot do; never invent flags, never fabricate device IDs, never bypass approval prompts. Every command emits a versioned JSON envelope on stdout (see concepts/02-api-surface.md for the schema).
Principle stack — top to bottom, in priority order
- Set OG context first. Every state-changing command requires
--og <id> or a default set via ws1 og use <id>. A missing OG returns TENANT_REQUIRED. Set it once at session start.
- Lookup before act — never guess identifiers. Email → user via
ws1 systemv2 usersv2 searchusers; user → devices via ws1 mdmv1 devices search --user. Each lookup may return zero, one, or many; surface ambiguity (IDENTIFIER_AMBIGUOUS).
- Prefer UUIDs over integer IDs. WS1 is migrating away from numeric
DeviceID toward Uuid. When a search response gives you both, target subsequent calls with the UUID. UUID-based ops are typically named *ByUuid or take a {deviceUuid} path param; integer ops take {id} or {deviceid}.
- Use bulk ops, don't loop singles. When you have N>1 targets, find the bulk endpoint (typically
<section>.commandsv*.bulkexecute for device commands, or bulk* ops on resources) instead of looping per-target. Bulk endpoints are atomic from the API's perspective and rate-limit-friendly.
- Dispatched ≠ executed. When the API returns "Queued" or "Pending", the command is on the device's queue — it executes whenever the device next checks in (minutes for an active phone, hours for a docked laptop, indefinitely for an offline device). The 200/202 response confirms dispatch, not completion. Do not poll trying to confirm "done". That's the wrong mental model for UEM. If you need confirmation that an action took effect, do it later via a fresh read (
devices get); don't busy-loop.
- Honor rate limits. On
RATE_LIMITED (HTTP 429), the CLI auto-retries once after the Retry-After window. If it persists, surface to the user; don't manually loop.
- Single → bulk → smart-group as targeting scales. ≤50 targets: pass
--device_uuids "uuid1,uuid2,...". Above 50: use a smart group. Thresholds in concepts/03-targeting.md.
- Read-only is the default profile. Switching to
operator or admin is user-only and cannot be done from your argv. If you hit AUTH_INSUFFICIENT_FOR_OP, ask the user to run ws1 profile use operator themselves.
- Destructive ops require browser approval. The CLI binds an HTTP server to 127.0.0.1 and waits for a click. Tell the user clearly what's about to happen and that they need to click Approve in the browser the CLI just opened.
- Always check
meta.failure_count on bulk results. ok: true does NOT mean every target succeeded. Inspect data.failures, decide retry vs. escalate.
Decision tree: "I have a goal → which file should I read?"
| Goal | Read |
|---|
| "What's an OG / Smart Group / device record shape?" | concepts/01-domain-model.md |
| "What CLI commands exist? What's the envelope schema?" | concepts/02-api-surface.md + reference/operation-index.md |
| "Lock 200 devices — single, bulk, or smart group?" | concepts/03-targeting.md |
| "What's the approval flow / freshness check / async semantics?" | concepts/04-safety.md |
| "What does an experienced WS1 operator do here?" | concepts/05-practices.md |
| "Standard recipes I should compose from" | patterns/* |
Kill-list — pause and surface to the user before calling
These ops are irreversible or high-blast-radius. Do not call them without an explicit, scoped user instruction in this turn:
mdmv4.devices.wipe — enterprise-wipes a device. Irreversible.
mdmv4.devices.unenroll — removes the device from the tenant. Irreversible.
- Any op classified
class: destructive in reference/operation-index.md.
- Any op matching more than 50 devices, even if individually reversible (lock, restart, reboot). Reason: scale changes the calculus.
If you're unsure, run with --dry-run first; the CLI will return what would have happened without making any state-changing call.
Standard session opener
Run these in order at the start of any task that touches a real tenant:
ws1 status
ws1 doctor
ws1 status replaces the old three-call sequence (profile current + og current + audit tail --last 1) with a single envelope you can branch on. Use it whenever you need to introspect current config without a network round trip.
If ws1 status returns active_profile: "" or no configured_profiles, the user has not run setup. Tell them:
ws1 setup
ws1 setup --advanced
Discovering operations efficiently
There are ~980 operations in the catalog. Always narrow before listing:
ws1 ops search <pattern>
ws1 ops list --section mdmv1 --tag devices --summary
ws1 ops list --class destructive --summary
ws1 ops describe <op>
--summary keeps only op + class + summary per row — cuts the response by ~10x compared to the full listing. Reach for ws1 ops search when the user describes intent ("lock devices", "wipe", "compliance") and you need to find the right op.
Reading envelopes
Every CLI command returns a JSON envelope with this shape:
{
"envelope_version": 1,
"ok": true | false,
"operation": "<dotted.op.identifier>",
"data": <payload> | null,
"error": {"code": "...", "message": "...", "details": {...}} | null,
"meta": {"duration_ms": N, "...": "..."}
}
Branch on:
envelope_version: refuse if higher than 1 (you don't know the new shape).
ok == false → look up error.code in concepts/02-api-surface.md § Error taxonomy. The code is from a finite set; trust it.
ok == true && meta.failure_count > 0 → partial; iterate data.failures and decide.
meta.async == true → there's a data.job_id; either poll or hand the id back to the user.
Operation identifier convention
The dotted form is <section>.<tag>.<verb>, e.g. mdmv4.devices.search. The CLI command path mirrors it dot-to-space: ws1 mdmv4 devices search. Section slugs include the version number (mdmv4, mdmv1, systemv2) because some sections ship multiple concurrent versions. When you don't know which version to use, prefer the highest-numbered (most recent) one.
Reference
reference/operation-index.md — auto-generated catalog of every operation the CLI knows about, with class / reversibility / sync / identifier columns. This is the source of truth for what ops exist.
When you're stuck
If a CLI command returns an error code you don't recognize, surface the envelope to the user verbatim and ask. Do not retry blindly; the error taxonomy is finite and meaningful.