sync: auto-sync from GURU-5070 at 2026-06-15 11:20:33
Author: Mike Swanson Machine: GURU-5070 Timestamp: 2026-06-15 11:20:33
This commit is contained in:
25
.claude/commands/rmm-search.md
Normal file
25
.claude/commands/rmm-search.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: rmm-search
|
||||
description: Cleanly find machines in the GuruRMM fleet with a flexible, client-aware search (no more grepping /api/agents and hitting the wrong client's box). Front door for locating an agent before acting on it via /rmm.
|
||||
---
|
||||
|
||||
# /rmm-search — find GuruRMM machines on the first try
|
||||
|
||||
Thin entry point to the `rmm-search` skill. Engine: `.claude/scripts/rmm-search.sh`.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/rmm-search <words...> [-c <client>] [--online] [--json] [-n N]
|
||||
/rmm-search -c <client> List ALL machines for a client
|
||||
/rmm-search --list-clients Show distinct client names
|
||||
```
|
||||
|
||||
Every query word must match some field (hostname/client/site/OS/id), so words
|
||||
**narrow** — `hyperv valleywide` returns only Valley Wide's hyperv host, never
|
||||
Dataforth's. Matching is normalized (case/space/hyphen-insensitive) with
|
||||
prefix/substring/subsequence ranking; `-c` is an explicit hard client scope and
|
||||
refuses to guess when the client name is ambiguous.
|
||||
|
||||
Use this to *find* an agent; pass the resulting hostname/id to `/rmm` to *act*.
|
||||
Full matching rules + examples: `.claude/skills/rmm-search/SKILL.md`.
|
||||
@@ -12,6 +12,7 @@
|
||||
- [Community Forum (Flarum)](reference_community_forum.md) — Flarum forum at community.azcomputerguru.com, API access, database, posting workflow.
|
||||
- [Radio Show Website](reference_radio_website.md) — Astro static site at radio.azcomputerguru.com on IX server.
|
||||
- [IX Server Access](reference_ix_server_access.md) — `ix.azcomputerguru.com` / 172.16.3.10. Reachable when Tailscale is on (no VPN). SSH currently uses sshpass with root password; key auth from GURU-5070 not configured yet (was CachyOS, now Win11 — verify).
|
||||
- [Cloudflare access](reference_cloudflare_access.md) — Cloudflare API creds in SOPS `services/cloudflare.sops.yaml` (full DNS + account tokens; azcomputerguru zone_id 1beb9917...). azcomputerguru.com DNS is on Cloudflare (not IX) — edit via Cloudflare API, not whmapi1.
|
||||
- [Matomo Analytics](reference_matomo_analytics.md) — Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites.
|
||||
- [TickTick Integration](reference_ticktick_integration.md) — OAuth API integration, MCP server, SOPS vault creds, project/task CRUD.
|
||||
- [Client Docs Structure](reference_client_docs_structure.md) — clients/<name>/docs/ layout (overview, network, servers, cloud, security, rmm). Template: clients/_client_template/.
|
||||
@@ -39,6 +40,7 @@
|
||||
- [Verify committed state before push](feedback_verify_committed_state_before_push.md) — webhook builds from origin/main: verify the COMMITTED build (git stash + build), not the working tree; bad git-add pathspec silently aborts staging. Stage by directory.
|
||||
- [Scheduling = coord todo, not schedulers](feedback_scheduling_via_coord_todo.md) — Defer future work as a coord todo (POST /api/coord/todos; needs text + created_by_user + created_by_machine) for a later session to pick up. NOT /schedule remote CCR agents (no vault/creds there) or local scheduled tasks.
|
||||
- [DMARC rua INKY only when onboarded](feedback_dmarc_rua_inky_onboarded_only.md) — Don't point a client's DMARC rua at reports-sg.inkydmarc.com unless that client is onboarded to INKY (most aren't). Use plain `p=none` with no rua otherwise.
|
||||
- [Use rmm-search to find machines](feedback_rmm_search_skill.md) — Find GuruRMM agents via the `rmm-search` skill (`rmm-search.sh <words> [-c client]`), never hand-grep /api/agents (it bleeds across clients). Then hand hostname/id to `/rmm`.
|
||||
- [DM wrapped command lines to Mike](feedback_dm_wrapped_command_lines.md) — Long single-line output (consent links, URLs, one-liners) gets DM'd to Mike via the `discord-dm` skill so it's copy-pasteable, not terminal-wrapped. `discord-dm.sh mike "<link>"`.
|
||||
- [Attribution is read, never inferred](feedback_attribution_from_identity.md) — Who-did-what (user+machine) comes ONLY from identity.json + users.json + git authorship. Never infer from hostname patterns, the userEmail hint, or memory. The "5070" box is Mike's. sync.sh reconciles git config to identity.json; /save renders the User block via whoami-block.sh.
|
||||
- [D2TESTNAS SSH Access](feedback_d2testnas_ssh.md) — Use root@192.168.0.9 with Paper123!@#, not sysadmin.
|
||||
|
||||
12
.claude/memory/feedback_rmm_search_skill.md
Normal file
12
.claude/memory/feedback_rmm_search_skill.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: feedback-rmm-search-skill
|
||||
description: Use the rmm-search skill to find GuruRMM machines, never grep /api/agents by hand
|
||||
metadata:
|
||||
type: feedback
|
||||
---
|
||||
|
||||
To locate a machine/agent in GuruRMM, use the **`rmm-search`** skill (`bash .claude/scripts/rmm-search.sh <words> [-c <client>]`) — do NOT pull `/api/agents` and grep client-side.
|
||||
|
||||
**Why:** Hand-grepping bleeds across clients and picks the wrong box — e.g. searching `hyperv` returns both Valley Wide's and Dataforth's hyperv hosts, and it's easy to act on the wrong one. Mike built the UI Omnibox for this and asked for a CLI equivalent (2026-06-15). rmm-search treats every query word as a required filter across hostname/client/site/OS (so `hyperv valleywide` can only return Valley Wide's box), is normalized (case/space/hyphen-insensitive) with typo tolerance, and `-c <client>` hard-scopes (refuses to guess on ambiguous client names).
|
||||
|
||||
**How to apply:** `rmm-search.sh hyperv valleywide` or `... hyperv -c valleywide` to find; `--json | jq -r '.[0].id'` to get the agent id; then hand hostname/id to the [[reference_gururmm]] `rmm` skill to actually run commands. Online state is from last_seen (<5min), not the unreliable `is_connected` flag. Engine: `rmm-search.sh` + `rmm-search.py`.
|
||||
14
.claude/memory/reference_cloudflare_access.md
Normal file
14
.claude/memory/reference_cloudflare_access.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: reference-cloudflare-access
|
||||
description: Where the Cloudflare API credentials live (SOPS vault) — azcomputerguru.com DNS is on Cloudflare, not the IX nameservers
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
Cloudflare API access is in the SOPS vault at **`services/cloudflare.sops.yaml`** (account "Mike@azcomputerguru.com Account", account_id `44594c346617d918bd3302a00b07e122`). Fields under `credentials`:
|
||||
- `api_token_full_account` — full-account token (`solitary-rain-773d`, added 2026-05-10, expires 2027-05-10)
|
||||
- `api_token_full_dns` — full DNS-edit token (use this for DNS record changes)
|
||||
- `api_token_legacy` — legacy token
|
||||
- `zone_id_azcomputerguru` = `1beb9917c22b54be32e5215df2c227ce`
|
||||
|
||||
**azcomputerguru.com DNS is hosted on Cloudflare** (ns mckinley/amir.ns.cloudflare.com), NOT the IX/cPanel nameservers (ns1/ns2.acghosting.com) that most CLIENT domains use. So azcomputerguru.com zone edits go through the Cloudflare API, not `whmapi1`. Pattern: `curl -H "Authorization: Bearer <api_token_full_dns>" https://api.cloudflare.com/client/v4/zones/<zone_id>/dns_records`. (Used 2026-06-15 to add the cross-domain DMARC report-authorization record `cryoweave.com._report._dmarc.azcomputerguru.com TXT "v=DMARC1;"` so client DMARC reports can be sent to rua@azcomputerguru.com.) See [[reference_ix_server_access]] for client-domain DNS (cPanel).
|
||||
138
.claude/scripts/rmm-search.py
Normal file
138
.claude/scripts/rmm-search.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""rmm-search engine. Reads the GuruRMM agents JSON array on stdin; reads
|
||||
QUERY/CLIENT/ONLINE/JSON/LISTC/LIMIT from the environment. Flexible, forgiving
|
||||
multi-field search — see rmm-search.sh for usage. Kept as a sidecar (not a
|
||||
heredoc) because the agents payload is too large to pass as a CLI argument."""
|
||||
import sys, os, json, re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
raw = sys.stdin.read()
|
||||
agents = json.loads(raw) if raw.strip() else []
|
||||
query = os.environ.get("QUERY", "").strip()
|
||||
client = os.environ.get("CLIENT", "").strip()
|
||||
online_only = os.environ.get("ONLINE") == "1"
|
||||
as_json = os.environ.get("JSON") == "1"
|
||||
list_clients = os.environ.get("LISTC") == "1"
|
||||
try:
|
||||
limit = int(os.environ.get("LIMIT", "0") or 0)
|
||||
except ValueError:
|
||||
limit = 0
|
||||
|
||||
|
||||
def norm(s):
|
||||
return re.sub(r'[^a-z0-9]', '', (s or '').lower())
|
||||
|
||||
|
||||
def is_subseq(t, v):
|
||||
it = iter(v)
|
||||
return all(c in it for c in t)
|
||||
|
||||
|
||||
def field_score(val, term, allow_subseq):
|
||||
"""How well a single query word matches one normalized field value."""
|
||||
if not val or not term:
|
||||
return 0
|
||||
if val == term:
|
||||
return 100
|
||||
if val.startswith(term):
|
||||
return 80
|
||||
if term in val:
|
||||
return 60
|
||||
if allow_subseq and len(term) >= 3 and is_subseq(term, val):
|
||||
return 25
|
||||
return 0
|
||||
|
||||
|
||||
# field -> (key, weight, allow_subsequence)
|
||||
FIELDS = [
|
||||
('hostname', 1.00, True),
|
||||
('client_name', 0.75, False),
|
||||
('site_name', 0.60, False),
|
||||
('os_type', 0.55, False),
|
||||
('id', 0.40, False),
|
||||
]
|
||||
|
||||
|
||||
def fresh(last_seen):
|
||||
if not last_seen:
|
||||
return False
|
||||
try:
|
||||
dt = datetime.fromisoformat(last_seen.replace('Z', '+00:00'))
|
||||
return (datetime.now(timezone.utc) - dt).total_seconds() < 300
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
if list_clients:
|
||||
for n in sorted({(a.get('client_name') or 'Unassigned') for a in agents}):
|
||||
print(n)
|
||||
sys.exit(0)
|
||||
|
||||
# --- explicit hard client scope (normalized; "valleywide" -> "Valley Wide Plastering") ---
|
||||
if client:
|
||||
cn = norm(client)
|
||||
matched = sorted({(a.get('client_name') or '') for a in agents
|
||||
if cn and cn in norm(a.get('client_name'))} - {''})
|
||||
if not matched:
|
||||
print(f"[ERROR] no client matches '{client}'. Try: rmm-search.sh --list-clients", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
if len(matched) > 1:
|
||||
exact = [m for m in matched if norm(m) == cn]
|
||||
if len(exact) == 1:
|
||||
matched = exact
|
||||
else:
|
||||
print(f"[AMBIGUOUS] '{client}' matches {len(matched)} clients - narrow --client:", file=sys.stderr)
|
||||
for m in matched:
|
||||
print(" - " + m, file=sys.stderr)
|
||||
sys.exit(3)
|
||||
agents = [a for a in agents if (a.get('client_name') or '') == matched[0]]
|
||||
|
||||
if online_only:
|
||||
agents = [a for a in agents if fresh(a.get('last_seen'))]
|
||||
|
||||
terms = [t for t in (norm(x) for x in query.split()) if t]
|
||||
joined = norm(query)
|
||||
|
||||
|
||||
def score_agent(a):
|
||||
if not terms:
|
||||
return 1 # no query (e.g. "all machines for client X")
|
||||
nf = [(norm(a.get(k)), w, sub) for k, w, sub in FIELDS]
|
||||
total = 0.0
|
||||
for term in terms:
|
||||
best = max(field_score(val, term, sub) * w for val, w, sub in nf)
|
||||
if best == 0:
|
||||
return 0 # AND: every word must hit some field
|
||||
total += best
|
||||
if joined and joined in norm(a.get('hostname')):
|
||||
total += 60 # whole query lands in hostname -> clearly THE machine
|
||||
return total
|
||||
|
||||
|
||||
results = sorted(((score_agent(a), a) for a in agents),
|
||||
key=lambda t: (-t[0], (t[1].get('hostname') or '').lower()))
|
||||
results = [(s, a) for s, a in results if s > 0]
|
||||
if limit > 0:
|
||||
results = results[:limit]
|
||||
|
||||
if as_json:
|
||||
print(json.dumps([{
|
||||
'hostname': a.get('hostname'), 'id': a.get('id'), 'client': a.get('client_name'),
|
||||
'site': a.get('site_name'), 'os': a.get('os_type'),
|
||||
'online': fresh(a.get('last_seen')), 'last_seen': a.get('last_seen'), 'score': round(s, 1),
|
||||
} for s, a in results], indent=2))
|
||||
sys.exit(0)
|
||||
|
||||
scope = f" in '{(agents[0].get('client_name') if (client and agents) else client)}'" if client else ""
|
||||
if not results:
|
||||
print(f"No machines match '{query}'{scope}. (try fewer/looser words, or --list-clients)")
|
||||
sys.exit(0)
|
||||
|
||||
hdr = f"{'HOSTNAME':<22} {'CLIENT':<26} {'SITE':<16} {'OS':<8} {'STAT':<7} ID"
|
||||
print(f"{len(results)} match(es) for '{query}'{scope} (best first):")
|
||||
print(hdr)
|
||||
print("-" * len(hdr))
|
||||
for s, a in results:
|
||||
st = 'online' if fresh(a.get('last_seen')) else 'offline'
|
||||
print(f"{(a.get('hostname') or '')[:21]:<22} {(a.get('client_name') or '?')[:25]:<26} "
|
||||
f"{(a.get('site_name') or '')[:15]:<16} {(a.get('os_type') or '')[:7]:<8} {st:<7} {a.get('id')}")
|
||||
46
.claude/scripts/rmm-search.sh
Normal file
46
.claude/scripts/rmm-search.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# rmm-search.sh — find machines in the GuruRMM fleet on the first try.
|
||||
#
|
||||
# Flexible, forgiving search: normalizes case/spaces/hyphens, matches across
|
||||
# HOSTNAME + CLIENT + SITE + OS, and treats every query word as a required
|
||||
# filter (AND). So a query naturally narrows and can't bleed across clients:
|
||||
# rmm-search.sh hyperv valleywide -> only Valley Wide's hyperv host
|
||||
# rmm-search.sh hyperv -> every hyperv box, each labeled by client
|
||||
# rmm-search.sh hyperv -c valleywide -> hard-scope to one client, then search
|
||||
#
|
||||
# Usage:
|
||||
# rmm-search.sh <words...> [-c|--client <name>] [--online] [--json] [-n N]
|
||||
# rmm-search.sh -c <client> # list ALL machines for a client
|
||||
# rmm-search.sh --list-clients # show distinct client names
|
||||
#
|
||||
# Online state is derived from last_seen recency (<5 min); the API is_connected
|
||||
# flag is currently unreliable (null fleet-wide). Engine: rmm-search.py.
|
||||
set -u
|
||||
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
||||
|
||||
QUERY=""; CLIENT=""; ONLINE=0; JSON=0; LISTC=0; LIMIT=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-c|--client) CLIENT="${2:-}"; shift 2;;
|
||||
-n|--limit) LIMIT="${2:-0}"; shift 2;;
|
||||
--online) ONLINE=1; shift;;
|
||||
--json) JSON=1; shift;;
|
||||
--list-clients) LISTC=1; shift;;
|
||||
-h|--help) sed -n '2,20p' "$0"; exit 0;;
|
||||
*) QUERY="${QUERY:+$QUERY }$1"; shift;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$QUERY" ] && [ -z "$CLIENT" ] && [ "$LISTC" -eq 0 ]; then
|
||||
echo "[ERROR] give <words>, a --client, or --list-clients" >&2
|
||||
sed -n '12,17p' "$0" >&2; exit 1
|
||||
fi
|
||||
|
||||
eval "$(bash "$ROOT/.claude/scripts/rmm-auth.sh" 2>/dev/null)" >/dev/null
|
||||
if [ -z "${TOKEN:-}" ] || [ -z "${RMM:-}" ]; then echo "[ERROR] RMM auth failed (see rmm-auth.sh)" >&2; exit 1; fi
|
||||
AGENTS=$(curl -s "$RMM/api/agents" -H "Authorization: Bearer $TOKEN")
|
||||
if [ -z "$AGENTS" ] || [ "${AGENTS:0:1}" != "[" ]; then echo "[ERROR] could not fetch agents: ${AGENTS:0:160}" >&2; exit 1; fi
|
||||
|
||||
# Pipe agents on stdin (payload too large for argv on Windows); flags via env.
|
||||
printf '%s' "$AGENTS" | QUERY="$QUERY" CLIENT="$CLIENT" ONLINE="$ONLINE" JSON="$JSON" LISTC="$LISTC" LIMIT="$LIMIT" \
|
||||
python3 "$ROOT/.claude/scripts/rmm-search.py"
|
||||
@@ -23,6 +23,7 @@ that will fail the next email task; fix it with `assign-exchange-role.sh <domain
|
||||
| cascadestucson.com | cascadestucson.com | 207fa277-e9d8-4eb7-ada1-1064d2221498 | NO | Old app only; IdentityRiskyUser not consented |
|
||||
| cclac.net | cclac.net | e8a0fafc-21ee-41e8-a5ba-f3a250a8a30e | NO | |
|
||||
| Cobalt Fine Arts | cobaltfinearts.com | 03c4d4ec-b6d3-4061-a75c-8a4250ba2b29 | NO | |
|
||||
| Cryoweave | cryoweave.com | 44705a37-b5d8-4bb1-882d-e18775612ada | YES | All apps consented 2026-06-15 (Sec Inv + Exch Op Exchange Admin, User Mgr User Admin + Auth Admin, Tenant Admin CA Admin); no MDE. Onboarded for outbound-email deliverability investigation. ACG GA `sysadmin@cryoweave.com` created (vault clients/cryoweave/m365-sysadmin + 1Password Clients). |
|
||||
| CUADRO LLC | cuadro.design | b68c7171-31d6-4b63-8243-7a2cade9caf8 | NO | |
|
||||
| Curtis Plumbing | cparizona.onmicrosoft.com | d2d7ea54-9146-42d1-b99e-0da098550bde | NO | |
|
||||
| cwconcretellc.com | NETORGFT11452752.onmicrosoft.com | dfee2224-93cd-4291-9b09-6c6ce9bb8711 | NO | |
|
||||
|
||||
74
.claude/skills/rmm-search/SKILL.md
Normal file
74
.claude/skills/rmm-search/SKILL.md
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: rmm-search
|
||||
description: >
|
||||
Find machines/agents in the GuruRMM fleet cleanly and on the first try. Use
|
||||
this ANY time you need to 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 and picks the wrong box). Flexible, forgiving, multi-field
|
||||
search with a client filter so a query like "hyperv valleywide" returns ONLY
|
||||
Valley Wide's hyperv host, never Dataforth's. Invoke on: "find the X machine",
|
||||
"which agent is", "look up <host> in RMM", "<client>'s server/DC/hyperv/file
|
||||
server", "search RMM for", "what's the agent id for". After finding the agent,
|
||||
hand its hostname/id 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.
|
||||
Reference in New Issue
Block a user