---
title: "CLI"
description: "The hashes command. Hash and HMAC and verify from a shell or stdin with the digest alone on stdout"
canonical_url: "https://hashes.agntn.dev/guide/cli"
---
# CLI

> The hashes command. Hash and HMAC and verify from a shell or stdin with the digest alone on stdout

## Install or run once

```bash
npx @agntn/hashes sha256 "hello world"
pnpm add -g @agntn/hashes   # then plain `hashes`
```

## Commands

<table>
<thead>
  <tr>
    <th>
      Command
    </th>
    
    <th>
      What it does
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        hash
      </code>
    </td>
    
    <td>
      Digest of text, or of stdin with <code>
        -
      </code>
    </td>
    
    <td>
      <code>
        hashes hash blake3 - < file.bin
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hmac
      </code>
    </td>
    
    <td>
      Keyed digest
    </td>
    
    <td>
      <code>
        hashes hmac sha256 message secret
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        verify
      </code>
    </td>
    
    <td>
      Compare with an expected digest, exit 1 if not
    </td>
    
    <td>
      <code>
        hashes verify md5 hello 5d41402abc4b2a76b9719d911017c592
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        extend
      </code>
    </td>
    
    <td>
      <a href="https://hashes.agntn.dev/guide/extend">
        Length extension
      </a>
      
      , a digest and a message per secret length
    </td>
    
    <td>
      <code>
        hashes extend md5 <digest> --message user=guest --suffix x --secret-length 3
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        identify
      </code>
    </td>
    
    <td>
      <a href="https://hashes.agntn.dev/guide/identify">
        Candidates
      </a>
      
       for a hash, by prefix or length
    </td>
    
    <td>
      <code>
        hashes identify '$2b$10$...'
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        search
      </code>
    </td>
    
    <td>
      <a href="https://hashes.agntn.dev/guide/search">
        The words and the recipe
      </a>
      
       behind a digest, exit 1 if none
    </td>
    
    <td>
      <code>
        hashes search <digest> alpha beta --rounds 2
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        algorithms
      </code>
      
      , <code>
        info
      </code>
    </td>
    
    <td>
      The list, <code>
        -f
      </code>
      
       keeps one family, <code>
        -c
      </code>
      
       one category. Name one and you get its options
    </td>
    
    <td>
      <code>
        hashes info pbkdf2
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mcp
      </code>
    </td>
    
    <td>
      The MCP server on stdio
    </td>
    
    <td>
      <code>
        hashes mcp
      </code>
    </td>
  </tr>
</tbody>
</table>

`hash` is the default, so the first word can be the algorithm. `hashes md5 hello` and `hashes hash md5 hello` are the same call. No words at all? You get the list.

Every command comes from the same tool definitions `hashes mcp` serves, so its flags are the schema's properties and its help is the schema's descriptions. `extend` and `algorithms` print the very text a model reads. The rest are the CLI's own. Why? A shell wants raw bytes in and out, the digest alone and a search that runs for minutes. A model wants none of that. Want data instead of text? Every command but `hash`, `hmac` and `mcp` takes `--json`:

```bash
hashes extend md5 <digest> --message m --suffix x --secret-length 3 --json | jq -r '.extensions[0].message'
```

## Stdout is the digest

Only the digest goes to stdout, so it pipes and it lands in a variable without trimming:

```bash
digest=$(hashes sha256 abc)
echo -n abc | hashes sha256 -
# ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
```

A KDF's salt and cost go to stderr, since the digest is useless without them. `-e binary` writes the raw bytes, for `xxd` or the next tool in the pipe. Stdin is read byte for byte, so a file hashes the same as `sha256sum` would hash it.

## Several at once

One input gets its digest alone. Two or more get `sha256sum` lines, the same ones `hashes_compute` hands a model:

```bash
hashes sha256 "hello world" foo
# 2 inputs, sha256, hex, 32 bytes
# b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9  "hello world"
# 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae  "foo"
```

