# watchctl

> Run Cloak Watch's engine from Terminal with watchctl: scan a Mac, list its inventory and footprint, check domains and package names, and verify the signed feed.

Source: https://docs.subcloak.com/cloak-watch/watchctl/

`watchctl` is Cloak Watch's engine on the command line, for scripts, CI and anyone who prefers Terminal. It runs the same inventory, matching and checks as the app. It reads: the one thing it writes is its own store, and only with `--store`. Three commands do more, and say so: `quarantine restore` moves back a file Watch quarantined, `guard remove` takes away install blocking's scripts and profile line, and `domain` makes DNS lookups and a TLS connection from this Mac. `watchctl` never stops a program, revokes a token or sends a key anywhere.

## Install watchctl

`watchctl` ships inside Cloak Watch, at `Cloak Watch.app/Contents/Resources/watchctl`, so it updates with the app. To run it as `watchctl` from Terminal:

1. Open **Settings › Advanced** and click **Install Command-Line Tool…**.
2. Copy the command it shows and run it once in Terminal. It asks for your password; Watch never changes that folder itself.

```bash title="Terminal"
sudo mkdir -p /usr/local/bin && sudo ln -sf "/Applications/Cloak Watch.app/Contents/Resources/watchctl" /usr/local/bin/watchctl
```

This links `/usr/local/bin/watchctl` to the copy inside the app. Settings › Advanced then says **Installed in /usr/local/bin**. To remove it, run `sudo rm /usr/local/bin/watchctl`.

## Commands

