npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

inklate

v0.1.0

Published

Inklate CLI — publish, schedule, preflight, and measure social posts from the terminal. `inklate mcp` runs the same binary as a local stdio MCP server.

Readme

Inklate

The Inklate CLI — create, schedule, validate, and measure social posts from the terminal. The same binary is a local stdio MCP server (inklate mcp).

npm i -g inklate
inklate auth login                  # device flow → org-pinned API key
inklate channels connect x          # browser OAuth, destinations in the terminal
inklate posts create --text "hello" --channels x --scheduled-at now
inklate posts publish <post-id> --wait
  • Thin client, structurally. Every command names a REST operation and the client resolves its verb and path from a table generated off the server's endpoint descriptors — so a command cannot address a route that does not exist.
  • Agent-grade output. Results as YAML on stdout (--json for JSON); progress, prompts, and errors on stderr; typed exit codes.
  • Interactive for humans, never a trap for agents. Prompts only on a TTY without CI/--no-input; otherwise missing flags are named in a usage error and destructive operations require --yes.
  • Rate limits handled. A 429 is retried after the server's Retry-After; a limit that survives the retries exits 7, the one code that means "this will work later".

Install

npm i -g inklate          # or: bunx inklate, pnpm dlx inklate
inklate --help

Requires Node 20+.

Authentication

inklate auth login        # device flow: approve a short code in the browser
inklate auth check        # who am I, and which organizations can I act in?

auth login prints a short user code, opens the browser to approve it, and stores an organization API key (prefix inklate_) in ~/.config/inklate/config.json at mode 0600. The key is org-pinned and revocable under Settings → API keys.

For CI and agents, skip the browser entirely:

export INKLATE_API_KEY=inklate_...   # or: inklate auth set --token inklate_...

Several credentials live side by side as named profiles: inklate --profile staging auth login, then --profile staging on any command.

Output contract

inklate posts list                   # YAML on stdout
inklate posts list --json | jq .     # JSON on stdout
inklate posts list --quiet           # no progress chatter on stderr

stdout carries the result and nothing else. stderr carries progress, prompts, and errors. That split is what makes every command pipeable.

Exit codes

| Code | Name | Meaning | | --- | --- | --- | | 0 | ok | The command succeeded. | | 1 | unexpected | An unhandled or transport-level failure — retry or report it. | | 2 | usage | Bad or missing arguments. Nothing was sent. | | 3 | auth | No stored credential, or it was rejected. Run inklate auth login. | | 4 | refusal | The platform refused on purpose — plan limit, permission, or a preflight verdict. The machine-readable refusal is on stdout. | | 5 | not found | The addressed resource does not exist. | | 6 | conflict | The resource changed underneath you, or its state forbids the action. | | 7 | rate limited | The organization's request budget is exhausted after the client already waited out Retry-After. Safe to re-run later — the message names the wait. |

Exit 4 is not a crash — it is the platform refusing on purpose (a plan limit, a permission, a post that is not ready), and the machine-readable refusal is on stdout for the caller to act on:

inklate posts validate --id <post-id> --json || echo "refused with $?"

Global flags

Accepted by every command:

| Flag | Description | | --- | --- | | --org <value> | Organization slug to act in | | --profile <value> | Named credentials profile (default: default) | | --json | JSON output instead of YAML | | --quiet | Suppress progress output on stderr | | --yes | Skip confirmation prompts | | --no-input | Never prompt (fail instead) | | --base-url <value> | Override the API base URL |

Scheduling times

Scheduling flags take ISO-8601 with an offset, or a relative shorthand anchored to the server clock:

inklate posts update <id> --scheduled-at 2026-07-21T09:00:00-07:00
inklate posts update <id> --scheduled-at 2h          # also: 30m, 3d, 1w, now

A naive local time (2026-07-21T09:00) is rejected with a corrective error — "9am" must never be guessed server-side.

Interactivity

On a TTY, missing arguments prompt (channel pickers come from your real channel list) and destructive commands confirm. In scripts, agents, and CI — no TTY, CI set, or --no-input — the CLI never prompts: it fails naming the exact flag, and destructive commands require --yes.

inklate mcp

Runs the same binary as a local stdio MCP server bridged to your organization. Every remote tool is forwarded unchanged, and files_upload additionally accepts local file paths (the bridge uploads the bytes and returns file ids):

{ "command": "inklate", "args": ["mcp"] }

Point Claude Code, Cursor, or any stdio MCP client at that.

Escape hatch

Any REST endpoint is callable before it has a dedicated command:

inklate api GET /posts
inklate api POST /posts --field content=hello --field 'channel_ids=["<channel-id>"]'
inklate api POST /posts --input body.json

Command reference

auth — Log in and manage stored credentials

