Impri CLI
Status — v0.1, pre-release. The CLI lives at
cli/in this repository and is not yet published to npm. Install it from the local source using the build steps below. The REST API it wraps is stable; the hosted cloud (api.impri.dev) is early beta.
The impri CLI is the fastest way to manage the approval inbox from a terminal — push actions, watch for new ones, approve or reject, and manage watchers and API keys — without writing any code.
Install (local, pre-npm)
The CLI depends on the TypeScript SDK via a file: reference. Build the SDK first:
cd sdk/typescript && npm install && npm run build
cd ../cli && npm install && npm run buildInstall globally to put impri on your PATH:
npm install -g ./cli
impri --versionThe build inlines the SDK into a single
dist/cli.js(shebang included). A global install requires nonode_modules/at runtime — only Node 18+.
Dev mode (no build step, for iterating on the CLI source):
cd cli && npx tsx src/cli.ts inboxOnboarding
Run impri init once to store your base URL and API key:
# Self-hosted (default)
impri init
# Cloud — connect with an existing key
impri init --cloud
# Cloud — create a new free project and key in one step
impri init --cloud --signup
# Seed two sample actions so the inbox is not empty on first run
impri init --cloud --signup --demoinit calls GET /v1/project to validate the key, then writes ~/.impri/config.json (mode 0600). The interactive flow:
- Prompt for base URL (default:
http://localhost:8484). - Prompt for API key (
im_...) with masked input. - Validate the key against the server.
- Write the config file.
- Print the connected project name.
With --cloud --signup, the CLI calls POST /v1/signup, prints the returned im_... key once inside a box border with a "store this now" warning, pauses for confirmation, then saves.
Config and credentials
File: ~/.impri/config.json
Directory created as 0700; file written as 0600 on every save.
{
"base_url": "https://api.impri.dev",
"api_key": "im_xxxxxxxxxxxxxxxxxxxxxxxx"
}Precedence (highest wins):
| Source | How to set |
|---|---|
| Environment | IMPRI_API_KEY, IMPRI_BASE_URL |
| Config file | ~/.impri/config.json (written by impri init) |
| Code default | http://localhost:8484; no key → commands print "Run 'impri init' first" and exit 1 |
Environment variables take effect immediately without touching the config file — suitable for CI, Docker, and per-process overrides:
IMPRI_BASE_URL=http://staging.internal:8484 \
IMPRI_API_KEY=im_stg_... \
impri inboxSecurity notes:
- The key is never logged, never printed back in full, and never echoed during interactive input (masked with
*). - Any
--verboseor debug output redacts the key to its prefix only:im_XXXX****. - A missing config file produces a friendly error, not a stack trace.
Commands
impri init
impri init [--base-url <url>] [--cloud] [--signup] [--demo]Interactive onboarding. Prompts for base URL and API key, validates credentials, and writes ~/.impri/config.json.
To supply the API key non-interactively, pipe it on stdin or set the IMPRI_API_KEY environment variable. The env var takes precedence over the prompt.
| Flag | Description |
|---|---|
--cloud |
Set base URL to https://api.impri.dev and skip the URL prompt |
--signup |
Call POST /v1/signup (cloud + unauthenticated) to create a free project and print the im_... key once |
--demo |
After connecting, seed two sample actions (demo.email, demo.publish) so the inbox has content |
--base-url <url> |
Skip the URL prompt |
impri login
impri login [--base-url <url>] [--cloud]Alias for impri init. Re-run onboarding to rotate your key or switch between self-hosted and cloud without wiping other settings. Supply the key via the interactive prompt, piped stdin, or IMPRI_API_KEY env var.
impri status
impri status [--json]Verify the active config by calling GET /healthz and GET /v1/project. Prints:
- Connected project name
- Base URL
- Key prefix (
im_xxxx****) - Current pending action count
Exits 1 if the server is unreachable or the key is invalid.
Approval Inbox
impri push
impri push --kind <kind> --title <title> [options]Create an action for human approval (POST /v1/actions). Preview body is read from --body or stdin when omitted, enabling pipe usage.
| Flag | Description |
|---|---|
--kind <kind> |
Action kind, e.g. email.send, db.exec, reddit.comment |
--title <title> |
Short description shown in the inbox card |
--body <text> |
Preview body text (or pipe from stdin) |
--format <plain|markdown|diff> |
Preview format (default: plain) |
--editable preview.body |
Allow the reviewer to edit the body before approving |
--target-url <url> |
Link to the resource being acted on |
--expires-in <seconds> |
Approval window (default: 259200 = 72 h; min: 300; max: 2592000) |
--wait |
Poll until decided; exit 0 on approval, exit 1 on rejection |
--timeout <seconds> |
Timeout for --wait (default: 300) |
--json |
Print raw ActionCreated JSON |
Examples:
# Push a draft email for review
impri push --kind email.send \
--title "Draft: Q3 newsletter" \
--body "Hi everyone, ..." \
--format markdown \
--editable preview.body
# Read preview from stdin
echo "$QUERY" | impri push --kind db.exec --title "Run migration"
# Push and block until the human decides
impri push --kind reddit.comment \
--title "Reply: self-hosting AI" \
--body "$(cat reply.md)" \
--wait --timeout 600impri inbox
impri inbox [--kind <kind>] [--limit <n>] [--json]Show only pending actions — shorthand for impri list --status pending. This is the primary human operator view.
impri inbox
impri inbox --kind email.sendimpri list
impri list [--status <pending|approved|rejected|expired>] [--kind <kind>] \
[--since <iso-date>] [--q <search>] [--limit <n>] [--json]List actions newest-first in a tabular view (id, kind, title, status, age). Defaults to all statuses.
| Flag | Description |
|---|---|
--status |
Filter by status |
--kind |
Filter by action kind |
--since <iso-date> |
ISO-8601 date string, e.g. 2026-07-01 (converted internally to a Unix timestamp) |
--q <search> |
Free-text search on title and preview body (max 200 chars) |
--limit <n> |
Max results (default: 50) |
--json |
Newline-delimited JSON records for scripting |
impri list --status approved --since 2026-07-01
impri list --q "migration" --json | jq .idimpri get <action-id>
impri get <action-id> [--json]Fetch a single action and print full detail: status, preview, editable fields, expires_at, and the decision (including final_preview and diff) when already decided.
impri get act_abc123impri approve <action-id>
impri approve <action-id> [--edit <new-body-text>] [--json]Approve an action (POST /v1/actions/:id/decision, verdict=approve).
--edit replaces the preview body before approving. The CLI fetches the action first and exits with an error if preview.body is not in the action's editable list.
impri approve act_abc123
# Approve with an edited body
impri approve act_abc123 --edit "$(cat edited-reply.md)"Exits 0 on success.
impri reject <action-id>
impri reject <action-id> [--json]Reject an action (POST /v1/actions/:id/decision, verdict=reject). Prints the DecisionResult.
Exits 0 on success; exits 2 on ImpriConflict (already decided).
impri tail
impri tail [--kind <kind>] [--interval <seconds>] [--json]Long-running watch mode for the human operator. Polls GET /v1/actions?status=pending every --interval seconds (default: 10, minimum: 5 to stay under rate limits) and prints newly-arrived pending actions as they appear. Tracks the latest created_at seen to avoid reprinting.
Press Ctrl-C to exit.
# Terminal 1 — tail the inbox
impri tail
# Terminal 2 — decide on actions printed by tail
impri approve act_xyz789With --json, each new action is streamed as a JSON line — useful for piping into other tools.
Watchers
impri presets
impri presets [--category <Community|Developer|Content|Research|News|Monitoring>] [--json]List the 18 watcher preset templates (GET /v1/watcher-presets) in a grouped table showing preset ID, description, required params, and default schedule. Use the preset ID with impri watch add.
impri presets
impri presets --category DeveloperAvailable presets:
| Category | Preset IDs |
|---|---|
| Community | hn-front-page, hn-keyword, hn-show-ask, reddit-subreddit, reddit-keyword |
| Developer | github-releases, github-commits, npm-package, pypi-package, stackoverflow-tag |
| Content | rss-feed, blog-newsletter, youtube-channel |
| Research | arxiv-papers |
| News | google-news, product-hunt |
| Monitoring | url-changed, changelog-status |
impri watch add <preset-id>
impri watch add <preset-id> [--param key=value]... [--name <name>] [--schedule <every>] [--json]Create a watcher from a named preset (POST /v1/watchers/from-preset). --param is repeatable.
# GitHub releases for fastify/fastify, polled hourly
impri watch add github-releases \
--param owner=fastify \
--param repo=fastify \
--schedule 1h
# Reddit keyword search with a custom name
impri watch add reddit-keyword \
--param "query=self-hosting AI" \
--param subreddit=selfhosted \
--name "SH AI mentions"
# Hacker News front page (no params required)
impri watch add hn-front-pagePrints the created watcher ID and next_run_at on success.
impri watchers list
impri watchers list [--status <active|paused|degraded>] [--kind <rss|reddit_search|url_diff>] [--json]List all watchers with status, kind, schedule, fail_count, and next_run_at.
impri watchers get <watcher-id>
impri watchers get <watcher-id> [--json]Fetch a single watcher by ID, including item_count (total deduplicated items seen since creation).
impri watchers delete <watcher-id>
impri watchers delete <watcher-id> [--yes]Permanently delete a watcher and its deduplication history. Pending inbox actions created by this watcher are not deleted.
--yes skips the interactive confirmation prompt — safe for scripting.
API Keys
All key commands require admin scope.
impri keys list
impri keys list [--json]List all API keys: prefix, scopes, created_at, last_used_at, and revocation status. Raw key values are never returned by the server after creation.
impri keys create
impri keys create --name <name> --scopes <actions|watch|admin>[,...]Create a new API key (POST /v1/keys) and print the raw im_... value exactly once. The CLI displays the key prominently and warns it will not be shown again.
# Per-agent key with minimal scope
impri keys create --name "deploy-agent" --scopes actions
# Ops bot that can also manage watchers
impri keys create --name "ops-bot" --scopes actions,watchimpri keys revoke <key-id>
impri keys revoke <key-id> [--yes]Revoke a key permanently (DELETE /v1/keys/:id). All subsequent requests with that key will be rejected with 401.
--yes skips the confirmation prompt.
Human operator workflow
The typical setup for monitoring an active agent:
# Terminal 1 — watch for new pending actions
impri tail
# Terminal 2 — inspect and decide on a specific action
impri get act_abc123
impri approve act_abc123
# or
impri reject act_abc123For a lighter setup, poll the inbox on a loop:
watch -n 30 impri inboxInspect then edit a draft before approving:
impri get act_abc123 # read the preview
$EDITOR /tmp/reply.md # edit locally
impri approve act_abc123 --edit "$(cat /tmp/reply.md)"Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (server unreachable, invalid key, action rejected on --wait) |
| 2 | Conflict — action already decided |
Build reference
cli/
src/cli.ts Entry point — subcommands registered with commander
package.json name: @impri/cli, version: 0.1.0, bin: { impri: ./dist/cli.js }
tsup.config.ts Bundles SDK inline via noExternal: [/@impri/]; shebang injected
dist/cli.js Compiled output (generated by npm run build)Build sequence for a clean checkout:
cd sdk/typescript && npm install && npm run build
cd ../cli && npm install && npm run build
npm install -g ./cliThe bundle carries the shebang so the OS can execute dist/cli.js directly. No node_modules/ needed at runtime beyond Node 18.
See also:
- TypeScript SDK reference — for agents that push and poll programmatically
- Quickstart — zero to first approved action in < 5 min
- Watcher presets — full preset catalog with param reference
- Inbox UX — keyboard shortcuts, bulk approve/reject, filters (web inbox)