The heading goes to stderr and the lines to stdout. Your quotes count: `"hello world"` is one input, not two. Each input is printed in JSON quotes. A line feed inside one can't fake a line of its own.

`hmac` and `verify` want the key or the expected digest last, the way `cp` wants its target. So `hashes hmac sha256 message secret` means what it always did. More texts just go before the key:

```bash
hashes verify md5 nope hello world 5d41402abc4b2a76b9719d911017c592
# MATCH: 1 of 3 inputs gives the expected md5 digest
# expected 5d41402abc4b2a76b9719d911017c592
# mismatch  4101bef8794fed986e95dfb54850c68b  "nope"
# match     5d41402abc4b2a76b9719d911017c592  "hello"
# mismatch  7d793037a0760186574b0282f2f435e7  "world"
```

Which guess was the password? Now you know. It exits 1 only when none of them is.

`identify` takes several hashes and prints a block for each. A KDF draws a salt per input and puts it on that input's line. `-` is still stdin, once per call, because stdin doesn't refill. And `-e binary` wants a single input. Raw digests in a row have nothing between them.

## Flags

```bash
hashes hash --help
```

`-e, --encoding` picks the output: `hex` (default), `base64`, `base64url`, `binary`. `--input-encoding` picks how the input is read: `utf8` (default), or `hex` and `base64` for the bytes they spell. `--key-encoding` does the same for the HMAC key.

```bash
hashes hash160 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input-encoding hex
# 751e76e8199196d454941c45d1b3a323f1433bd6
```

Without the flag that same line hashes 66 characters of text, which is a valid digest of the wrong thing. The flag also applies to stdin.

The rest of the flags come from what the algorithms declare in `info().options`: `--salt`, `--n`, `--r`, `--p`, `--key-length`, `--iterations`, `--digest` for the KDFs, `--seed` for xxHash, `--passes` and `--bits` for HAVAL, `--rounds` and `--chain` for every fixed-length digest. Of these, `hmac` takes only `--passes` and `--bits`. HMAC runs one round. Each one is the option's name in kebab case, so `keyLength` turns into `--key-length` and scrypt's capital `N` into a humble `--n`. The help text names the algorithms that take each one, or the few that don't. `--bits` means one thing to HAVAL and another to xxHash. The help gives both. A flag the algorithm doesn't declare is an error, not a digest computed without it:

```bash
hashes xxhash abc --seed 1
# bea9ca8199328908
hashes sha256 abc --rounds 2 --chain hex
# dfe7a23fefeea519e9bbfdd1a6be94c4b2e4529dd6b7cbea83f9959c2621b13c
hashes sha256 abc --seed 1
# Invalid option seed=1: sha256 takes rounds, chain
```

A flag no algorithm has is refused before anything gets hashed, with the whole list of what the command takes. Typos don't sneak through:

```bash
hashes sha256 abc --sead 1
# Invalid arguments: unknown option "--sead"; takes --input-encoding, --encoding (-e), --rounds, --chain, ...
```

## Exit codes

`0` for a digest, and for `verify` a match. `1` for a mismatch and for an `identify` or `search` that comes back empty. A list follows suit: `1` when one input fails, when no candidate matches, or when one hash fits nothing. Also `1` for any error in the input: an unknown name, a bad option, a missing salt. The error is one line on stderr, with no stack trace:

```bash
hashes sha265 abc
# Unknown algorithm: sha265. Available: sha256, sha384, sha512, ...
hashes CRC-32/BZIP2 abc
# Unknown algorithm: CRC-32/BZIP2. Did you mean crc32 with variant=bzip2?
```

So it's `hashes crc32 abc --variant bzip2`, and out comes `648cbb73`. Right name, one flag away.

A pipe that closes early (`| head -1`) ends the process quietly instead of throwing `EPIPE` at you. There are no colours at all, so a pipe, a file or an agent's transcript never gets escape codes.


## Sitemap

See the full [sitemap](https://hashes.agntn.dev/sitemap.md) for all pages.
