# Poof CLI reference for agents

Interface: **Poof 0.3.2 customer package**. Executable: `/var/jb/usr/local/bin/poofctl` on the phone. The app and CLI share the same profiles. Install through https://repo.usepoof.app/; see [INSTALL.md](INSTALL.md).

## Invocation contract

Run commands on the phone as root. `--version`, `specs`, `self-check` and `hooks` are dispatched before the CLI's root check; use root for consistent access to system diagnostics.

```sh
export PATH=/var/jb/usr/local/bin:/var/jb/usr/bin:/var/jb/bin:/var/jb/usr/sbin:/var/jb/sbin:/usr/bin:/bin:/usr/sbin:/sbin
PC=/var/jb/usr/local/bin/poofctl
"$PC" --version
"$PC" setup-status
"$PC" list --json
```

- Options use `--key value`. `list --json` is the only value-less command option. Boolean options use the literal values documented below, usually `yes` or `no`.
- `--profile` takes a profile **UUID**, not its display name or list position. Get it from `list --json` or the output of `create`.
- Quote user-supplied names and arguments; pass argument arrays when calling from code. Unknown, duplicate or empty options are rejected, except repeatable `enroll --group`.
- Capture stdout, stderr and exit status separately. Diagnostic notes can appear on stderr even on success. Only commands explicitly marked JSON below return JSON. There is no global `--json` or `--help`; invoking without a command prints usage to stderr and exits 64.
- `create`, `select`, `edit` and `rename` require an active licence and completed setup. Inspection, cleanup and licence commands remain available without a valid licence.
- Wait for each mutation to finish. Registry operations are serialized internally; avoid overlapping workflow commands or app actions.

## Command index

| Command | Output | Effect |
|---|---|---|
| `--version` | Version string | Inspect installed CLI version |
| `list [--json]` | Text or JSON | List profiles and selection |
| `status` | Text counters | Inspect enrollment/selection summary |
| `specs` | One model/OS pair per line | Inspect the identity catalog |
| `device-id` | Phone ID string | Read phone licence identifier |
| `setup-status` | JSON | Read compatibility and setup state without changing it |
| `hooks [--process NAME] [--strict yes]` | Text per daemon | Inspect running hook evidence |
| `self-check` | Text, exit status | Run built-in identity/path checks; not an account-login test |
| `keychain-count --profile UUID` | Counts and `total=N` | Count a profile's keychain records; no secret values |
| `create …` | New UUID | Create a profile and empty data folders |
| `select …` | `selected for next launch` | Close Reddit, switch profile, refresh relevant services |
| `rename …` | `renamed` | Change display name only |
| `edit …` | `edited` | Change model/OS identity fields |
| `delete …` | `deleted` | Delete a profile, its data and normally its keychain records |
| `setup` | JSON | Discover Reddit and prepare/reload Poof as needed |
| `setup-retry` | JSON | Explicit recovery of failed/interrupted setup |
| `setup-probe` | No normal output | Internal preferences-service probe |
| `enroll …` | `enrolled` / `already enrolled` | Advanced manual enrollment |
| `repair` | Text | Reconcile an interrupted selection |
| `prune [--dry-run yes]` | Count | Remove stale launch records and temporary publish files |
| `gc [--apply yes]` | Paths and count | List orphan profile folders/snapshots; delete only with `--apply yes` |
| `sweep [--dry-run yes]` | Paths | Remove enrolled-app HTTP/cookie/snapshot stores outside profile folders |
| `rotate-log [--max-mb N] [--force yes]` | `rotated bytes=N` | Rotate event logs |
| `licence-status` | JSON, potentially including token | Inspect local licence |
| `licence-install --token TOKEN` | `licence installed` | Install an already-issued signed token |
| `licence-remove` | `licence removed` | Remove local licence token |

## Profiles

### List and inspect

```sh
"$PC" list --json
"$PC" status
"$PC" specs
```

`list --json` returns:

```json
{
  "active": "PROFILE_UUID_OR_EMPTY_STRING",
  "profiles": [
    {
      "uuid": "PROFILE_UUID",
      "name": "Account A",
      "model": "iPhone13,2",
      "osVersion": "16.6",
      "build": "20G75",
      "width": 390,
      "height": 844,
      "scale": 3,
      "active": true,
      "deleting": false,
      "createdAt": 1792796400
    }
  ]
}
```

This is a shape example, not an identity recommendation. Rows are sorted by name, then UUID. `createdAt` is Unix seconds or `null` when unavailable; missing identity values can also be `null`. `deleting: true` means removal was started and has not finished.

