dm1 CLI

Every capability from a shell, with tables for a terminal, JSON for a pipe and exit codes for a script.
npm i -g @cloudraker/milliseconds
export MS_API_KEY=test_sk-...
dm1 check

The package ships the library and the dm1 binary together. Node 20 or later.

dm1 reads the key from MS_API_KEY. Pass --key to override it for one call. Without a key the tool exits 2 and prints the shell answer. Get a key in the console.

Help

dm1 --help
dm1 — typed decisions over text, from milliseconds.ai
USAGE
dm1 <capability> [text] [args...] [options]
<command> | dm1 <capability> [args...]
CAPABILITIES
classify <text> <label[=description]>... Pick one label
yes-no <text> <statement>... True or false, per statement
rate <text> <level>... Place the text on a low-to-high scale
answer <text> <question>... Quote the answer out of the text
entities <text> <type[=description]>... Find every mention, with offsets
extract <text> --schema <file|json> Fill a JSON Schema
verify <text> --field <name> --value <v> Check a value against the text
classify-tree <text> --tree <file|json> Walk a nested label tree
check Test the key. Print the limits.
TEXT
[text] The text itself. Omit it, or pass -, to read stdin.
-f, --file <path> Read the text from a file. Repeat it for a batch, in order.
--lines <path> One text per line. Sent in chunks of 32, in order.
Piped input that starts with { becomes the whole request body. Flags still win.
SPECIFICATION
A positional after the text is a label, statement, level, question or type.
name=description splits on the first =. A bare name has no description.
Any list also loads from a file: @labels.json holds an array or a name-to-description object.
--tree @taxonomy.json --schema @invoice.json @- reads stdin.
--when-true <s> --when-false <s> yes-no hints
--field <name[=description]> --value <v> verify
OPTIONS
--json Print JSON, even on a terminal.
--jsonl One compact JSON result per line. For pipes.
--raw Print the result with its API envelope.
-q, --quiet Print the primary value only.
--check yes-no and verify only. Exit 3 when the answer is no.
--min <p> Exit 3 when probability is below <p>. Not rate or extract.
--min-confidence <c> Exit 3 when confidence is below <c>. classify, rate and
classify-tree only.
--key <key> API key. Default: $MS_API_KEY.
--base-url <url> Default: https://api.milliseconds.ai
--retries <n> Retries on 429, 502, 529 and network errors. Default 2.
--timeout <ms> Per attempt. Default 60000.
-v, --usage Print tokens, model time and rate limits to stderr.
--no-color No ANSI. NO_COLOR and a non-TTY stdout do the same.
-h, --help This text. After a capability, that capability's help.
-V, --version
DESCRIBE YOUR LABELS
The label text is the instruction. The model reads it literally.
dm1 classify "$T" billing shipping works
dm1 classify "$T" billing="charges and refunds" shipping="delivery and tracking"
The second call scores measurably better. Use name=description everywhere.
EXIT CODES
0 decided 2 bad usage
1 API error 3 --check or --min failed
EXAMPLES
dm1 classify "I was charged twice" billing shipping account
dm1 yes-no "Ship it today" "The customer expresses urgency." --check
dm1 rate "This is unacceptable" Calm Annoyed Angry "Threatening to leave"
dm1 entities "Ada met Grace in Paris" person place --json | jq -r '.[].text'
dm1 extract -f invoice.txt --schema @invoice.json
dm1 classify --lines tickets.txt billing shipping account --jsonl > labelled.ndjson
pbpaste | dm1 classify @labels.json
No key yet? https://console.milliseconds.ai then export MS_API_KEY=test_sk-...

dm1 <capability> --help prints that capability’s own help, with its arguments, its result fields and its examples.

Worked examples

Each capability’s own help carries its examples. These are they.

classify
dm1 classify "I was charged twice." billing="charges and refunds" shipping="delivery"
dm1 classify -f ticket.txt @labels.json --json
dm1 classify --lines tickets.txt @labels.json --jsonl | jq -r '.label'
cat ticket.txt | dm1 classify @labels.json --min 0.9 -q
yes-no
dm1 yes-no "Fix this today." "The customer expresses urgency."
dm1 yes-no -f reply.txt "The reply promises a refund." --check --min 0.9 -q
dm1 yes-no -f a.txt -f b.txt "The text mentions a price." --jsonl
rate
dm1 rate "I am done with this company." Calm Annoyed Angry "Threatening to leave"
dm1 rate -f email.txt @scale.json --usage
answer
dm1 answer -f press.txt "Who announced the product?" "How much does it cost?"
curl -s https://example.com/press.txt | dm1 answer - "Who announced the product?"
entities
dm1 entities "Ada met Grace in Paris." person="a human name" place="a city or country"
pbpaste | dm1 entities @types.json --json | jq -r '.[] | select(.type=="person") | .text'
extract
dm1 extract -f invoice.txt --schema @invoice.json --json > invoice.json
dm1 extract -f a.txt -f b.txt --schema @invoice.json --jsonl
verify
dm1 verify -f invoice.txt --field invoice_number="the identifier printed on the invoice" \
--value 4471 --check
dm1 verify -f invoice.txt --field total --value 999 --json | jq .found
classify-tree
dm1 classify-tree -f ticket.txt --tree @taxonomy.json --json
dm1 classify-tree "I want my money back." --tree @taxonomy.json -q

Describe your labels. The label text is the instruction, and the model reads it literally. dm1 classify "$T" billing shipping works. dm1 classify "$T" billing="charges and refunds" shipping="delivery and tracking" scores measurably better. Use name=description everywhere. On a terminal, dm1 classify says so when every label arrives bare.

Any list also loads from a file. @labels.json holds an array or a name-to-description object. --tree @taxonomy.json and --schema @invoice.json take a file, an @file, inline JSON, or @- for stdin.

Where the text comes from

One source only, in this order:

SourceWhat it does
--lines <path>One text per line. A batch, in order.
-f, --file <path>The file’s text. Repeat the flag for a batch, in order.
a pipeAll of stdin. Every positional then belongs to the capability’s list.
[text]The first positional, when nothing is piped in.

A leading - reads stdin explicitly. A trailing newline is a file ending, so dm1 drops it. It would otherwise bill and print.

When a pipe carries the text and the first positional could have carried it too, dm1 writes one line to stderr and names the argument that lost. Pass - to silence it.

Piped JSON becomes the request body

Piped input that starts with { becomes the whole request body. Copy a curl body out of these docs, pipe it in, and add flags:

pbpaste | dm1 classify --json
echo '{"text":"I was charged twice","labels":{"billing":"charges and refunds","shipping":"delivery"}}' \
| dm1 classify

Flags still win over the body’s own keys, so dm1 replaces a single key rather than the request. A yes-no or answer body with no text and no texts answers 200 and {"results": []} at the API. dm1 refuses that body instead, because nothing downstream can tell it apart from a real answer.

Batches and chunking

--lines and repeated --file send the texts in chunks of 32, the API’s batch limit, in order. This is the one place a client chunks for you, and it says so.

dm1 classify --lines tickets.txt @labels.json --jsonl > labelled.ndjson

A streaming mode flushes each chunk as it lands, so a later failure still leaves usable output. When a chunk fails, dm1 names the chunk and the text range on stderr, and exits 1. The chunks before it were billed, so --usage still prints their totals.

Output modes

FlagOutput
none, on a terminalAn aligned table. Colour follows the terminal, --no-color and NO_COLOR.
none, into a pipeCompact JSON, the same as --json.
--jsonJSON, even on a terminal. Indented on a terminal, compact in a pipe.
--jsonlOne compact JSON result per line, with the input text on each line.
--rawThe result with its API envelope, as the wire sends it.
-q, --quietThe primary value only: the label, yes or no, the quoted answer.

--jsonl writes the input text key after the result’s own fields, so an extracted field named text never takes the join key. A capability that returns a list keeps its envelope on the line: {"text": ..., "results": [...]}.

stdout holds results only. -v, --usage prints the tokens, the model time and the rate limits to stderr:

dm1 classify -f ticket.txt @labels.json --usage > out.json
# usage: <chars> chars, <tokens> tokens, <ms> ms inference, <remaining>/<limit> requests left

The requests left value counts inference request units, not HTTP calls. A batch with 24 texts consumes 24 units; text and statement or question counts multiply.

See Limits and rate limits for the limits by key type and production tier.

Exit codes and gates

CodeMeaning
0decided
1API error
2bad usage
3--check or --min failed

Three flags turn a decision into an exit code. The result still prints.

  • --check works with yes-no and verify only. It exits 3 when the answer is no. In a batch, any no fails the run. A gate that verified nothing has not passed, so an empty result also exits 3.
  • --min <p> exits 3 when probability falls below p. rate and extract carry no probability, so they refuse the flag.
  • --min-confidence <c> exits 3 when confidence falls below c. classify, rate and classify-tree carry one.
if dm1 yes-no -f reply.txt "The reply promises a refund." --check --min 0.9 -q; then
echo "promise found"
fi

dm1 check

dm1 check --help
dm1 check [options]
Tests the key and prints the limits. It sends one small classify, about 20 tokens.
Do not run it in a health-check loop.
EXAMPLES
dm1 check
dm1 check --base-url https://api.milliseconds.ai --key test_sk-...

dm1 check proves the key, the endpoint and the limits in one small classify of about 20 tokens. It prints a table on a terminal and JSON in a pipe.

dm1 check
dm1 check --json | jq .rate_limit

Do not run it in a health-check loop. It bills like any other call. See Pricing.

Options

FlagDefault
--key <key>$MS_API_KEY
--base-url <url>https://api.milliseconds.ai
--retries <n>2. Retries on 429, 502, 529 and network errors.
--timeout <ms>60000, per attempt.
--no-colorNO_COLOR and a non-TTY stdout do the same.
-V, --versionPrints the version.