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:
- Local CLI accounts — credentials live on the machine. Posting calls the SDK directly. For X, YouTube, Facebook, Instagram, and Bluesky the CLI ships with a bundled OAuth app, so
simplepost account add <platform>is the whole setup. TikTok, Threads, LinkedIn, and Pinterest require your own OAuth app (you provide the client secret through an environment variable). Telegram uses a bot token and chat ID — no OAuth at all. - 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
The published binary is simplepost. 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
| Backend | Best for | Requires |
|---|---|---|
keychain | Developer machines | OS keychain (macOS Keychain, Windows Credential Manager, libsecret) |
file-encrypted | Scripts, CI, headless servers | A password supplied through SIMPLE_POST_CONFIG_PASSWORD, or an interactive prompt |
file-plain | Local testing only | Nothing — 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
Bundled OAuth providers
X, YouTube, Facebook, Instagram, and Bluesky ship with a SimplePost OAuth client, so you do not need a developer app:
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 http://127.0.0.1:5000/oauth/callback (Instagram uses https://localhost:5000/oauth/callback because Meta requires HTTPS for that product). The browser returns an access token, the CLI stores it through your chosen backend, and it appears under simplepost account after that.
For Telegram, no OAuth is involved:
simplepost account add telegram \
--alias announcements \
--bot-token "$TELEGRAM_BOT_TOKEN" \
--chat-id "@channel"
Bring-your-own-OAuth-app providers
TikTok, Threads, LinkedIn, and Pinterest do not have a bundled OAuth client. Create your own platform app first, then pass the client secret through the appropriate env var before running account add:
| Platform | Required env var |
|---|---|
| TikTok | SIMPLE_POST_TIKTOK_CLIENT_SECRET (and the matching client key inside the CLI prompt) |
| Threads | SIMPLE_POST_THREADS_CLIENT_SECRET |
SIMPLE_POST_LINKEDIN_CLIENT_SECRET | |
SIMPLE_POST_PINTEREST_CLIENT_SECRET |
Optional SIMPLE_POST_<PLATFORM>_REDIRECT_URI env vars override the loopback URI when your platform app expects a different redirect.
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://schedule.simplepost.dev
simplepost account
simplepost post --interactive
The browser flow authorizes the CLI on the Scheduler, which returns a SimplePost CLI token. The CLI stores that token 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:
simplepost connect --url https://schedule.example.com --token "$SIMPLE_POST_CLI_TOKEN"
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.
Non-interactive posting through Scheduler account IDs is not exposed by the CLI yet. For CI publishing, use local CLI accounts or call the Scheduler API directly with accountIds.
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:
| Group | Flags |
|---|---|
| Workflow | --interactive |
| Targets | --account (repeatable, <platform>:<alias>) |
| 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-publish-at | |
| TikTok | --tiktok-publish-mode, --tiktok-visibility, --tiktok-allow-comment, --tiktok-allow-duet, --tiktok-allow-stitch |
--linkedin-visibility | |
--pinterest-board-id, --pinterest-title, --pinterest-description, --pinterest-link, --pinterest-alt-text |
Run simplepost post --help for the generated full list, including option descriptions.
Other commands
| Command | What it does |
|---|---|
simplepost setup | Configure or change the secret storage backend. |
simplepost account | List local and Scheduler-connected accounts. |
simplepost account add <platform> | Add a local account. Supports --alias, --bot-token, --chat-id, --no-browser, --callback-url, --redirect-uri. |
simplepost account remove <alias> | Remove a local account by alias. |
simplepost connect | Authorize the CLI against a Scheduler app. Supports --url, --token, --no-browser. |
simplepost disconnect | Remove the stored Scheduler CLI token. |
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.