Syndroodocsv0.7.2
WebsiteGitHubOverview
Syndroo is under active development. Documentation and CLI behavior may change, and features are not guaranteed to work reliably.

Run the CLI on this machine

Syndroo is a single Node.js program. It reads one configuration file, keeps its state under one state-root directory, and talks to platform APIs directly through an egress policy built from each provider's manifest. Nothing watches, schedules or runs in the background.

Install

Choose the project directory first, and the runtime manager that directory declares. A chat scratch directory or the home directory is not a project, and an install that lands in one Node installation while later commands resolve another is the usual reason a command then goes missing. From the project directory, record the runtime that is actually in effect and where a global install lands:

command -v node npm
node --version
npm --version
npm root --global
npm prefix --global

Node.js 24.19.0 or newer is the floor, and supported local writes require macOS or Linux. Keep using that one runtime for the install, the version check and every later command. With mise, prefix each command with mise exec --; mise which takes a single argument, so resolve the paths one at a time: mise which node, mise which npm and mise which syndroo. A shim is expected, so verify the executable the shell resolves with command -v syndroo instead of comparing pathnames across managers or adding a bin directory to PATH. Do not assume Homebrew: several Node installations can coexist, and command -v node says which one wins.

Install the CLI, provider SDK, and all five official providers at 0.7.2 with one command, from that runtime:

npm install --global @syndroo/cli@0.7.2 @syndroo/provider-sdk@0.7.2 @syndroo/provider-bluesky@0.7.2 @syndroo/provider-threads@0.7.2 @syndroo/provider-linkedin@0.7.2 @syndroo/provider-mastodon@0.7.2 @syndroo/provider-devto@0.7.2

Run command -v syndroo and then syndroo --version. --version reports the installed CLI version; the Node.js version is reported separately by node --version. --help prints the command list, and each command answers its own --help. Both work before any configuration exists and create nothing.

Contributors who need a source build can follow the product repository README; a checkout and build are not prerequisites for readers.

Configuration

There is one configuration source. Without --config the CLI reads $XDG_CONFIG_HOME/syndroo/config.json, falling back to $HOME/.config/syndroo/config.json. A missing default file is fine for the commands that resolve no provider; a missing file named by --config is an error. The path given to --config must be absolute.

{
  "version": 1,
  "stateRoot": "state",
  "providers": {
    "ghost": { "path": "vendor/ghost-provider" }
  }
}

version must be 1. stateRoot and each providers.<id>.path resolve relative to the directory of the configuration file, so changing the working directory cannot change which state or which plugin a run uses. Unknown keys are refused, and a key that looks like a token, secret, password or API key is refused by name rather than ignored. Configuration has the full contract.

Local state

The state root defaults to $XDG_STATE_HOME/syndroo/runtime-v1, falling back to $HOME/.local/state/syndroo/runtime-v1. It carries a single format marker, and the runtime refuses a root whose marker is missing or names another format. Earlier Syndroo versions used a different root; this runtime never probes, migrates or deletes it.

Reads never create the root and never repair it. A read-only command on a machine that has never connected anything reports the state as uninitialized instead of writing a directory into place.

Connect one provider

One credential source per run: --from-env reads the SYNDROO_CREDENTIALS environment variable, and --credential-file reads a private JSON file. Both are parsed as strict JSON and validated against the provider's own credential schema before anything is stored.

export SYNDROO_CREDENTIALS='{"identifier":"you.bsky.social","password":"APP_PASSWORD_PLACEHOLDER"}'
syndroo connect bluesky --from-env --label work --default

Adding --label names the connection, and --default makes it the target a document gets when its target names no connection. A provider can hold several connections: connect again with --connection to refresh an existing one, use --update to change a label or the default flag, and --disconnect to remove the local connection. Disconnecting never revokes a platform token.

A machine caller can drive the same command with a whole request document instead of flags: --input takes a file, and - reads standard input. Every connection step is recorded, so replaying the same request returns the stored answer rather than connecting twice.

Prepare, then execute

A publication is frozen before it is sent. Preparing validates the content and the targets and returns a preview plus a single-use approval token; executing consumes that token.

{
  "type": "prepare",
  "content": { "text": "One document, chosen targets." },
  "targets": [
    { "provider": "bluesky" },
    { "provider": "devto", "options": { "title": "Hello", "body_markdown": "One document." } }
  ]
}
syndroo publish --input post.json --json
syndroo publish --input - < execute.json --json

On a terminal the prepare shows the frozen preview and asks Publish to N accounts? [y/N/v]. The accepted answers are y or yes, n or no, and v or view. Only an unambiguous y executes the same token; v expands the complete frozen preview and asks again without preparing again or extending the window, and n, an empty line or end-of-input cancels and exits 5 without sending. Any other answer is not an authorization and asks again. --yes auto-confirms the frozen intent this process just prepared, without a prompt. In --json the command never prompts and returns the prepared result, so a caller decides; with --yes --json it prepares, confirms and executes in one call and prints exactly one final envelope, with no second prepared result. An explicit execute request is read only from standard input, while the same-process confirmation or --yes does not need a separate execute request on stdin; the token is minted and consumed by the same process family, editing the file after the prepare changes nothing about what is sent, and an approval that expired or a connection whose binding changed refuses the old token.

--data takes the same request document inline, which is convenient for a one-off but leaves the content in the process arguments. --dry-run parses and previews without writing state, resolving credentials or making a network request. --request-id gives the call a stable identity so a retry of the same logical intent is recognised.

Read status

Reads never mutate and never repair, and they never contact a platform. With no selector the overview lists provider availability, the connection count and the recent operations.

syndroo status --json
syndroo status --connections --json
syndroo status --operation <operationId> --json
syndroo status --operations --limit 20 --json

--provider asks about one provider and may also filter the connection list. --limit and --cursor belong to --operations only, and an opaque cursor is passed back unchanged to fetch the next page.

Retry

Retrying is a mode of the same publish command, not a fourth command: --retry names the earlier operation and --to names the connection to retry, repeatable once per target. Only targets whose failure is known not to have been applied are eligible. A delivery whose outcome is unknown is never eligible: the platform may already hold the post, and the CLI will not find out by sending it again.

syndroo publish --retry <operationId> --to <connectionId> --json

Exit codes

An exit code says what the local call established, not whether a platform accepted the post. The exit code table lists all seven values, including the two that matter most: 4 means a write may have reached the provider with no known result, and 6 means a finished execution is known not to be fully successful.