| Command | What it does |
|---|---|
| [`watchctl scan`](#scan) | Inventory, feed matching, checks and secrets; prints the incidents |
| [`watchctl inventory`](#inventory) | What's on the Mac: counts by kind, or every item as JSON |
| [`watchctl match`](#match) | Affected or not, for each incident pack you give it |
| [`watchctl watch`](#watch) | Scans, then rescans on file changes and prints new incidents |
| [`watchctl quarantine`](#quarantine) | Lists Quarantine, or puts one item back |
| [`watchctl checks`](#checks) | Reads trust stores, versions, services and workflows, then answers each headline's checks |
| [`watchctl footprint`](#footprint) (added in Cloak Watch 1.2.0) | Lists the repositories, domains, servers, clusters, cloud accounts and registries this Mac's config files name |
| [`watchctl domain`](#domain) (added in Cloak Watch 1.2.0) | Runs the domain checks for the domains you name |
| [`watchctl playbook`](#playbook) (added in Cloak Watch 1.2.0) | Prints the "I think I've been hit" plan for this Mac |
| [`watchctl processes`](#processes) (added in Cloak Watch 1.2.0) | Lists running programs that match a Watch mode rule |
| [`watchctl xprotect`](#xprotect) (added in Cloak Watch 1.2.0) | Prints Apple XProtect's versions and update setting |
| [`watchctl check-name`](#check-name) (added in Cloak Watch 1.2.0) | Checks a package name before you install it |
| [`watchctl guard`](#guard) (added in Cloak Watch 1.2.0) | Shows or removes install blocking's scripts and shell-profile line |
| [`watchctl service-catalog`](#service-catalog) | Prints the built-in service catalogue |
| [`watchctl feed`](#feed) | Verifies the signed feed and checks pack files |
| `watchctl help` | Prints the usage (also `--help`, `-h`, or no command) |
| `watchctl --version` | Prints the version |

`watchctl render-icon --out DIR` writes the app icon files; it's used by the app's build and isn't needed otherwise.

### Options for scanning

These options work with `scan`, `inventory`, `match` and `watch`. `checks` takes `--roots`, `--home` and `--system-root`.

| Option | Meaning |
|---|---|
| `--roots DIR,DIR` | Code folders to read, comma-separated. Default: whichever of `~/Developer`, `~/code`, `~/src`, `~/Projects`, `~/dev`, `~/repos`, `~/git`, `~/workspace`, `~/Code` and `~/GitHub` exist. |
| `--home DIR` | Treat this folder as the home folder (for testing against a copy) |
| `--system-root DIR` | Treat this folder as `/` for system paths such as `/Library` and `/Applications` |
| `--no-caches` | Skip download caches |
| `--no-secrets` | Skip the plain-text secrets checks ([SEC-1](https://docs.subcloak.com/cloak-watch/detections/#sec-1) and [SEC-3](https://docs.subcloak.com/cloak-watch/detections/#sec-3)) |
| `--history` | Also check shell history ([SEC-2](https://docs.subcloak.com/cloak-watch/detections/#sec-2)), which the app doesn't |

## scan

```bash
watchctl scan [--json] [--roots DIR,DIR] [--pack FILE]... [--packs DIR] [--index FILE] [--store PATH]
              [--no-secrets] [--history] [--no-caches] [--home DIR] [--system-root DIR] [--all]
```

Runs a full scan and prints a summary, any gaps, and the incidents by tier with their evidence and steps.

| Option | Meaning |
|---|---|
| `--pack FILE` | An incident pack to match. Repeat for more. |
| `--packs DIR` | Every pack in a folder. Other JSON files are skipped with a note. |
| `--index FILE` | A malicious-version index file, as saved by [`feed client`](#feed) |
| `--store PATH` | Keep state in a store at this path, so the starting point and dismissals work across runs |
| `--json` | Print the whole report as JSON |
| `--all` | Print every Good to know incident and all evidence and steps, instead of the first few |

`watchctl` doesn't download the feed. Without `--pack`, `--packs` or `--index` it runs only the built-in checks.

**Exit status:** `2` when anything needs fixing now (Act now), `0` otherwise, `1` on an error. Use it in CI:

```bash title="Terminal"
watchctl scan --roots ~/code/shopapp --packs ./packs || echo "Act now: check the output"
```

Example output, against a test Mac with one malicious package:

```text
2 packages, 0 extensions, 0 MCP servers, 0 skills; 1 known-malicious; 0 secrets in plain-text files
Scanned 1 projects, 5 items, 4 files in 0.0 s (inventory 0.0 s, matching 0.01 s).
  ○ Safari extensions: Safari keeps its extension list where only apps with Full Disk Access can read it. (Grant Full Disk Access in System Settings › Privacy & Security)

Act now (1)
  ▲ [PKG-1] Hollowtide npm worm (quickcache-utils-demo / quickcache-store-demo)
      Affected: yes, because quickcache-utils-demo@6.0.0 installed, but it arrived outside the live window (and 1 more).
      · ~/code/shopapp/node_modules/quickcache-utils-demo/package.json
      · ~/code/shopapp/package-lock.json
      1. Stop the launch item com.user.gh-token-monitor and quarantine its plist.
      2. Quarantine ~/.local/bin/gh-token-monitor.sh.
      3. Quarantine ~/.config/gh-token-monitor/.
      4. Remove the SessionStart hook running “setup.mjs” from .claude/settings.json.

FYI (2)
  ○ [AGT-5] Claude Code may run any shell command without asking
      Affected: yes, because ~/code/shopapp/.claude/settings.json allows Bash(*).
      · ~/code/shopapp/.claude/settings.json
  ○ [PKG-8] No install cooldown is set
      Affected: yes, because A 7-day cooldown would have kept most 2025–2026 hijacked versions off this Mac.
      · ~/.npmrc
      1. Set a 7-day cooldown in Install Guard (npm min-release-age, pnpm minimumReleaseAge, uv exclude-newer).

Packs: 1 affected, 0 not affected.
```

`▲` marks Act now, `●` Review and `○` FYI; lines starting `○` under the summary are gaps. Incidents from the starting point are marked `(baseline)`. `watchctl` prints steps; it never runs them.

## inventory

```bash
watchctl inventory [--json] [--kind KIND] [--roots DIR,DIR] [--home DIR]
```

Lists what's on the Mac, by tab and kind, with packages broken down by ecosystem and where they were found:

```text
5 items (2 distinct packages) in 1 projects, 0.0 s.

Packages: 4
  Package: 4 (npm 4; installed 2, lockfile 2)

AI agents & MCP: 1
  Agent settings: 1
  ○ Safari extensions: Safari keeps its extension list where only apps with Full Disk Access can read it.
```

`--json` prints every item. `--kind` limits it to one kind: `package`, `pythonStartup`, `homebrewTap`, `editorExtension`, `browserExtension`, `mcpServer`, `agentSkill`, `agentConfig`, `agentHook`, `rulesFile`, `editorTask`, `launchItem`, `shellProfile`, `gitHook`, `gitConfig`, `workflow`, `registryConfig`, `credentialFile`, `dockerImage` or `hostArtifact`.

```bash title="Every MCP server on this Mac, as JSON"
watchctl inventory --kind mcpServer --json
```

## match

```bash
watchctl match --pack FILE [--pack FILE]... [--json] [--roots DIR,DIR]
```

Answers "am I affected?" for each pack: the verdict, why, the evidence and the steps. It warns if a pack fails validation. **Exit status:** `2` when any pack affects this Mac.

```text
▲ Affected: Hollowtide npm worm (quickcache-utils-demo / quickcache-store-demo) (CW-TEST-0001)
    Affected: yes, because quickcache-utils-demo@6.0.0 installed, but it arrived outside the live window (and 1 more).
    · quickcache-utils-demo@6.0.0 installed
    · quickcache-utils-demo@6.0.0 pinned in package-lock.json
    1. Stop the launch item com.user.gh-token-monitor and quarantine its plist.
    …
```

A pack that doesn't affect the Mac prints `✓ Not affected:` and the reason.

## watch

```bash
watchctl watch [--seconds N] [--roots DIR,DIR] [--pack FILE]...
```

Scans once, then watches your code folders and the places attacks persist for file changes, rescans only what changed, and prints new incidents as they appear. It runs until you press <kbd>Control</kbd> <kbd>C</kbd>, or for `N` seconds.

```text
Watching 14 folders. 1,240 packages, 38 extensions, 7 MCP servers, 4 skills; 0 known-malicious; 0 secrets in plain-text files
[2026-10-23T09:12:40Z] 312 changed paths → rescan 0.42 s, 1 new cards
  Review: …
```

The app does the same: with **Watch for changes as they happen** on in Settings › General, it rechecks what changed within seconds, and runs a full scan once a day.

## quarantine

```bash
watchctl quarantine list [--root DIR]
watchctl quarantine restore ID [--root DIR]
```

`list` prints each record's ID, kind (`moved` or `backup`), whether it was restored, the original path and the reason. `restore ID` puts that item back, as **Put Back** does in the app. `--root` uses a different Quarantine folder than `~/Library/Application Support/Cloak Watch/Quarantine`.

```text
20261023-091512-4f2a9c1e  moved  ~/Library/LaunchAgents/com.user.gh-token-monitor.plist  Launch item com.user.gh-token-monitor
```

## checks

```bash
watchctl checks --headlines FILE [--only H-ID,H-ID] [--json] [--roots DIR,DIR]
```

Answers [Check my Mac](https://docs.subcloak.com/cloak-watch/check-my-mac/) for each headline in `FILE`, a file of headlines in the feed's format, without the full package scan. It reads this Mac's trust stores, apps and tools, services and workflows (and Homebrew and your projects' files), then prints one answer per headline: **AFFECTED**, **RELEVANT TO YOU**, **NOT AFFECTED** or **NOT CHECKED**, each check's answer, the places behind it, what was checked, and numbered steps.

| Option | Meaning |
|---|---|
| `--headlines FILE` | The headlines to answer. Required. |
| `--only H-ID,H-ID` | Answer only these headlines |
| `--json` | Print the answers as JSON |
| `--all` | Also list places that were checked and are fine (marked without `!`) |
| `--roots DIR,DIR` | Code folders to read for workflows, as for `scan` |

```text
Read 14 trust stores, 23 apps and tools, 9 services, 31 workflows in 1.2 s (412 files).

H-2026-0001  RELEVANT TO YOU  Example breach at a package registry
  This Mac uses npm (1 file). Watch can't see your npm account.
  - [serviceFootprint] Relevant to you: This Mac uses npm (1 file). Watch can't see your npm account.
      ! npm login: .npmrc: Names registry.npmjs.org, changed 2 Oct 2026  ~/.npmrc
      checked: Looked for npm's login and config in ~/.npmrc. Watch notes only that they exist and which hosts, profiles or providers they name, never what's in them.
    1. Revoke the access tokens this Mac holds for npm, then create new ones  <https://www.npmjs.com/settings/~/tokens>
    2. Put the new access tokens in ~/.npmrc, and in your CI secrets
```

It only reads. Paths are printed with `~` for your home folder, and no secret values appear: service checks note presence and names only.

## footprint

(added in Cloak Watch 1.2.0)

```bash
watchctl footprint [--counts] [--roots DIR,DIR]
```

Prints the [Footprint](https://docs.subcloak.com/cloak-watch/footprint/): one line per repository, domain, server, cluster, cloud account or registry this Mac's config files name, with its type, provider, where it was found and the file. `--counts` prints how many of each type and provider instead. It reads files only and contacts nothing.

## domain

(added in Cloak Watch 1.2.0)

```bash
watchctl domain NAME [NAME…]
```

Runs the [domain checks](https://docs.subcloak.com/cloak-watch/domains/) for each domain you name: its CNAME chain (with "(no address: dangling)" for a CNAME that points at nothing), any DNS or TLS error, the certificate chain with each certificate's issuer and expiry, whether macOS trusts it, and its CAA records. Then one line per finding, such as `[review] DOM-4 …`. When a local TLS-inspecting proxy answered, it says the real certificate wasn't seen.

This command makes network lookups from this Mac: DNS queries through your usual resolver, and a TLS connection to each domain on port 443. Nothing goes to Subcloak.

## playbook

(added in Cloak Watch 1.2.0)

```bash
watchctl playbook [--roots DIR,DIR]
```

Scans the Mac and prints the [I think I've been hit](https://docs.subcloak.com/cloak-watch/respond/) plan: its warnings (marked `!`), then each step (Contain, Revoke, Rotate, Verify) with its rows and up to three places for each. It only prints: it never stops, revokes or checks anything. It doesn't download the feed, so it lists what the built-in checks find, not feed matches.

## processes

(added in Cloak Watch 1.2.0)

```bash
watchctl processes [--bench N]
```

Runs [Watch mode](https://docs.subcloak.com/cloak-watch/watch-mode/)'s running-programs check once and prints one line per match: `PROC-1 act now` or `PROC-2 review`, the process ID, the program's path, the incident or place it matched and its signature. Otherwise it prints "No running program matches a Watch mode rule.", then how many processes it looked at and the CPU each check took. `--bench N` runs the check N times and also prints the cost of listing processes. **Exit status:** `2` when any PROC-1 program is running.

## xprotect

(added in Cloak Watch 1.2.0)

```bash
watchctl xprotect [--last-run]
```

Prints XProtect's rules version and when they were installed, XProtect Remediator's version, and whether **Install Security Responses and system files** is on. `--last-run` also reads the system log for when XProtect Remediator last ran, in the last 3 days.

## check-name

(added in Cloak Watch 1.2.0)

```bash
watchctl check-name ECOSYSTEM NAME [VERSION] [--index FILE]
```

Checks a package name as [Install Guard](https://docs.subcloak.com/cloak-watch/install-guard/#check-a-package-before-you-install-it) does: whether it looks like a popular package, and whether it has letters from outside the Latin alphabet or invisible characters. With `--index FILE` (a malicious-version index saved by [`feed client`](#feed)), it also says whether the feed lists it as malicious. ECOSYSTEM is `npm`, `pypi`, `cargo`, `gem`, `go` or `composer` (or a name such as `crates.io`, `rubygems` or `packagist`). Nothing is sent. **Exit status:** `2` when the version is on the malicious list.

## guard

(added in Cloak Watch 1.2.0)

```bash
watchctl guard status | remove [--home DIR]
```

Install blocking is [coming soon](https://docs.subcloak.com/cloak-watch/install-guard/#block-suspected-releases) and isn't set up by Cloak Watch 1.2. If an earlier preview build set it up on this Mac, these commands show and remove what it left:

| Command | What it does |
|---|---|
| `guard status` | Whether Watch's scripts are in `~/.cloak-watch/bin`, whether its handshake token is there, and for each of `.zshrc`, `.zprofile`, `.bash_profile` and `.bashrc`, whether it has Watch's PATH line |
| `guard remove` | Removes the scripts, the token and the PATH line from each shell profile, as **Turn Off** in Install Guard does. A profile it can't edit is listed with the line to remove by hand (exit status `1`). Open a new terminal window afterwards. |

## service-catalog

```bash
watchctl service-catalog
```

Prints the built-in service catalogue as JSON, in the same format as the feed's service catalogue: each service's ID, name, what kind of login it is, the files Watch looks for and which names it may read from them, and its revoke and activity-log links. The feed's copy, when there is one, replaces entries with the same ID.

## feed

```bash
watchctl feed verify --root ROOT.json --repo DIR|URL [--state FILE] [--now ISO8601] [--packs] [--content-key EPOCH=HEX]
watchctl feed pack FILE.json…
watchctl feed client --url URL --root ROOT.json --cache DIR [--save-index FILE] [--lookup ECOSYSTEM/NAME@VERSION]…
```

| Command | What it does |
|---|---|
| `feed verify` | Verifies a signed feed (a folder or URL) from a trusted root file, as the app does, and prints the sequence number and every file it lists. `--packs` also downloads, verifies and validates every pack (`--content-key` decrypts paid ones). `--state` keeps a trust state file so rollback is checked across runs. `--now` checks as of another time. |
| `feed pack` | Decodes and validates pack files with the app's rules: `ok` or `FAIL` with the problems. |
| `feed client` | Runs the app's feed client against a feed into a cache folder, with timings. `--save-index` writes the merged malicious-version index for `scan --index`; `--lookup npm/NAME@VERSION` looks a version up. |

```text
verified: seq 1, root v1, timestamp v1 signed 2026-10-22T12:00:00Z, expires 2026-10-22T13:00:00Z
  CW-TEST-0002 r1 malicious/confirmed free broadcast  packs/07b0…a3.json
  CW-TEST-0001 r2 malicious/confirmed free broadcast  packs/e173…36.json
  index seq 1, 15 entries  index/1-1a0b…28.bin
```

If verification fails, the command says why, for example `feed verification failed: timestamp expired at …`.

## Related

  - [Detection catalogue](https://docs.subcloak.com/cloak-watch/detections/): The IDs in the output.
  - [Check my Mac](https://docs.subcloak.com/cloak-watch/check-my-mac/): What watchctl checks answers.
  - [Feed](https://docs.subcloak.com/cloak-watch/feed/#how-the-feed-is-signed-and-verified): How the feed is signed and verified.
  - [Quarantine and restore](https://docs.subcloak.com/cloak-watch/quarantine/): What restore does.