`status` prints `enrolled profiles=N selected=yes|no deleting=N bundle=...`. Use JSON `list` to get the selected UUID. `specs` prints model, board, OS/build, screen geometry, `fit=recommended|usable|different`, sources and verification flags. Read the current catalog instead of hardcoding device/OS combinations.

### Create

Generated identity:

```sh
"$PC" create --name 'Account A' --generate usable --allow-unverified yes
```

| Option | Meaning |
|---|---|
| `--name NAME` | Required; 1–128 characters, case-insensitively unique, cannot itself be a UUID |
| `--generate layout` | Default when no model is given: same width, height and scale as the physical phone |
| `--generate usable` | Same scale and each screen axis within 100 points |
| `--generate any` | Any eligible catalog layout |
| `--allow-unverified yes` | Allow generated catalog entries whose memory/network values are derived or unverified; some layouts otherwise have no eligible candidates |
| `--app-attest passthrough\|unsupported` | Default `passthrough`; `unsupported` reports App Attest unavailable |

Explicit identity:

```sh
"$PC" create --name 'Account B' --model MODEL --os IOS_VERSION --build BUILD
```

`MODEL`, `IOS_VERSION` and `BUILD` must be a valid combination from `specs`. Explicit creation requires all three. Optional `--width N --height N --scale N` must exactly match that model's catalog geometry. `--allow-layout-mismatch yes` permits an explicit model with a `different` layout; a `usable` fit needs no override. `--generate` and `--model` are mutually exclusive. Both generated and explicit identities exclude the physical phone's model family and OS version.

Success prints only the new UUID to stdout. Creation does not select the profile or sign into Reddit. Store the UUID and use it for later commands.

### Select

```sh
"$PC" select --profile PROFILE_UUID
```

Closes Reddit, waits for outgoing preferences, publishes the selected profile, stops related extension processes and refreshes shared caches/services. It does **not** launch Reddit. Open Reddit after the command finishes. Returning to an existing account means selecting its existing UUID.

Advanced options; ordinary switching should use the defaults:

| Option | Default / behavior |
|---|---|
| `--wait-app S` | 10 seconds for the app to stop |
| `--settle-min S` | 7 seconds minimum outgoing-preferences settling |
| `--settle-quiet S` | 1.5 seconds quiet interval |
| `--settle-max S` | 8 seconds settling limit |
| `--require-hooks yes` | Additional strict hook check before switching |
| `--sweep yes` | Also clear enrolled-app HTTP/cookie stores outside profile folders |
| `--snapshots no` | Skip ordinary app-switcher snapshot cleanup; `--sweep yes` still includes snapshots |
| `--rotate-log no` | Skip automatic log rotation above 16 MB |

Timing values accept 0–600 seconds. Shortening the settle defaults can lose late preference writes. `--require-hooks` supplements the customer setup check; omitting it does not bypass setup or licence requirements.

### Rename and edit

```sh
"$PC" rename --profile PROFILE_UUID --name 'Work account'
"$PC" edit --profile PROFILE_UUID --model MODEL --os IOS_VERSION
```

Close Reddit before renaming/editing its selected profile. Rename changes only the display name and follows the same name rules as create.

`edit` accepts `--model`, `--os`, `--build` and `--allow-layout-mismatch yes`; at least one of model/OS/build is required. Omitted fields retain their current values, except a changed OS defaults its build from the catalog. Model/OS/build must remain a valid pair and pass the same identity/layout rules as explicit creation.

Editing preserves the profile UUID and persistent account identifiers. Prefer choosing the identity before signing in: Reddit may treat a changed model or OS as a device change.

### Delete — destructive

```sh
"$PC" delete --profile PROFILE_UUID
```

Close Reddit first, including when deleting an inactive profile. Deletion removes the profile's data folders, snapshots, launch records and keychain records. By default, the active profile cannot be deleted; select another first.

- `--deselect yes` explicitly permits deleting the active profile and clears its selection.
- `--keep-keychain yes` leaves its keychain records behind; it does not preserve its data folders or provide a way to restore the profile.

Use deletion only for the profile the owner intends to remove. It is not an update or login-recovery step.

## Setup and health

```sh
"$PC" setup-status
"$PC" hooks
```

`setup-status` is read-only JSON with `state`, `message`, `deferred`, `catalogAvailable`, `supported` and `supportReason`. States include `waiting`, `checking`, `blocked`, `failed` and `ready`. Read the message for the specific action. `supported` describes system-image compatibility; `ready` describes current hook observations. A zero command exit status means the status was read, not that its state is ready.

