Metadata-Version: 2.4
Name: grim-cli
Version: 0.1.0
Summary: Command-line client for Grim, the OSINT/recon platform.
Author: smowggyayy
Requires-Python: >=3.11,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: httpx (>=0.27,<1.0)
Requires-Dist: rich (>=13.7,<14.0)
Requires-Dist: tomli-w (>=1.0,<2.0)
Requires-Dist: typer (>=0.15,<1.0)
Project-URL: Homepage, https://github.com/smowggyayy/grim/tree/main/cli
Project-URL: Repository, https://github.com/smowggyayy/grim
Description-Content-Type: text/markdown

# grim-cli

A command-line client for [Grim](https://github.com/). It's a thin wrapper
around Grim's HTTP API — every command maps to one API call, authenticated
with a personal API key generated from the Grim account page.

## Install

```bash
pipx install grim-cli   # or: pip install grim-cli
grim --help
```

To install from source instead (e.g. to work on the CLI itself):

```bash
poetry install
poetry run grim --help
```

## Setup

Generate an API key from the Grim account page (Account → API keys →
Create), then:

```bash
grim configure
```

This prompts for your API key, verifies it against Grim, and saves it to
`~/.config/grim-cli/config.toml` (owner-only permissions). You only need to
do this once. The prompt is intentionally not a plain `--api-key` flag —
that would leak the key into shell history and be visible to other local
users via `ps`. Running `grim configure` again asks for confirmation before
overwriting an already-saved key. `grim logout` removes the saved key from
disk (the key itself stays valid server-side — revoke it from the account
page if it was compromised).

Also worth running once: `grim --install-completion` — this is a stock
Typer/Click feature (every `grim --help` shows it, not something specific
to this CLI), and it's a bigger win here than on a smaller tool given how
many nested subcommands there are (`scan`/`template`/`schedule`, each with
7-8 of their own). It detects your shell and adds a completion script to
its config (e.g. `~/.zshrc`); restart your shell afterward for it to take
effect. `grim --show-completion` prints the same script to stdout instead
of installing it, if you'd rather wire it into your dotfiles by hand.

## Usage

```bash
grim account                        # tier, credits, 2FA/Tor status
grim modules                        # list available scan modules

grim scan create example.com                          # scan with all modules
grim scan create example.com -m headers -m dns        # only specific modules
grim scan create example.com --preset quick             # canned module list (see below)
grim scan create example.com --force                   # re-run cached modules too
grim scan create example.com --wait                    # stream progress until done

grim scan list                      # your scan history
grim scan get                       # status + per-module results of the scan you just created
grim scan stream                    # tail a running scan's progress
grim scan cancel                    # cancel a running scan
grim scan delete                    # delete a scan and its results
grim scan retry headers             # free re-run of one infra-failed module
grim scan diff                      # compare against the previous force re-scan
grim scan export                    # full Markdown report to stdout (or --output FILE)

grim template create quick -m headers -m dns -m tls  # save a reusable module list
grim template list                  # your saved templates
grim template get <template-id>     # a single template's modules
grim template update <template-id> --name renamed -m headers  # rename/replace modules
grim template delete <template-id>  # delete a template

grim schedule create example.com --interval daily             # all modules, daily
grim schedule create example.com --interval weekly -m headers -m tls
grim schedule create example.com --interval monthly --template-id <template-id>
grim schedule create example.com --interval daily --diff-only # email a diff, not the full report
grim schedule list                  # your scheduled scans
grim schedule get <schedule-id>     # a single schedule's settings
grim schedule update <schedule-id> --interval weekly
grim schedule update <schedule-id> --disable           # pause without deleting
grim schedule update <schedule-id> --diff-only         # switch report style
grim schedule delete <schedule-id>  # delete a scheduled scan
grim schedule run-now <schedule-id> # trigger immediately instead of waiting

grim logout                         # remove the locally saved API key
grim --version                      # print the CLI version and exit
```

### The scan ID is usually optional

Every `scan` subcommand that acts on a specific scan (`get`, `stream`,
`cancel`, `delete`, `retry`, `diff`, `export`) takes the scan ID as an
*optional* trailing argument — omit it and the CLI uses whichever scan you
most recently ran `scan create` against, saved locally in
`~/.config/grim-cli/config.toml`. This is what makes the common
create-then-inspect flow short:

```bash
grim scan create example.com --wait
grim scan export --output report.md   # no id to paste — it's the scan you just created
```

Pass a scan ID explicitly at any time to target a different scan instead
(e.g. `grim scan get abc-123`); it always wins over the saved one, and
nothing here is fetched from the server or verified to still exist — it's
just "the last id `scan create` returned," refreshed every time you create
or resume a scan. `scan list`/`scan get <explicit-id>` never change it, so
checking on an old scan doesn't knock the scan you're actively working on
out of "last." If you've never run `scan create` (e.g. right after `grim
configure`), omitting the ID fails with a clear error telling you to run
`scan create` first or pass one.

### Running specific modules

`-m`/`--module` is repeatable and takes exactly the module names `grim
modules` lists (e.g. `headers`, `dns`, `tls`, `whois`, `crawler`,
`portscan`, `git_exposure` — run `grim modules` for the full, current list
rather than relying on it being documented here, since new modules get
added over time):

```bash
grim modules                                          # see what's available
grim scan create example.com -m headers -m tls -m dns  # only these three
```

Omitting `-m` entirely runs every module. `--preset quick` (headers, dns,
tls) and `--preset passive` (whois, dns, wayback, typosquat) apply the same
canned module lists as the dashboard's Quick/Passive only buttons —
`--preset passive` in particular never touches the target directly (every
module in it is third-party-sourced or DNS-only). `--module` and `--preset`
are mutually exclusive; for a reusable named list instead of a one-off
preset, see `grim template` below. `scan create --template-id` doesn't
exist — templates are only a module-list source when *creating a
schedule* (`--template-id`); for a one-off scan, resolve the template's
modules yourself with `grim template get` and pass them via repeated `-m`.

### Exporting the Markdown report

`grim scan export` downloads the full per-module report for a *completed*
scan — the same report generated by the dashboard's "Export as Markdown"
button and linked from scheduled-scan report emails, byte-for-byte
identical either way, since all three are the same server endpoint. It
fails with a 409 if the scan is still running or hasn't been scanned yet
(check `grim scan get` first). With no `--output`, the Markdown is written
straight to stdout so it composes with shell redirection:

```bash
grim scan create example.com --wait
grim scan export --output report.md     # save to a file
grim scan export > report.md            # equivalent, via redirection
grim scan export | grep -A2 "## Findings"  # or pipe it straight into another tool
```

Unlike every other command here, `scan export` has no `--json` — it
downloads Markdown, not JSON, so there's no JSON form to switch to.

Scheduled scans always email their report to your account's own confirmed
address (add and confirm one from the Grim account page first — there's no
way to point a schedule at a different inbox) and always force a fresh
re-scan, charging a credit on every successful run just like `scan create
--force`.

Every command accepts `--json` (except `scan export`, above) for raw JSON
output instead of a formatted table/message — handy for piping into `jq`.
On `scan create --wait`, `scan stream`, and `scan retry --wait`, `--json`
switches the live progress feed to one JSON object per line (instead of
colored text), so each event stays independently parseable, e.g. piped
through `jq -c`. Every invocation also prints a startup banner to stderr,
so none of this ever mixes into piped/redirected stdout — including the
"Using last scan: ..." note printed whenever a scan ID is defaulted, which
also goes to stderr for the same reason.

Commands that return a single JSON document (`account`, `modules`, every
`scan`/`template`/`schedule` subcommand except `scan stream` and `scan
export`) also accept `--output`/`-o FILE` to save that JSON to disk in
addition to whatever's printed to the screen — independent of `--json`, so
you can keep the human-readable table on screen and still get a saved
artifact:

```bash
grim scan get --output result.json
```

`template update` and `schedule update` only change the fields you pass —
omitted fields are left as-is server-side. Calling either with no fields at
all is rejected locally before any request is sent.

If the connection drops during `scan create --wait`, `scan stream`, or
`scan retry --wait`, the CLI automatically reconnects with backoff (up to 5
attempts) instead of dying with a traceback; already-printed module results
aren't repeated. Any other network failure (unreachable host, timeout) is
reported as a clean one-line error instead of a raw Python traceback.

Common workflow — start a scan, wait for it, then fetch the results as JSON:

```bash
grim configure
grim scan create example.com --wait
grim scan get --json | jq
```

## Scope

This wraps the API-key-accessible subset of the Grim API: scan
create/get/list/cancel/delete/stream/retry/diff/export, scan templates,
scheduled scans, the module list, and the caller's own account info
(tier/credits). Admin operations (user management, tier/role changes, the
audit log) require a browser session and aren't reachable with an API key —
use the Grim web dashboard for those.

## Development

```bash
poetry install --with dev
poetry run pytest
```

## Releasing

Publishing to PyPI is automated (`.github/workflows/publish-cli.yml`) and
triggered by a `cli-vX.Y.Z` tag — the tag itself doesn't set the version,
it's checked against `cli/pyproject.toml`'s `version` and the workflow
fails loudly if they don't match. To cut a release:

```bash
# bump the version field in cli/pyproject.toml, then:
git add cli/pyproject.toml
git commit -m "Release grim-cli vX.Y.Z"
git tag cli-vX.Y.Z
git push origin main cli-vX.Y.Z
```

The workflow runs `cli/`'s own test suite before building and publishing,
so a red test suite blocks the release rather than shipping a broken
package. Publishing uses PyPI's trusted publishing (OIDC), not a stored API
token — see the workflow file's header comment for the one-time PyPI-side
setup this needs before the first tag.

