The command line

bongsu is for what is better scripted than clicked: checking the queue, reading and setting texts, approving from a terminal, exporting the plan, and generating the Cloudflare secrets. Setup, connections, devices and keys stay in the app and the dashboard. The CLI talks to the instance's HTTP API only (never to the database), so it works the same against Cloudflare and Docker.

Install

The package is bongsu on npm (Node 22 or later). Run it with npx, or install it once:

npx bongsu --help
npm install -g bongsu   # then plain `bongsu`

The examples below call it bongsu. bongsu --version prints its version; npx bongsu@<version> runs a given one.

From a checkout of the repository, the same command is built with pnpm --filter bongsu build and runs as node packages/cli/dist/cli.mjs (an alias makes it bongsu: alias bongsu="node $PWD/packages/cli/dist/cli.mjs").

Log in

Make a CLI token in the app's Keys & tokens → Your CLI tokens (see CLI tokens), then:

bongsu login --url https://bongsu.example.com
# CLI token: (typed without echo)

--token bongsu_cli_… also works, but then the token sits in your shell history: prefer the prompt. The token is checked against the instance before anything is saved, so a typo never becomes the default.

Profiles live in ~/.config/bongsu/cli.json ($XDG_CONFIG_HOME/bongsu/cli.json), a file only you can read (mode 0600; the directory 0700). There is no keychain integration: the file holds the tokens. A profile is named after the instance's host name; --workspace <name> at login names it yourself. The last login becomes the default, and --workspace <name> on any command picks another profile:

bongsu --workspace demo status
bongsu logout                     # forgets the default profile (or --workspace <name>)

Scripts and CI

When both BONGSU_URL and BONGSU_TOKEN are set they take the place of the profile, so a pipeline needs no file:

BONGSU_URL=https://bongsu.example.com BONGSU_TOKEN=bongsu_cli_… bongsu export --format csv > plan.csv

Commands

Global options go before or after the command: --workspace <name>, --json (print the API's answer as JSON; for queue, status, texts, approve, stats and ingest), --help, --version. Exit codes: 0 done; 2 an unknown command or option; 1 a command the CLI refuses or that fails (the reason is on stderr).

Command What it does
login --url <url> [--token t] Saves a CLI token as a profile
logout Forgets a profile
status Paused or not, dry run, today, accounts, jobs of the last 7 days
queue [--days n] [--account id] What is due next and whether it is approved
texts get <key> Prints caption, comment, comment2 and alt; for a YouTube video, the caption (its description) and comments, then its title, language and translations
texts set <key> [--caption f] [--comment f] [--comment2 f] [--alt f] [--title t] [--language lang] [--translation lang --title t --description f] Sets texts from files (- is stdin)
approve <key> [--yes] Shows what you are approving, then approves
stats <key> | --account id [--daily] [--from d] [--to d] A post's metrics series, or an account's followers and views per day
ingest <file.json | -> Uploads metrics samples collected elsewhere (needs the metrics scope)
export [--format json|csv] [--texts] Every plan item, to stdout
mcp A stdio bridge to /mcp for MCP clients without HTTP
schema --dialect sqlite|postgres The DDL for a fresh database
deploy-secrets Generates and sets the Cloudflare secrets

status and queue

bongsu status
bongsu queue --days 3 --account demo_travel

queue prints due, account, key and state: not approved, approved, approved (time changed) (approved for another time: approve it again) or held. Without items it says nothing due until <date>.

texts

bongsu texts get reel:demo/first_reel
bongsu texts set reel:demo/first_reel --caption caption.txt --alt alt.txt
echo "Link in bio" | bongsu texts set reel:demo/first_reel --comment -

For a YouTube video, --title sets the title, --language the video's own language, and --translation <lang> --title <text> --description <file|-> adds or replaces one translation (a --title after --translation belongs to it, and a translation needs both). YouTube takes translations only with the video's language, so set it first (or in the same command). The other titles, translations and tags stay as they are.

bongsu texts set video:demo/first_video --title "Lisbon by tram" --language en
bongsu texts set video:demo/first_video --translation es --title "Lisboa en tranvia" --description es.txt

At least one field is needed, and - (stdin) may be used once per command. Approvals hash the texts, so an existing approval stops matching after a change: approve again.

approve

bongsu approve reel:demo/first_reel
bongsu approve comment:demo_video_id --yes

Item keys look like reel:demo/first_reel; a Shorts comment is comment:<video id> (this is also how you approve comments: there is no separate --comments flag). The command prints the texts, the media names with their checksums and the lint checks that didn't pass, then asks approve? [y/N]. --yes skips the question; without a terminal --yes is required. With --json the summary goes to stderr and the answer to stdout.

Only a CLI token of an admin or a creator (or an instance admin) can approve, and only exactly what it was shown: if the texts, media or time change between the summary and the approval, the instance refuses (409) and you look again. If the item isn't ready (a check fails, no media), the problems are listed. An agent key can never approve, whatever its scopes.

stats and ingest

bongsu stats 'reel:20261020_LISBON/tram' --daily --from 2026-10-20
bongsu stats --account demo_travel --from 2026-10-01 --to 2026-10-31
bongsu ingest samples.json

stats prints the series as a table (times in UTC); --json prints the API's answer. ingest sends a file shaped like ingest_metrics takes and says how many samples were added and how many were already there; a CLI token of an admin, an editor or a creator has the metrics scope.

export

bongsu export --format csv --texts > plan.csv
bongsu export --format json | jq -r '.[] | select(.approved | not) | .key'

JSON is the default and prints an array of items; CSV has one row per item. --texts adds caption, comment, comment2 and alt. alt is one line per entry: an array in JSON, newline-joined in CSV and in texts get. This is a plan export for scripts, not a database backup (for that, see Databases).

mcp

claude mcp add bongsu -- npx -y bongsu mcp

Run bongsu login first: the bridge uses that profile. From a checkout instead, give Claude the absolute path (node "$PWD/packages/cli/dist/cli.mjs" mcp from the repository root), since it starts the bridge from elsewhere and an alias doesn't reach it. See Agents.

schema

bongsu schema --dialect postgres | psql "$DATABASE_URL"

deploy-secrets

bongsu deploy-secrets [--config-dir <dir>] [--rotate] [--only A,B] generates the secrets and sets them in both Workers with wrangler. When it generates SETUP_TOKEN it prints it once, since the dashboard's first sign-up asks for it; a SETUP_TOKEN already set in both Workers is kept and not printed. The other secrets are never printed. It is explained in Deploy on Cloudflare, section 6.

What a token can do

Token Can
CLI token, admin or creator (or an instance admin) Read, edit plans and texts, approve, plus draft, pause and resume in the API
CLI token, editor Read, draft, plan, move release dates and upload stats: can texts set, cannot approve, pause or resume
Agent key Only its scopes; never approve

status, queue, texts get and export need the read scope; texts set needs plan. A CLI token acts as its person, with their role's rights, read on every request.

Edit this page