Skip to main content

CLI

Use the CLI when you want to post from a terminal, script, CI job, local automation, or an AI coding agent that can run shell commands.

The CLI has two ways to reach social accounts:

  1. Local CLI accounts — credentials live on the machine and posting calls the SDK directly. OAuth platforms use your own developer app credentials through SIMPLE_POST_<PLATFORM>_CLIENT_ID and, where required, _CLIENT_SECRET. Telegram uses a bot token and chat ID instead.
  2. Scheduler-connected accounts — the CLI authorizes against a running Scheduler app and uses accounts the user has already connected there from the interactive posting flow. The Scheduler app keeps the raw social tokens; the CLI never sees them.

You can use both at the same time: locally stored accounts and Scheduler-connected accounts appear together in the interactive picker. Non-interactive --account <platform>:<alias> posting targets local CLI accounts.

Install

Install the public npm package:

npm install -g @simple-post/cli
simplepost --help

To run a development build from a checkout of the core repo:

yarn workspace @simple-post/cli build
node cli/bin/run.js --help

The examples below use simplepost. Inside the repo, replace it with node cli/bin/run.js.

Configure secret storage

The CLI needs to know how to store secrets before any account command runs.

simplepost setup --backend keychain
BackendBest forRequires
keychainDeveloper machinesOS keychain (macOS Keychain, Windows Credential Manager, libsecret)
file-encryptedScripts, CI, headless serversA password supplied through SIMPLE_POST_CONFIG_PASSWORD, or an interactive prompt
file-plainLocal testing onlyNothing — secrets are stored unencrypted

For non-interactive setup (CI, Docker), set SIMPLE_POST_CONFIG_PASSWORD first, then run simplepost setup --backend file-encrypted. Without the env var the CLI will prompt and the script will hang.

Local CLI accounts

OAuth providers

Register your own developer app and export its client ID (and client secret when required) before connecting:

simplepost account add x --alias main
simplepost account add youtube --alias channel
simplepost account add bluesky --alias personal

Each command opens a loopback browser flow on port 5000. The browser returns an authorization code, the CLI exchanges it directly with the platform, stores the resulting credentials through your chosen backend, and the account appears under simplepost account.

If port 5000 is occupied, use --callback-port 6123 or set SIMPLE_POST_CALLBACK_PORT=6123. Register the resulting loopback URI exactly in the platform developer app. SIMPLE_POST_<PLATFORM>_REDIRECT_URI remains available when the complete redirect must be overridden and takes precedence over the shared port.

For Telegram, no OAuth is involved:

simplepost account add telegram \
--alias announcements \
--bot-token "$TELEGRAM_BOT_TOKEN" \
--chat-id "@channel"

X can use a public PKCE client without a secret. Facebook and Instagram use public Meta apps. YouTube, TikTok, Threads, LinkedIn, and Pinterest require a client secret. Bluesky uses the URL of your hosted atproto client metadata as its client ID.

The provider guides under Platform setup describe what to enable in each developer dashboard before running account add.

Scheduler-connected accounts

When a user has already connected accounts in the Scheduler app, the CLI can use those without ever holding the raw social tokens:

simplepost connect --url https://app.simplepost.social
simplepost account
simplepost post --interactive

The browser flow authorizes the CLI on the Scheduler and returns a short-lived, single-use code to the loopback listener. The CLI exchanges that code directly for a 90-day SimplePost CLI token, so the bearer token never appears in browser history. The CLI stores it in the configured secret store. After connect, Scheduler-connected accounts appear alongside local accounts in the interactive posting flow.

For CI or another non-interactive environment that only needs to establish the Scheduler connection, pass a token directly:

SIMPLE_POST_CLI_TOKEN="sp_cli_..." simplepost connect --url https://schedule.example.com

Self-hosted Scheduler:

simplepost connect --url https://schedule.example.com

Replace schedule.example.com with your deployment hostname.

When posting through Scheduler accounts, media must be reachable through a public URL — Scheduler does not pull bytes from local CLI files. For fully offline-friendly local-file posting, use local CLI accounts instead.

For non-interactive Scheduler posting, get target IDs from simplepost account, then repeat --app-account-id <id> on simplepost post.

Posting

Interactive (the default when no flags are passed):

simplepost post

Non-interactive across multiple stored accounts:

simplepost post \
--account x:main \
--account telegram:announcements \
--text "Hello from SimplePost CLI" \
--image ./image.png \
--telegram-chat-id "@channel"

Repeat --account to target multiple stored accounts. The format is <platform>:<alias>.

Pass a full SDK Post payload as JSON:

simplepost post --post-json ./post.json --account x:main
{
"content": {
"text": "Launch day",
"media": [{ "type": "image", "url": "https://cdn.example.com/image.jpg" }]
},
"platforms": ["x"]
}

Merge platform options with --options-json:

simplepost post \
--account x:main \
--text "Replying from the CLI" \
--options-json '{"x":{"replyToId":"1234567890"}}'

Useful flags

The simplepost post command groups flags by purpose. The most common ones:

GroupFlags
Workflow--interactive
Targets--account (repeatable, <platform>:<alias>), --app-account-id (repeatable)
Content--text
Media--image, --video, --media-json
JSON input--post-json, --options-json
Advanced--strict-mode, --log-level
X--x-reply-to-id
Telegram--telegram-chat-id, --telegram-parse-mode
YouTube--youtube-tags, --youtube-category-id, --youtube-playlist-id, --youtube-made-for-kids, --youtube-publish-at, --youtube-privacy-status
Facebook--facebook-publish-at
TikTok--tiktok-publish-mode, --tiktok-visibility, --tiktok-allow-comment, --tiktok-allow-duet, --tiktok-allow-stitch
LinkedIn--linkedin-visibility
Pinterest--pinterest-board-id, --pinterest-title, --pinterest-description, --pinterest-link, --pinterest-alt-text

Run simplepost post --help for the generated full list, including option descriptions.

Boolean options use positive/negative forms rather than string values: --strict-mode / --no-strict-mode, --youtube-made-for-kids / --no-youtube-made-for-kids, and the TikTok --tiktok-allow-* / --no-tiktok-allow-* variants.

Other commands

CommandWhat it does
simplepost setupConfigure or change the secret storage backend.
simplepost accountList local and Scheduler-connected accounts.
simplepost account add <platform>Add a local account. Supports --alias, --bot-token, --chat-id, --no-browser, --callback-port, --callback-url, --redirect-uri.
simplepost account remove <alias>Remove a local account by alias.
simplepost connectAuthorize the CLI against a Scheduler app. Supports --url, --token, --no-browser, --callback-port.
simplepost disconnectRevoke the Scheduler CLI token remotely, then remove the local copy.

Token rotation

X rotates refresh tokens on every refresh: a successful refresh returns a new refresh token and invalidates the old one. The CLI persists rotated tokens back into the local secret store automatically when posting through a stored X account. If you instead use SDK env vars (X_CLIENT_ID, X_REFRESH_TOKEN, ...), nothing rewrites your .env; you have to capture the refreshed values yourself. See token rotation.