---
name: rmm-search
description: >
  Find machines/agents in the GuruRMM fleet cleanly and on the first try — locate an
  RMM agent by name, role, client, site, or OS before acting on it, instead of pulling
  /api/agents and grepping (which bleeds across clients). Forgiving multi-field search
  with a client filter so "hyperv valleywide" returns ONLY Valley Wide's host. Triggers:
  find the X machine, which agent is, look up <host> in RMM, <client>'s server/DC/hyperv,
  search RMM for, what's the agent id for. Hand the result to the `rmm` skill to run commands.
---

# rmm-search — clean machine lookup in GuruRMM

The `/rmm` skill resolves agents by grepping `/api/agents` client-side, which is
exactly where lookups go wrong: a bare term like `hyperv` matches every client's
hyperv box, and it's easy to act on the wrong one. This skill does a forgiving,
ranked, **client-aware** search instead. Use it as the front door for *finding*
an agent; use `/rmm` for *acting* on it.

## Usage

```bash
bash .claude/scripts/rmm-search.sh <words...> [-c|--client <name>] [--online] [--json] [-n N]
bash .claude/scripts/rmm-search.sh -c <client>          # list ALL machines for a client
bash .claude/scripts/rmm-search.sh --list-clients       # distinct client names
```

## How matching works (built for first-try correctness)

- **Normalized:** case, spaces, and hyphens are ignored — `valleywide`,
  `valley wide`, and `Valley Wide Plastering` all match the same client.
- **Multi-field:** every query word is matched against `hostname`, `client_name`,
  `site_name`, `os_type`, and `id`.
- **AND semantics:** *every* word must hit some field, so adding words **narrows**.
  `hyperv valleywide` → only the box that is both a hyperv host AND Valley Wide.
  This is why a query can't bleed across clients without `-c`.
- **Ranked:** exact > prefix > substring > subsequence (typo tolerance on
  hostnames, e.g. `hyprv`→`HYPERV`). Hostname matches outrank client/site/OS.
  Best result first; a whole-query hostname hit gets a boost.
- **`-c/--client`** is an explicit hard scope. If the client term is ambiguous
  (matches >1 client and none exactly), it **lists the candidates and stops**
  rather than guessing — narrow the value and retry.
- **`--online`** keeps only agents seen in the last 5 minutes. Online state is
  derived from `last_seen`, NOT the API `is_connected` flag (currently null
  fleet-wide — don't trust it).

## Output

A ranked table: `HOSTNAME | CLIENT | SITE | OS | STAT | ID` (best match first).
`--json` emits `{hostname,id,client,site,os,online,last_seen,score}` for piping
the `id` into a `/rmm` command. `-n N` caps the rows.

## Examples

```bash
rmm-search.sh hyperv valleywide          # VWP-HYPERV1 only (not DF-HYPERV-B)
rmm-search.sh dc -c dataforth            # Dataforth's domain controllers
rmm-search.sh vwp fil                    # VWP-FILES (partial words ok)
rmm-search.sh -c "peaceful spirit"       # every Peaceful Spirit machine
rmm-search.sh files valleywide --json | jq -r '.[0].id'   # id -> feed to /rmm
```

## Implementation

- Wrapper `.claude/scripts/rmm-search.sh` (arg parsing + auth via `rmm-auth.sh` +
  fetch `/api/agents`), engine `.claude/scripts/rmm-search.py` (scoring/filter).
- The agents payload is piped to the engine on **stdin** — it's too large to pass
  as a CLI argument on Windows ("Argument list too long").
- Read-only. To run a command on a found agent, pass its hostname/id to `/rmm`.
- Ranking heuristic mirrors the dashboard Omnibox (`scoreMatch`) in spirit but is
  deliberately looser (multi-field + subsequence) to favor first-try hits.