inklate auth login

Log in via the browser (device flow) and store an organization API key

inklate auth login [flags]

| Name | Kind | Description | | --- | --- | --- | | --no-browser | flag | Print the URL without opening a browser |

REST: startDeviceAuthorization, pollDeviceAuthorization

inklate auth set

Store an organization API key (paste flow — works everywhere, incl. CI)

inklate auth set [flags]

| Name | Kind | Description | | --- | --- | --- | | --token <value> | flag | The inklate_… API key; prompted for when omitted |

inklate auth check

Verify the stored credentials and show reachable organizations

inklate auth check

inklate auth logout

Remove the stored credentials for this profile

inklate auth logout

organizations — Organizations you can act in

inklate organizations list

List organizations you can act in

inklate organizations list

REST: organizationsList

channels — Connected social channels, their metrics, and growth

inklate channels list

List publishable channels + connection health

inklate channels list

REST: channelsList

inklate channels creator-policy

What a channel's creator account currently allows (TikTok)

inklate channels creator-policy <channel>

| Name | Kind | Description | | --- | --- | --- | | channel | argument | Channel id (or provider name when unambiguous) |

REST: channelsCreatorPolicy

inklate channels connect

Connect a channel: browser OAuth consent, then destination picking

inklate channels connect <provider> [flags]

| Name | Kind | Description | | --- | --- | --- | | provider | argument | Provider key (the connectable set is listed by inklate capabilities list) | | --destinations <value> | flag | Which destinations to connect: "all", or comma-separated external ids | | --no-browser | flag | Print the URL without opening a browser | | --timeout <value> | flag | Seconds to wait |

REST: connectionsCreate, connectionsCreateWithCredentials, connectionsGet, connectionsDestinations, connectionsSelect

inklate channels disconnect

Disconnect a channel (its scheduled placements stop publishing)

inklate channels disconnect <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Channel id |

REST: channelsDisconnect

inklate channels metrics

Aggregated per-day engagement for one channel

inklate channels metrics <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Channel id (or provider name when unambiguous) | | --from <value> | flag | ISO-8601 with offset | | --to <value> | flag | ISO-8601 with offset |

REST: channelsMetrics

inklate channels growth

Followers trajectory + per-day page flows for one channel

inklate channels growth <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Channel id (or provider name when unambiguous) | | --from <value> | flag | ISO-8601 with offset | | --to <value> | flag | ISO-8601 with offset |

REST: channelsGrowth

capabilities — Per-channel capabilities + provider manifests

inklate capabilities

Per-channel effective capabilities + provider manifests

inklate capabilities

REST: capabilitiesList

posts — Create, validate, schedule, publish, and measure posts

inklate posts create

Create a post — draft, scheduled, or published now, in one call

inklate posts create [flags]

| Name | Kind | Description | | --- | --- | --- | | --content <value> | flag | Post copy as markdown. Text with no markup posts exactly as typed; on a thread each PARAGRAPH becomes an item. Use * for a literal delimiter. | | --channel <value> | flag | Repeatable: channel id or provider name (x, linkedin, …) (repeatable) | | --scheduled-at <value> | flag | Schedule: "now", ISO-8601 with offset, or "30m"/"2h"/"3d"/"1w"; omit to save a draft | | --per-channel <value> | flag | Per-channel overrides as JSON: {"x": {"content"?, "format"?, "options"?}}. content takes markdown: --per-channel '{"x": {"content": "shorter text"}}' | | --autofix | flag | Apply suggested machine fixes server-side (--no-autofix to disable) | | --media <value> | flag | Repeatable: file id (file_…), public https URL, or local file path (repeatable) | | --label <value> | flag | Repeatable: label id to apply (repeatable) | | --campaign <value> | flag | Campaign id to attach | | --idempotency-key <value> | flag | Dedupe key: retries replay, not duplicate |

REST: postsCreate

inklate posts list

List posts with their per-channel status

inklate posts list [flags]

| Name | Kind | Description | | --- | --- | --- | | --status <value> | flag | Repeatable status filter (repeatable) | | --scheduled-from <value> | flag | ISO-8601 with offset | | --scheduled-to <value> | flag | ISO-8601 with offset | | --campaign <value> | flag | Only posts filed under this campaign id | | --label <value> | flag | Only posts carrying this label id | | --channel <value> | flag | Only posts targeting this channel — id or provider name (x, linkedin) | | --include <value> | flag | Repeatable: extra per-row field to compute — ready fit-checks each row (repeatable) | | --limit <value> | flag | Rows per page (default 100) | | --cursor <value> | flag | next_cursor from a prior page |

REST: postsList

inklate posts get

The post, its per-channel validation, and live publish results

inklate posts get <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Post id |

REST: postsGet

