Skip to content
Back to skills

It Cli

ASecurity

Use the `its` CLI for THF IT operations: servers, endpoint/RMM agents, Entra ID, Intune, Microsoft 365, Outlook, SharePoint, Dokploy, Bitwarden, UniFi, Wrike, Azure, Cloudflare, Power BI, Power Platform, PeopleHR, factory attendance and Business Central. Use for device health, users, groups, licences, passwords, deployments, network, tickets, mailboxes, security audits, onboarding/offboarding, clocking, headcount, timesheets and absence. Prefer `its` over vendor CLIs or duplicate connectors.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 5, 2026
devopsrustgoshellbashreactazuregitapidatabasesecurity

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add sling86/it-cli-plugin --skill it-cli --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of It Cli?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for It Cli
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sling86-it-cli/badge)](https://www.skillsdirectory.com/skills/sling86-it-cli)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: it-cli
description: "Use the `its` CLI for THF IT operations: servers, endpoint/RMM agents, Entra ID, Intune, Microsoft 365, Outlook, SharePoint, Dokploy, Bitwarden, UniFi, Wrike, Azure, Cloudflare, Power BI, Power Platform, PeopleHR, factory attendance and Business Central. Use for device health, users, groups, licences, passwords, deployments, network, tickets, mailboxes, security audits, onboarding/offboarding, clocking, headcount, timesheets and absence. Prefer `its` over vendor CLIs or duplicate connectors."
---

# IT CLI (`its`)

`its` is a unified CLI over the SaaS and self-hosted tools IT admins run day to day. One command shape, one set of output modes, one set of flags — across every provider. Reach for it first whenever the user asks about infrastructure, identity, endpoints, deployments, passwords, network, tickets, files, or cloud resources.

Binary: `its` (globally installed). Install/update from <https://github.com/sling86/its-releases>.

## Invocation

```
its <provider> <resource> [action] [positional_args...] [--flags]
```

- Default action is `list` when omitted.
- Every command supports `--ai` for machine-readable JSON.
- Help at every level: `its --help` · `its <provider>` · `its <provider> <resource> help`.

## Always use `--ai` when processing output

When you intend to parse or reason about the result, **always** pass `--ai` — output becomes minified single-line JSON. Only omit it when the user explicitly wants the human-formatted table.

## Seeing `***REDACTED***`? Keep the secret out of machine output

Secrets are masked by default — passwords, keys, tokens, TOTP seeds and Bitwarden hidden fields. `--include-secrets` / `--unsafe` is accepted only for interactive human output; JSON/AI modes and redirected stdout reject it. An AI shell therefore cannot retrieve a raw secret through captured command output.

When a human needs a Bitwarden value, ask them to run the clipboard command in their own terminal:

```bash
its bw password "server-login" --copy # clipboard only; auto-clears
```

For providers without a secure destination command, the human may use `--include-secrets` in an interactive terminal. Every use is audit-logged. Never paste the revealed value into chat, a ticket, or an AI transcript.

## Global commands (cross-provider)

```bash
its setup                       # Configuration overview — which providers are set up + the exact setup command for the rest
its status                      # Health check — every provider, configured/reachable (--test probes connectivity)
its config                      # Show configuration — env vars (masked), secrets, sessions
its config set KEY=value        # Set config without the wizard (scripts/agents); secrets: `its config set KEY --from-stdin` → keychain
its digest                      # Morning snapshot — counts + warnings across providers
its find <query>                # Cross-provider text search (users, devices, tickets, files)
its health <hostname|username>  # Cross-provider device/user health
its audit <user|UPN>            # Security audit — MFA, sign-ins, risk, endpoint, compliance
its inventory [--unifi]         # Device gap matrix across endpoint sources
its onboard preview <email>     # Starter readiness check (read-only)
its plan "<request>" --ai       # TypeSafe-backed command plan; never executes on the first call
its plan --execute <id> --confirm --ai  # Apply an inspected short-lived mutation plan + fresh-read verify
its user <upn>                  # Single-user snapshot — Entra + groups + licences + devices + tickets
its resume [--project <p>]      # Open backlog / resume-prompt ctxc memories, grouped by project
its log <text>                  # Append a timestamped entry to today's vault daily note
its auth login                  # OAuth sign-in (browser PKCE; --device-code for headless)
its auth status                 # Inspect delegated identity + token cache
its auth doctor                 # Full diagnostic — env + delegated + per-resource swap
its auth scopes [provider]      # Required Delegated + Application Graph scopes per provider
its secrets                     # Keychain entries (names only, never values); migrate / clear
its trust-cert <url>            # Trust + remember a self-signed TLS cert (TOFU); list / remove <host>
its watch <provider> <resource> # Re-run on an interval with change highlighting
its diff <provider> <resource> <snapshot.json>   # Diff live vs saved JSON
its export [--data]             # Export config (+ optionally live data)
its info                        # Diagnostic snapshot — version, paths, update state, keychain
its tui                         # Interactive browser over every command (needs a TTY)
its update                      # Check for a newer release
```

Full detail for all of these — usage, flags, worked examples — is in
[reference/global.md](reference/global.md), generated from the same source as
the commands themselves.

## Global output flags (work on every command)

| Flag                | Purpose                                                                 |
| ------------------- | ---------------------------------------------------------------------- |
| `--ai`              | Minified JSON output (use this when parsing)                            |
| `--ai-flat`         | Minified JSON without the columnar envelope                            |
| `--json`            | Pretty-printed JSON                                                     |
| `--jsonl`           | NDJSON — one compact JSON record per line                              |
| `--csv` / `--tsv`   | Delimited output                                                        |
| `--sort <column>`   | Sort by column (case-insensitive, partial names OK)                     |
| `--order asc\|desc` | Sort direction (default asc)                                            |
| `--filter col=val`  | Filter rows — case-insensitive substring, comma for OR                  |
| `--fields a,b,c`    | Select columns — works in all output modes, partial names OK           |
| `--limit N`         | Cap output to N rows (server-side where supported)                      |
| `--count`           | Return only the row count                                              |
| `--no-cache`        | Bypass cache (force fresh data)                                         |
| `--stdin`           | Fan out over JSON, NDJSON, scalar, or columnar `--ai` input            |
| `--map arg=.path`   | Explicitly bind an input field to a positional argument; repeatable    |
| `--on-error <mode>` | `stop` (default) or `continue` after a per-record failure              |
| `--max-input N`     | Bound fan-out; required explicitly for irreversible commands           |
| `--dry-run`         | Preview mutations — print method + URL + body, skip send               |
| `--include-secrets` | Human TTY plaintext only; rejected in machine/captured output          |
| `--auth <mode>`     | OAuth mode — `auto`, `delegated`, `app`, or `az`                       |
| `--profile <name>`  | Force a delegated identity for one call                                |

### How `--filter` works

- **Syntax:** `--filter column=value`
- **Column:** case-insensitive, partial names work (`company` matches `companyName`).
- **Value:** case-insensitive **substring** match.
- **Multiple values:** comma-separated = OR (`--filter status=online,overdue`).

### How `--fields` works

- **Syntax:** `--fields col1,col2,col3`
- Case-insensitive, partial names work; filters table columns AND JSON object keys.
- Example: `its exo domains --fields domain,type --json` returns only those two keys.

## Providers

For a command you can already name, prefer **live help** — `its <provider> <resource> help` (always current, near-zero cost). Read the bundled reference (`reference/<alias>.md`, start at [`reference/index.md`](./reference/index.md)) to **discover** what a provider offers — but the big providers' files are large, so switch to live help once you know the command.

| Provider | Alias | What it covers | Reference |
| -------- | ----- | -------------- | --------- |
| Tactical RMM | `rmm` | Endpoint agents — status, processes, services, updates, software, remote run, live terminal | [rmm](./reference/rmm.md) |
| Entra ID | `entra` | Users, groups, licences, roles, MFA, sign-ins, CA policies, on/offboarding | [entra](./reference/entra.md) |
| Intune | `intune` | Managed devices, apps, scripts, remediations, compliance, ESP, Autopilot | [intune](./reference/intune.md) |
| Exchange Online | `exo` | Distribution groups, mailboxes, permissions, forwarding, rules, message trace | [exo](./reference/exo.md) |
| SharePoint | `sp` | Sites, drives, files, lists, permissions, search | [sp](./reference/sp.md) |
| Outlook | `outlook` | Mail, calendar, contacts — signed-in user (delegated) or any mailbox (`--as <upn>` app-only) | [outlook](./reference/outlook.md) |
| Microsoft Teams | `teams` | Signed-in user's chats: read, send (Markdown, links, mentions, cards, images), edit, react, delete; presence (delegated-only) | [teams](./reference/teams.md) |
| Dokploy | `dokploy` | Apps, projects, databases, domains, env vars, deployments | [dokploy](./reference/dokploy.md) |
| Bitwarden | `bw` | Vault search, item/password/TOTP retrieval, password audits | [bw](./reference/bw.md) |
| UniFi Network | `unifi` | Devices, clients, WLANs, firewall, PoE, alarms, guests | [unifi](./reference/unifi.md) |
| UniFi Protect | `protect` | Cameras, NVR storage, motion events | [protect](./reference/protect.md) |
| Wrike | `wrike` | Tickets, tasks, projects, contacts | [wrike](./reference/wrike.md) |
| Azure | `az` | Subscriptions, VMs, resources, Key Vault, storage, costs | [az](./reference/az.md) |
| Cloudflare | `cf` | Zones, DNS, tunnels | [cf](./reference/cf.md) |
| Power BI | `pbi` | Workspaces, reports, datasets (tenant + delegated `my`) | [pbi](./reference/pbi.md) |
| Power Platform | `pa` | Environments, apps, flows | [pa](./reference/pa.md) |
| PeopleHR | `hr` | Employee directory, starters, leavers, timesheets, holiday, sickness, other leave, lateness, PeopleHR → Entra sync | [hr](./reference/hr.md) |
| Factory attendance | `attendance` | Read-only clocking terminals — punches, per-person summaries, exceptions, aggregate department headcount | [attendance](./reference/attendance.md) |
| Business Central | `bc` | Companies, query + write records (standard or custom API), environments, apps, users/permissions | [bc](./reference/bc.md) |
| GitHub | `gh` | Branch protection, webhooks (via local `gh`) | [gh](./reference/gh.md) |
| M365 Service Health | `m365` | Service health, incidents, message centre | [m365](./reference/m365.md) |
| Docs UI | `docs` | Browser-based command explorer | [docs](./reference/docs.md) |

**Microsoft providers share one Entra app registration.** Entra, Intune, SharePoint and Power BI reuse `TENANT_ID` / `CLIENT_ID` / `CLIENT_SECRET`. Set up once, several providers light up. Business Central prefers its own dedicated app (`BC_CLIENT_ID` / `BC_CLIENT_SECRET`) with its own BC user and permissions.

**Auth profiles — run some providers as one identity, others as another.** `its auth login --profile <name>` signs into a named slot (browser picker = pick a different account each time); `its auth use <profile> --default | --provider a,b,c` maps providers to it (stored in `~/.its/auth-map.json`). Resolution: `--profile` flag > per-provider map > default > the unnamed slot. Typical setup: an admin account as `default` for tenant tools (entra/intune/sp), your own user for `teams`/`outlook`. `its auth status` lists every profile + the map. No map set = single-identity behaviour, unchanged.

## Power queries — combine flags

```bash
# Top 5 offline agents, most recently seen first
its rmm agents --filter status=offline --sort last_seen --order desc --limit 5 --ai

# Count of online agents
its rmm agents --filter status=online --count

# All servers as CSV
its rmm agents --filter type=server --csv

# Distribution group names + emails only
its exo groups --fields displayName,primarySmtpAddress --ai
```

## Cross-command pipelines (`--stdin`)

The downstream command accepts a JSON array, one JSON object, NDJSON, a scalar, or the columnar envelope emitted by `--ai`. Each input record must bind at least one positional argument. Command-owned `pipeFrom` metadata is preferred; use repeatable `--map arg=.path` when the input shape differs. The temporary legacy identifier fallback warns when used.

```bash
# Command metadata maps agent_id to the required agent argument
its rmm agents --filter status=overdue --jsonl \
  | its rmm agents ping --stdin --jsonl

# Explicit mapping overrides command metadata when needed
its rmm agents --jsonl \
  | its rmm agents get --stdin --map agent=.agent_id --jsonl

# `-` marks the argument that comes from the piped record. Positionals you type
# fill from the left, so without it the date would land in the taskId slot.
its wrike tickets --filter status=active --jsonl \
  | its wrike tickets set-due - 2026-09-01 --stdin --max-input 50
```

Only the first positional falls back to guessing a field, so a `-` after it needs `--map` or command-owned `pipeFrom`. `-` is only special under `--stdin`.

Commands with no positional arguments reject record fan-out. Mutations validate every binding before the first write and run sequentially. Fan-out defaults to 100 records; irreversible commands require an explicit `--max-input`. Per-record failures go to stderr, successful records go to stdout, and any failure produces a non-zero exit code.

## Prefer `its` over the bare vendor CLIs

`its` wraps several official CLIs — prefer the wrapper:

| Bare CLI | Use instead | Why |
| -------- | ----------- | --- |
| `dokploy app list` / `app deploy` | `its dokploy apps` / `apps deploy` | Name resolution, unified output, caching |
| `bw list items` / `bw get` | `its bw items` / `its bw items get` | Speaks the vault REST API directly — no `bw` binary dependency, PIN-encrypted session, 2FA device-remember, TOTP, audits |
| `az vm list` / `az account` | `its az vm` / `its az account` | Same flags/filters/exports as every other provider |
| `az ad app permission grant --scope X` | `its entra consent add-scope <appId> "X"` | `az` **replaces** the whole scope list (destructive); `its consent` does a read-modify-write UNION |
| `az rest --url https://graph.microsoft.com/...` | `its entra graph get /...` (or `intune graph`, `sp graph`) | Uses the provider's Graph token with the correct scopes |
| `Get-Mailbox` / `Get-TransportRule` | `its exo mailboxes` / `its exo rules` | No manual Connect-ExchangeOnline; same output pipeline |

If `its` exposes a command for what you want, use it. Only reach for the bare CLI if `its` genuinely doesn't cover it.

## People and attendance data

Two providers, two very different privacy levels. Pick by who will see the answer.

**Aggregate — safe to show anyone.** `its attendance headcount` returns counts per department per day and never a name, employee ID or device user ID. Departments smaller than `--min-group` (default 5) fold into one combined row so no cell identifies a person.

```bash
its attendance headcount                                  # last 7 days
its attendance headcount --department packing --since -30d
its attendance headcount --department warehouse --since -2d --with-absence
```

Columns: `onSite` (distinct people at a reader), `typical` (median of complete days — today is excluded while it runs), `shopFloor` (staff in post that day who are expected at a reader), `onBooks` (contracted headcount). `--with-absence` adds `onHoliday`, `offSick`, `otherLeave`, `expectedIn` and `unaccounted` (due in, no punch, no logged reason). It is slow: PeopleHR only answers leave one person at a time, so keep `--since` short and add `--department`. A shortfall means *not seen at a reader*, never *absent without leave*.

**Person-level — HR only.** `its attendance events`, `summary` and `exceptions` carry names. So do `its hr timesheets|holidays|otherleave|absences|lates get <employee>`. Don't paste their output anywhere it will be read beyond HR.

**Which source for which day.** The terminals are live. PeopleHR timesheets arrive through a separate sync *after* the day, and can stall. So for today use `its attendance`, and for past days use `its hr timesheets get`. Only PeopleHR timesheets say whether a punch was IN or OUT — the terminal protocol records neither.

**The resources are plural.** `its hr timesheets get`, not `timesheet`. Same for `holidays`, `lates`, `absences`, `employees`.

### Keeping Entra in step with PeopleHR

PeopleHR is the source of truth; `its hr drift` makes Entra agree with it. `detect` is read-only. `apply` writes exactly what `detect` reports and nothing else.

```bash
its hr drift detect                                        # what disagrees, with an `apply` column
its hr drift apply --dry-run --confirm                     # every PATCH it would send — nothing sent
its hr drift apply --field officeLocation --confirm        # one field
its hr drift apply --user someone@example.com --confirm    # one person
```

Always run `detect` and read it before `apply`. Built-in holdbacks, reported with a `reason` and never written:

- a **placeholder** in PeopleHR (`TBC`, `Test`, `n/a` and similar) — HR hasn't decided yet, so it must not overwrite a real value
- **`displayName`** — reported only, never rewritten
- **`companyName`** with no `PHR_COMPANY_MAP` entry — the tool won't guess a legal entity name

A blank PeopleHR value never blanks an Entra field. A manager whose `ReportsToEmailAddress` matches no paired account is skipped rather than guessed. Anything specific to your organisation goes in the env file, not the code. `PHR_COMPANY_MAP` is a JSON object mapping each PeopleHR company to its Entra name. `PHR_SYNC_EXCLUDE` is a comma list of `upn` or `upn:field` to leave alone.

## Setting up the newer features

Each is optional; everything else works without it. `its <provider> setup --check` reports the line that matters ("usable now?").

- **Signatures on drafts** (`outlook drafts create|reply`, `mail draft` with `--signature` / `--sign-as <upn>`):
  1. `its config set SIGNATURE_API_URL=<signature service URL> SIGNATURE_API_BW_ITEM=<bitwarden item id>` — the item's **password** is the API key (`its bw search signature` finds it). Or keep the key itself in the keychain: `its config set SIGNATURE_API_KEY --from-stdin`. `its outlook setup` asks for the same values interactively.
  2. With the Bitwarden route the vault must be unlocked (`its bw session unlock`, human terminal, 8 h).
  3. `its outlook signature test` fetches it without making a draft (size only, never the key). Then add `--signature` to any draft command — it fails before creating anything if the signature can't be fetched, so an unsigned draft never appears.
- **Removing a user's sign-in method** (`its entra authmethods user <upn>` → `authmethods remove <upn> --method-id <id> --confirm`): nothing to set up. Signed-in admin tokens usually only read methods; on a 403 it retries once as the app (which holds `UserAuthenticationMethod.ReadWrite.All`) and reports `ranAs: "app"`. An explicit `--auth` is respected.
- **Azure role assignments** (`its az role-assignments list|create|delete`, `its az role-definitions list`): runs as the Azure CLI identity — `az login` once; `its auth doctor` shows the "az broker" line. Create/delete plan without `--confirm`.
- **Bitwarden passkeys**: `its bw items shape <item>` is the transcript-safe audit (yes/no and counts, passkey sites, personal vs org). `items passkeys` lists them; `items remove-passkey <item> --credential-id <id> --confirm` removes one. Passkeys can't be created from the CLI (needs a browser WebAuthn ceremony). Edits keep existing passkeys.
- **Business Central** runs app-only as a dedicated app. Besides the Entra app and its BC API roles, it must be listed in BC's *Microsoft Entra Applications* page **in every environment** (production and each sandbox — no API for this; `its bc users get <app-user> --env <env> --auth az` shows whether it took), with `D365 AUTOMATION` + `D365 READ`. `its bc environments` and `its bc apps` also need the app under the Admin Centre's *Authorized Microsoft Entra apps*. A `403 … _Exclude_APIV2_` means the app's BC user is missing a permission set; a `401 … rejected the client credentials` on one environment means it isn't registered there.
- **Teams messages** (`its teams chats send|edit-message|react`): URLs in the text become real links; `--markdown` converts headings, bold/italic/strike, code, links, lists, quotes and tables; plus `--importance`, `--card file.json` (Adaptive Card — links work, submit buttons don't) and `--image`. `edit-message` and `delete-message` only touch your own messages, and Teams keeps one reaction per person per message.
- **HR drift nicknames**: optional `PHR_NAME_ALIASES=mike:michael,…` (`its config set PHR_NAME_ALIASES=…`) or `--alias-map <file>`; a built-in English nickname list is always on.

## Safety rules

- **Destructive actions** (delete, remove, disable, block, reboot, purge) — confirm with the user before executing. Most accept `--confirm`; preview mutations with `--dry-run` first.
- **Business Central writes** (`bc record create|update|delete|call`, `bc environments copy|delete`, `bc apps install|uninstall|update`) need an explicit `--env <name>` (never the default) and `--confirm`; without `--confirm` they only preview. The app's BC user is read-only unless a write permission set was added — try writes in a sandbox first. `environments copy|delete` are sandbox-only.
- **Secret redaction is on by default.** Any key that looks like a credential (`password`, `clientSecret`, `privateKey`, `refresh_token`, `apiKey`, `token`, `secret`, …) and any value shaped like a PEM key or JWT is replaced with `***REDACTED***` in output. Bitwarden **hidden custom fields** are also masked in every mode, and **live TOTP codes** are masked whenever output isn't an interactive terminal (piped / `--ai` / captured) — so nothing secret lands in an AI transcript by default. `its secrets` only reports `SET`/`NOT SET`, never values.
- `--include-secrets` / `--unsafe` reveals plaintext only in interactive human output and is audit-logged to `~/.its/audit.log`; machine formats and redirected stdout reject it. Never paste revealed output into chat, tickets, or AI tools. Bitwarden `--copy` is the secure human hand-off, but it also requires an interactive terminal.
- To hand a secret to another command, use the file sink, not stdout: `its bw items get <id> --to-file <path>` / `its bw password <q> --to-file <path>` write a 0600 file and print only the byte count (no TTY needed), and `its dokploy env set <app> KEY --from-file <path>` / `--from-stdin` reads one back. Never `KEY=$SECRET` on the command line — argv is world-readable via `/proc/<pid>/cmdline` and lands in shell history.
- Some items keep their secret in a hidden custom field with an empty password (API keys especially). `its bw items get <id> --field "<name>" --to-file <path>` sends that field instead. A wrong name lists the available ones and writes no file.
- **Bitwarden needs an unlocked session before any of that works.** The vault is PIN-locked; the prompt needs a real terminal, so in an AI shell, cron or a pipe every `its bw` command fails until a human runs `its bw session unlock` in their own terminal (8 hours). The failure text suggests `its bw setup`, which cannot unlock anything — ignore it and ask the user to unlock. `its bw setup --check` reports the true state: `Configured but LOCKED` vs a missing configuration.
- Use `its <provider> setup --check` if unsure whether a provider is configured — the line under the checklist says whether it is usable *now*, which is the different and usually more useful question.

Files in this skill

  • SKILL.md10.9 KB
  • reference/az.md5.9 KB
  • reference/bc.md1.9 KB
  • reference/bw.md11.7 KB
  • reference/cf.md5.4 KB
  • reference/docs.md1.7 KB
  • reference/dokploy.md33.1 KB
  • reference/entra.md36.3 KB
  • reference/exo.md9.5 KB
  • reference/gh.md1.2 KB
  • reference/hr.md2.6 KB
  • reference/index.md1.1 KB
  • reference/intune.md12.6 KB
  • reference/outlook.md17.3 KB
  • reference/pa.md3.6 KB
  • reference/pbi.md6.6 KB
  • reference/protect.md1.7 KB
  • reference/rmm.md18.4 KB
  • reference/sp.md14.7 KB
  • reference/unifi.md10.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.