@bufferapp/cli
v1.0.0
Published
CLI for the Buffer, designed for AI agents and humans
Maintainers
Keywords
Readme
Buffer CLI
A command-line interface for the Buffer API, designed for both humans and AI agents. All output is JSON, all errors are structured, and every command is generated from the public GraphQL schema.
Installation
npm install -g @bufferapp/cli
buffer --versionRequires Node.js 18 or later.
Quick start
# 1. Install
npm install -g @bufferapp/cli
# 2. Authenticate (recommended). Interactive setup writes your API key,
# default organization, and timezone to global config, then offers
# to install the agent skill into Claude Code or Codex.
buffer init
# 3. Verify setup
buffer doctor
# 4. Run a command
buffer account
buffer channels listPrefer not to persist an API key to disk (CI, containers, ephemeral envs)? Skip buffer init and set BUFFER_API_KEY instead — see Authentication. Generate an API key at https://publish.buffer.com/settings/api.
Authentication
buffer init is the recommended setup. It writes your API key to the global config alongside a default organization and timezone, then offers to install the agent skill. Generate an API key at https://publish.buffer.com/settings/api.
buffer initThe CLI also accepts an API key from the BUFFER_API_KEY environment variable. Supported for CI, containers, and any case where persisting an API key to disk is undesirable — it takes precedence over the config file when both are present.
export BUFFER_API_KEY=your-api-key-hereResolution order: BUFFER_API_KEY → apiKey in the global config file. The first non-null API key wins. Missing or invalid API keys produce exit code 4 with a structured error on stderr. Regular commands never prompt interactively for credentials — only buffer init is interactive, and it is opt-in.
For security, apiKey is only accepted in the global config file. Attempts to write it to a repo-scoped config are rejected.
Usage
buffer --help
buffer --version
buffer <group> <command> [flags]See Schema introspection below to discover commands programmatically.
Example commands
# Account info
buffer account
# Channels
buffer channels list
buffer channels get --id <channel-id>
# Posts (queries)
buffer posts list --channel-id <channel-id>
buffer posts get --id <post-id>
# Posts (mutations, flags)
buffer posts create --channel-id <channel-id> --scheduling-type automatic --mode addToQueue --text "Hello"
# Posts (mutations, JSON)
buffer posts create --json '{"channelId": "...", "schedulingType": "automatic", "mode": "addToQueue", "text": "Hello"}'
# Ideas (mutations, flags)
buffer ideas create --organization-id <org-id> --text "Idea body"
# Ideas (mutations, JSON)
buffer ideas create --json '{"organizationId": "...", "content": {"text": "Idea body"}}'Input can also come from a file or stdin:
buffer posts create --input post.json
cat post.json | buffer posts create --input -Dry run
All mutations support --dry-run, which validates the input locally and prints the payload that would be sent without making the API call:
buffer posts create --json '{"channelId": "abc"}' --dry-runRequest timeout
Use --timeout <ms> to bound how long any command will wait for the API. The default is 30000ms (30s). Pass 0 to disable the timeout for long-running operations:
# Custom timeout (5s)
buffer posts list --timeout 5000
# Disable the timeout
buffer posts create --json '{...}' --timeout 0When the timeout fires the request is aborted and the CLI exits with code 3 and a message on stderr like Request timed out after 5000ms. Set a default with buffer config set timeout 60000.
Output
- All command output is JSON to stdout; errors go to stderr
- Relay connections are flattened — responses return
{ items, pageInfo }rather thanedges/nodewrappers - Each command ships with a curated default field set so responses stay small without
--fields. Pass--fieldsto override the default with a comma-separated list of dot-notation paths. The CLI builds a minimal GraphQL request from those paths so the API only returns what you ask for.
# Default subset (small, still useful)
buffer posts get --id post_123
# Cherry-pick fields, including nested paths
buffer posts get --id post_123 --fields id,text,channel.name
# Connections expose items.* and pageInfo.*
buffer posts list --fields items.id,items.text,pageInfo.endCursor
# Brace expansion for sibling fields under a common prefix.
# Quote the value so the shell does not expand the braces itself.
buffer posts list --fields 'items.{id,text,status},pageInfo.endCursor'
# Nested groups work too — keep them quoted. Brace must be the last
# token of a path; cross-products like `{a,b}.{x,y}` are rejected.
buffer posts list --fields 'items.{id,channel.{id,name}}'
# Opt back into the full response
buffer posts get --id post_123 --fields allQuote
--fieldswhenever you use braces. Bash and zsh do their own brace expansion: an unquoteditems.{id,text}becomes two arguments (items.id items.text) before the CLI ever sees it. Wrap the value in single quotes (--fields 'items.{id,text}') so the literal string reaches the CLI's parser.
Run buffer schema describe <command> to see the defaultFields (returned without --fields) and selectableFields (every accepted path) for any command.
Prefer
--fieldsover piping tojq. Filtering withjqstill pays the cost of fetching every field over the network and through the response parser.--fieldsshrinks the GraphQL request itself, so the API skips the work and the response stays small. Usejqfor transforming the JSON the CLI already emitted, not for trimming fields you didn't need.
Output format and logging flags
Four global flags control rendering and stderr noise on every command:
--output <json|pretty|auto>overrides theoutputFormatconfig for one invocation.auto(default) pretty-prints to a TTY and emits JSON otherwise;--output jsonis the safe choice for scripts and agents.--quietsuppresses non-essential stderr — the in-flight spinner, the post-1-second completion line, the update-available notice, and rate-limit warnings. Stdout is unaffected. The spinner is also suppressed automatically when stderr is not a TTY orNO_COLORis set in the environment.--verboseprints a one-line rate-limit summary to stderr after every request. Without it, only policies that drop below the low-remaining threshold are reported.--no-colordisables ANSI colors in pretty output. TheNO_COLORenv var (any value) andCLICOLOR=0do the same; non-TTY stdout disables colors automatically, as doesTERM=dumb. SetFORCE_COLOR=1(orCLICOLOR_FORCE=1) to keep colors on when piping to a pager that handles them.
Exit codes
| Code | Meaning | | ---- | ----------------------------------------------------------- | | 0 | Success | | 1 | General error | | 2 | Usage error (bad flags, missing fields, validation failure) | | 3 | API error | | 4 | Auth error (missing or invalid API key) | | 130 | Interrupted by SIGINT (Ctrl-C) | | 143 | Terminated by SIGTERM |
When the CLI's stdout pipe is closed by the consumer (e.g. buffer ... | head), the CLI exits 0 silently rather than printing an EPIPE stack trace.
Schema introspection
buffer schema list
buffer schema describe posts createbuffer schema describe returns a full method signature as JSON — input types, output shape, required fields, enum values. Use it to discover commands and validate payloads before execution.
Shell completion
The CLI prints a completion script for bash, zsh, or fish. Pass --install to wire it up automatically; without it the script goes to stdout so you can eval or pipe it yourself.
# Install for the current shell (idempotent)
buffer completion bash --install
buffer completion zsh --install
buffer completion fish --install
# Or install manually by capturing the script
buffer completion bash > /etc/bash_completion.d/buffer
buffer completion zsh > "${fpath[1]}/_buffer"
buffer completion fish > ~/.config/fish/completions/buffer.fish
# Or source it on the fly (bash/zsh)
eval "$(buffer completion zsh)"--install appends a sourcing block to ~/.bashrc or ~/.zshrc, marked with # buffer cli completion so re-running is a no-op. For fish it writes the script directly to ~/.config/fish/completions/buffer.fish. Restart your shell (or source the rc file) to pick it up.
Agent context
The CLI ships markdown skill files (workflows, pitfalls, rate limits, idempotency, getting-started) that encode invariants beyond what --help and buffer schema describe can express. They are designed to be loaded into an AI agent's context window.
buffer context # all topics, concatenated markdown
buffer context workflows # one topic
buffer context --list # topic name + one-line summary
buffer context --output json # structured envelope for programmatic useThese commands make no API calls and require no auth.
Install agent skill
Install a Buffer skill into your agent so it can call the CLI without a custom system prompt:
buffer install claude # writes ~/.claude/skills/buffer/SKILL.md
buffer install codex # appends a managed block to ~/.codex/AGENTS.mdThe skill body is a single !buffer context directive. Claude Code expands !<command> and inlines the live buffer context output before the model reads the skill, so the agent always sees the up-to-date reference — no stale snapshot on disk, no follow-up tool call. Both targets are idempotent — re-running keeps the file current and reports alreadyInstalled when nothing changed. No API call, no auth required.
Reverse with buffer uninstall <agent>. For claude the standalone skill file is deleted; for codex the managed block is stripped, preserving surrounding notes. Idempotent — reports removed: false when nothing was found.
Configuration
The CLI reads config from two files. Repo config overrides global config per key. Missing files are treated as empty.
| Scope | Path | Notes |
| ------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Global | $XDG_CONFIG_HOME/buffer/config.json (or ~/.config/buffer/config.json) | User-level defaults. Only place apiKey can be stored. |
| Repo | .buffer/config.json (walking up from the current directory) | Project-level overrides. apiKey is stripped on load for safety. |
Inspect and manage config with:
# Interactive setup — writes apiKey + organizationId + timezone to global
# config, then offers to install the Buffer skill into your agent (Claude
# Code or Codex). Anything else (output format, pagination, timeout, update
# check) is set with `buffer config set`.
buffer init
# Read
buffer config get <key>
buffer config get --all
# Write (defaults to global; use --repo for repo-scoped)
buffer config set <key> <value>
buffer config set outputFormat pretty --repo
# Remove (defaults to global; use --repo for repo-scoped). Idempotent —
# missing keys report `removed: false` and exit 0.
buffer config unset <key>
buffer config unset outputFormat --repo
# Show resolved config file paths
buffer config pathConfig keys
| Key | Type | Description |
| ----------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| apiKey | string | API key. Global scope only. |
| organizationId | string | Default organization for commands that accept --organization-id. |
| outputFormat | json | pretty | auto | Stdout rendering. auto (default) pretty-prints to TTYs, JSON otherwise. |
| paginationLimit | number | Default value for --limit on list commands. |
| timeout | number | Default request timeout in milliseconds. 0 disables the timeout. |
| timezone | string | IANA timezone (e.g. America/Panama). Cached by buffer init from your account so agents can build dueAt offsets without a per-command buffer account call. |
Resolution order for any value: command flag → environment (BUFFER_API_KEY for apiKey, BUFFER_DISABLE_UPDATE_CHECK=1 for disableUpdateCheck) → repo config → global config → built-in default.
buffer config get reflects the resolution order: each returned entry includes source (env | repo | global | default), path for file-backed sources, and envVar (the variable name) when source is env. This lets agents and buffer doctor triage tell which layer the CLI actually picked, so an env-var override can't silently outrank the on-disk config.
Troubleshooting
Run buffer doctor to diagnose setup issues. It checks:
- Node.js version (18+ required)
- Config file is present and valid JSON
- API key is present (env var or global config)
- Default organization is set
- API endpoint is reachable
- API key is accepted by the API
- Rate limit headroom
- CLI version is current
buffer doctorBy default the report lists only failing checks so the actionable items are easy to spot. Pass --verbose to include passing checks as well:
buffer doctor --verboseEach check returns a status and, on failure, a suggested fix. Typical issues:
API key rejected— regenerate at https://publish.buffer.com/settings/api and re-runbuffer init.No default organization set— runbuffer initorbuffer config set organizationId <id>.Could not reach <url>— check network and VPN.Config at <path> is not valid JSON— fix or delete the file and re-runbuffer init.CLI <x> is behind the latest <y>—npm install -g @bufferapp/cli@latest.
Changelog
See CHANGELOG.md for release notes.