inklate posts update

Change anything — content, channels, media, labels, campaign, or the schedule

inklate posts update <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Post id | | --content <value> | flag | New copy as markdown (replaces the current copy) | | --channel <value> | flag | Repeatable: channel id or provider name — REPLACES the target set (repeatable) | | --scheduled-at <value> | flag | (Re)schedule: "now", ISO-8601 with offset, or "30m"/"2h"/"3d"/"1w" | | --clear-schedule | flag | Return the post to draft | | --per-channel <value> | flag | Per-channel overrides as JSON; content takes markdown; {"x": null} resets that channel to the shared content | | --autofix | flag | Apply suggested machine fixes server-side (--no-autofix to disable) | | --media <value> | flag | File ids, https URLs, or local paths (repeatable) | | --label <value> | flag | Repeatable: replace the post's labels (repeatable) | | --campaign <value> | flag | Campaign id to attach | | --detach-campaign | flag | Detach the post from its campaign | | --expected-updated-at <value> | flag | Optimistic-concurrency guard from the last read | | --idempotency-key <value> | flag | Dedupe key: retries replay, not duplicate |

REST: postsUpdate

inklate posts validate

Dry-run validation — exit 4 when the post is not ready, nothing stored

inklate posts validate [id] [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Validate a STORED post (media readiness included) (optional) | | --content <value> | flag | Inline draft copy as markdown | | --channel <value> | flag | Repeatable: channel id or provider name to fit-check against (repeatable) | | --per-channel <value> | flag | Per-channel overrides as JSON | | --media <value> | flag | File ids or https URLs (no local paths) (repeatable) |

REST: postsValidate

inklate posts delete

Delete a post (--takedown also removes it from the platforms)

inklate posts delete <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Post id | | --takedown | flag | Best-effort delete the provider-side copies first |

REST: postsDelete

inklate posts publish

Publish an existing draft now (--wait polls to a terminal state)

inklate posts publish <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Post id | | --wait | flag | Poll until published/failed | | --expected-updated-at <value> | flag | Refuse (exit 6) if the post changed since this read, from posts get | | --idempotency-key <value> | flag | Dedupe key: retries replay, not republish |

REST: postsPublish

inklate posts metrics

Per-channel totals + snapshot time series for one post

inklate posts metrics <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Post id |

REST: postsMetrics

files — Upload and manage the files a post attaches

inklate files upload

Upload local files (presign → PUT → poll) and print reusable file ids

inklate files upload <paths> [flags]

| Name | Kind | Description | | --- | --- | --- | | paths | argument | 1–10 local files (jpg/jpeg/png/gif/webp/mp4/mov) | | --idempotency-key <value> | flag | Dedupe key for the whole batch | | --no-wait | flag | Return after the PUTs without polling readiness |

REST: filesUpload, filesList

inklate files get

Get a file (readiness, facts, variants; optionally a signed URL)

inklate files get <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | File id (file_…) | | --include-url | flag | Include a signed GET URL (600s) | | --variant <value> | flag | Signed URL for a named variant |

REST: filesGet

inklate files list

List files (keyset-paginated, newest first)

inklate files list [flags]

| Name | Kind | Description | | --- | --- | --- | | --readiness <value> | flag | Filter | | --kind <value> | flag | Filter | | --limit <value> | flag | | | --cursor <value> | flag | next_cursor from the previous page |

REST: filesList

inklate files delete

Delete a file (refused while attached to a post)

inklate files delete <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | File id (file_…) |

REST: filesDelete

search — Full-text search over your content

inklate search

Full-text search over your own content

inklate search <query> [flags]

| Name | Kind | Description | | --- | --- | --- | | query | argument | Search query | | --kinds <value> | flag | Comma-separated kinds filter | | --limit <value> | flag | | | --cursor <value> | flag | |

REST: searchContent

interactions — Ingested comments and mentions

inklate interactions list

Ingested comments and mentions across channels

inklate interactions list [flags]

| Name | Kind | Description | | --- | --- | --- | | --kinds <value> | flag | Comma-separated kinds (comment, mention, …) | | --channel <value> | flag | Channel id (or provider name when unambiguous) | | --since <value> | flag | ISO-8601 with offset — only newer interactions | | --limit <value> | flag | |

REST: interactionsList

inklate interactions reply

Reply to an ingested comment or mention (1 Ink)

inklate interactions reply <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Interaction id from inklate interactions list | | --body <value> | flag | The reply text, posted as the channel (required) |

REST: interactionsReply

webhooks — Webhook endpoints and deliveries

inklate webhooks list

List webhook endpoints

inklate webhooks list

REST: webhooksList

inklate webhooks create

Register a webhook endpoint (the signing secret is shown once)

inklate webhooks create <url> [flags]

| Name | Kind | Description | | --- | --- | --- | | url | argument | HTTPS endpoint to deliver events to | | --event-types <value> | flag | Comma-separated event types, e.g. "post.published,token.expired" (required) | | --description <value> | flag | What this endpoint is for |

REST: webhooksCreate

inklate webhooks delete

Delete a webhook endpoint

inklate webhooks delete <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Webhook endpoint id |

REST: webhooksDelete

inklate webhooks rotate-secret

Rotate a webhook signing secret (the old secret stops verifying)

inklate webhooks rotate-secret <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Webhook endpoint id |

REST: webhooksRotateSecret

inklate webhooks deliveries

Recent deliveries for a webhook endpoint

inklate webhooks deliveries <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Webhook endpoint id |

REST: webhooksDeliveries

campaigns — Campaign grouping and rollups

inklate campaigns list

List campaigns

inklate campaigns list

REST: campaignsList

inklate campaigns create

Create a campaign

inklate campaigns create <name> [flags]

| Name | Kind | Description | | --- | --- | --- | | name | argument | Campaign name | | --status <value> | flag | Initial status (default: server-side default) | | --starts-at <value> | flag | ISO-8601 with offset | | --ends-at <value> | flag | ISO-8601 with offset |

REST: campaignsCreate

inklate campaigns metrics

Roll up a campaign's engagement

inklate campaigns metrics <id>

| Name | Kind | Description | | --- | --- | --- | | id | argument | Campaign id |

REST: campaignsMetrics

approvals — Approval rules, requests, and decisions

inklate approvals rule

Show the organization's approval rule

inklate approvals rule

REST: approvalsRule

inklate approvals set-rule

Set the organization's approval rule

inklate approvals set-rule <policy> [flags]

| Name | Kind | Description | | --- | --- | --- | | policy | argument | Rule policy: single, sequential, or parallel | | --approvers <value> | flag | Comma-separated approver user ids | | --quorum <value> | flag | Approvals required | | --disabled | flag | Turn the rule off |

REST: approvalsSetRule

inklate approvals requests

List approval requests

inklate approvals requests

REST: approvalsRequests

inklate approvals request

Submit a post for approval

inklate approvals request <post>

| Name | Kind | Description | | --- | --- | --- | | post | argument | Post id |

REST: approvalsRequest

inklate approvals decide

Approve or reject an approval request

inklate approvals decide <id> [flags]

| Name | Kind | Description | | --- | --- | --- | | id | argument | Approval request id | | --approve | flag | Approve the request | | --reject | flag | Reject the request | | --comment <value> | flag | Reviewer comment |

REST: approvalsDecide

audit-logs — The organization's audit trail

inklate audit-logs list

Read the audit trail (admin)

inklate audit-logs list [flags]

| Name | Kind | Description | | --- | --- | --- | | --action <value> | flag | Filter by action name | | --resource-id <value> | flag | Filter by resource id | | --limit <value> | flag | |

REST: auditLogsList

api — Call any REST endpoint directly

inklate api

Examples: inklate api GET /posts, inklate api POST /posts --field content=hello --field channel_ids='["<channel-id>"]', inklate api POST /posts --input body.json.

inklate api <method> <path> [flags]

| Name | Kind | Description | | --- | --- | --- | | method | argument | HTTP method | | path | argument | Endpoint path, e.g. /posts | | --field <value> | flag | Body field as key=value (value parsed as JSON when it looks like it) (repeatable) | | --query <value> | flag | Query param as key=value (repeatable) | | --input <value> | flag | Read the JSON body from a file (- for stdin) |

mcp — Run a local stdio MCP server bridged to your organization

inklate mcp

Run a local stdio MCP server bridged to the remote Inklate MCP

inklate mcp

completions — Shell completion scripts

inklate completions

bash: source <(inklate completions bash) · zsh: inklate completions zsh > "${fpath[1]}/_inklate" · fish: inklate completions fish > ~/.config/fish/completions/inklate.fish

inklate completions <shell>

| Name | Kind | Description | | --- | --- | --- | | shell | argument | Target shell |


Development

Lives in the Inklate monorepo at packages/cli.

bun run --filter inklate build           # bundle with tsdown (workspace deps inlined)
bun run --filter inklate docs:generate   # regenerate this README + the docs reference
bun run --filter server sdk:generate     # regenerate the SDK operation table

This file and the per-command pages under apps/docs/content/docs/cli/ are generated from the command registry (src/registry.ts) and drift-tested byte for byte, so they cannot fall behind the binary. Releases ride changesets → .github/workflows/release-npm.yml with npm provenance; the procedure is docs/runbook.md § "Releasing the CLI to npm".

Full documentation: https://docs.inklate.com/cli