`hooks` inspects `cfprefsd`, `securityd`, `containermanagerd`, `lsd`, `accountsd` and `pkd`. `--process NAME` narrows the request. `--strict yes` also treats missing evidence as failure; a daemon that has not started or just restarted can lack evidence without being broken. Use `setup-status` for the overall interpretation.

The app normally handles setup. For explicit command-line setup/recovery:

```sh
"$PC" setup
# If setup failed and the reported cause has been addressed:
"$PC" setup-retry
```

These commands can close Reddit, enroll its discovered installation and reload Poof's service hooks. They preserve existing Poof profiles/licence. They return JSON with setup state/message; exit 0 means `ready`, and exit 2 means not yet ready, including waiting conditions. `prepared` may appear on setup results when completed preparation is retained while hook evidence catches up. `setup-probe` is an internal preferences read used during setup, not a substitute for `setup-status`.

Advanced manual enrollment is also exposed:

```sh
"$PC" enroll --bundle-id com.reddit.Reddit --app /exact/Reddit.app --home /exact/data --group GROUP_ID=/exact/group
```

Repeat `--group` for additional groups. Paths and IDs must come from the actual installed app. This writes enrollment/control state; it is not a routine customer installation step. Prefer automatic `setup`, which discovers the paths itself.

## Licence

```sh
"$PC" device-id
"$PC" licence-status
```

`device-id` prints the phone identifier used by licensing; it is different from a profile UUID. `licence-status` returns a `state`, possibly `reason`, and, when verified claims are available, `iat`, `exp` and **`token`**. Timestamps are Unix seconds. Treat that token as a credential; redact it from reports.

`licence-install --token TOKEN` validates and stores an already-issued signed token for this phone. It does not accept the owner's numeric access code or issue a new licence. `licence-remove` deletes the local token and disables licensed profile operations; it does not erase profiles or revoke the server-side licence. These are support operations. Normal code activation and renewal happen in the app's key panel.

## Maintenance and diagnostics

| Command | Behavior and limits |
|---|---|
| `repair` | Reconciles selection copies/registry with the main app's selected snapshot after an interrupted switch. Does not stop processes, recreate deleted profiles or reset enrollment. |
| `prune --dry-run yes` | Lists the count of stale records/temp files. `prune` without this option removes them. Launch records must be older than one hour and no longer belong to a live enrolled process. |
| `gc` | Lists orphan profile folders/snapshots not owned by the current registry. `gc --apply yes` deletes them. A restored older registry can make valid data look orphaned; review the listed paths before applying. |
| `sweep --dry-run yes` | Lists existing enrolled-app HTTPStorages, cookie and SplashBoard snapshot paths outside profile folders. `sweep` without this option removes them. Close Reddit before applying. This can discard shared app state; it is not needed for ordinary switching. |
| `keychain-count --profile UUID` | Reads counts for that profile's keychain namespace; does not print stored credentials. |
| `rotate-log --max-mb N` | Rotates event logs above N MB, default 16, retaining `.1`, `.2`, `.3`. `--force yes` rotates a nonempty log regardless of the threshold. |
| `self-check` | Runs built-in identity validation and path-isolation regression checks. Successful output does not prove runtime hooks or real account persistence. An intentional rejected-pair subcheck can emit stderr. |

Some cleanup failures, such as an individual `sweep` removal failure, are reported on stderr without a nonzero final exit. Read both streams for maintenance commands.

## Exit statuses

| Code | Meaning |
|---|---|
| `0` | Command completed; interpret its status fields/output as described above |
| `1` | Validation, permissions, licence/setup refusal or operation error; read stderr |
| `2` | `setup` / `setup-retry` returned a state other than `ready` |
| `3` | Hook check failed, including `select --require-hooks yes` |
| `64` | Missing or unknown command; usage printed |

## Minimal agent workflow

1. Read `--version`, `setup-status` and `list --json`. Use existing UUIDs for existing accounts.
2. For a new account, create one profile and capture its UUID. Select it, wait for success, then have the owner sign into Reddit on the phone.
3. To verify separation, switch A → B → A and check the actual account identity after each launch.
4. For a failure, report the command, exit status and relevant error. Do not delete profiles, remove licences or loop setup retries to hide the failure.

Poof's CLI manages profiles, identities, setup and diagnostics. It does not automate Reddit sign-in, import Crane sessions or provide a general-purpose Reddit API.
