Skip to content

CLI Tool

Installation

bash
npm install -g @simplepush/cli

The package installs two identical binaries: sp and simplepush. Node.js 20+ (Bun works too: bun add -g @simplepush/cli).

Commands

sp task      Send a task, optionally wait for the first completion.
sp subtask   Append a follow-up to an existing task via its append token.
sp cancel    Withdraw a pending task, subtask, or task group you sent.
sp notify    Send a fire-and-forget notification.
sp collect   Collect replies, inputs, or submissions as bounded NDJSON. Built for scripts and agents.
sp download  Download one file a collect line referenced, by its ids.
sp get       Read what came back: task listings, one task with its subtasks, a task group roster, submissions.
sp search    Full-text and location search over everything that came back.
sp events    Stream or replay the raw event feed.
sp auth      Log in to an organization (login / logout / status).
sp org       Administer an organization: members, invites, topics, API key, encryption.

Run sp <command> --help for the full flag list of any command.

Credentials

The CLI uses two independent credentials:

  • Personal API token — from the app settings, under API Token. Passed via --api-token or $SP_API_TOKEN. Used for personal topic sends, sends to your own devices, sp events, and personal sp collect.
  • Organization session — created by sp auth login (browser or device-code flow) and stored in ~/.config/simplepush/auth.json. Used for org-targeted sends (--member, --broadcast, --org-topic), all sp org commands, and org sp collect.

sp collect and sp download pick automatically: with an API token they work on your personal stream, without one they fall back to the logged-in org session.

Shared options

FlagDescription
-t, --topic <TOPIC>Topic to send to, or to filter on for sp events. Omit every target to send to your own devices.
--api-token <TOKEN>Personal API token. Falls back to $SP_API_TOKEN.
-p, --password <VALUE>Encryption password, repeatable. secret@alerts registers a password for the topic alerts; a bare secret is your personal password (decrypts your submissions and what you send to your own devices). Encrypts on send, decrypts on receive.
--base-url <URL>Server override. Falls back to $SP_BASE_URL, default https://api.simplepu.sh.
-q, --quietSuppress informational output, print payloads only.

sp task

Send an interactive task. Addressing is one of -t/--topic (personal), -m/--member, -b/--broadcast, -o/--org-topic (org, requires sp auth login), or nothing at all, which sends to your own devices.

bash
# ask a question on a topic
sp task -t deploys --title "Approve deploy?" -c "Approve,Deny,Hold"

# actions input: tappable buttons whose key is reported back
sp task -t deploys --content "Deploy to prod?" \
  -a "approve=Approve:primary,deny=Deny:destructive"

# slider input
sp task -t pools --content "Log the readings" \
  -s "pH of pool 3;min=0;max=14;step=0.1;unit=pH;default=7"

# send to your own devices: no target at all
sp task --title "Reminder" --content "Water the plants"

By default each recipient gets an independent task instance; the send prints the group id (grptsk_...) and per-instance append tokens. --shared sends one shared task that every recipient answers together, and prints its tsk_ id.

FlagDescription
--title, --contentTask title and body.
--text-input <SPEC>Text input. SPEC: description[;key=value...]; settings required=true|false (default true), defaultValue=.... Repeatable, like all input flags.
-c, --choice-input <SPEC>Choice input. SPEC: [description;]opt1,opt2[;key=value...]; settings required, multi=true, minSelections=N, maxSelections=N. \; for a literal semicolon.
-a, --action-input <SPEC>Actions input. SPEC: [description;]key=Label[:style],...[;required=...]; style default, primary, or destructive. \, for a literal comma.
-s, --slider-input <SPEC>Slider input. SPEC: [description;]min=..;max=..[;step=..][;unit=..][;default=..][;required=..].
--photo-input, --voice-recording-input, --file-input, --location-input <SPEC>Upload-style inputs. SPEC: description[;required=...].
-l, --link <URL>Link attachment. Repeatable.
-f, --file <PATH>Local file attachment, uploaded (and encrypted when the send is — topic password or org master key). Repeatable.
-m / -b / -oOrg targets: one member by name, every member, or an org topic.
--submitRequire explicit submission instead of auto-completing when required inputs are filled.
--waitBlock until the first completion and print the result. Personal sends only.
--reply <MODE>Show a reply composer: one-shot, sticky, or one-time-per-user.
--markdownRender --content as Markdown on the recipient.
--tag <NAME>Tag for receiver-side filtering. Falls back to $SP_TAG.
--expires <DUR|ISO>Deadline: a duration from now (2h, 7d) or an ISO 8601 timestamp. Past it, an unanswered task expires.
--sharedOne shared task for all recipients instead of one each, see above.
--no-encryptOrg sends: plaintext even when the org vault is unlocked.
--format <text|json>text prints the bare id; json prints a machine-readable sent line for piping into sp collect.

sp collect

The receive half of a send: collect the answers to a task group — or a notification's answer — as machine-readable NDJSON. Every line is one JSON object, starting with a sent line describing the group, and a run that stops emits a final end line saying why — so a consuming script or agent never parses ambiguous output.

By default it collects the group's full activity — input answers, reply-composer replies, and completions — and stops once every recipient has finished. --inputs or --replies narrow it to one kind; the watcher modes (--replies, --submissions) have no natural end and run until Ctrl-C or an explicit --until.

bash
# the canonical pipe: send, then collect everything that comes back
sp task -t crew --format json --title "Status?" --text-input "What are you seeing?" \
  | sp collect

# collect only the input answers
sp task -m "Alice" --format json -a "ok=Done:primary" | sp collect --inputs

# manually collect a single task or notification by id, no pipe needed. Without
# a piped `sent` line there is no implicit resume point, so pass --since to
# backfill answers that arrived before the collect started; exits on completion
sp collect --instance tsk_0193... --since 2h
sp collect --instance ntf_0193... --since 2h

# watch your inbox submissions instead of a group
sp collect --submissions --until count:1

# download every uploaded file into ./inbox as it streams in
sp task -t crew --format json --title "Send the report" --file-input "This week's report" \
  | sp collect --inputs --save-files ./inbox

Output shape:

json
{"type":"sent","groupId":"grptsk_...","createdAt":"...","instances":[{"taskId":"tsk_...","recipient":{"publicId":"usr_...","name":"Alice"}}]}
{"type":"reply","groupId":"grptsk_...","taskId":"tsk_...","recipient":{...},"actor":{...},"body":{"text":"All good"},"createdAt":"..."}
{"type":"end","reason":"idle","counts":{"reply":1},"instances":{"total":1,"completed":0,"deleted":0,"canceled":0,"declined":0,"expired":0,"pending":1}}
FlagDescription
--group <grptsk_...>Group id label for the output lines. Usually supplied by the piped sent line; needs --instance ids alongside it.
--instance <tsk_...|ntf_...>Collect specific instances by id — the manual alternative to piping a sent line. Repeatable (one kind per run). Notification answers arrive as completed lines with a reply field.
--repliesCollect only reply-composer replies. Without a mode flag, collect streams the group's full activity (inputs + replies + completions).
--inputsCollect only input fills and completions.
--submissionsCollect your inbox submissions instead of a group.
--since <POINT>Resume point (2h, 7d, or ISO 8601): backfills group events or submissions from that point. Defaults to the piped send's createdAt.
--until <COND>Stop condition, repeatable; first to trip wins: complete, idle:<dur>, count:<n>, timeout:<dur>, or forever. Defaults: complete for group collects; --replies / --submissions watch forever.
--save-files <DIR>Download every collected file (photo, voice, and file uploads, reply and submission files) into DIR as it arrives, decrypted and checksum-verified. Each file object on the emitted line gains a path field: the saved location, or null if its download failed.
--format <json|pretty>NDJSON (default) or human-readable lines.

sp download

Fetch one file a collect line referenced, after the fact. Task-scoped files — input uploads (inp_...) and reply files (rfl_...) — are addressed by their task id; submission files (sbf_...) by their submission id. These are exactly the ids the NDJSON lines carry: the line's taskId (or the submission's id) plus the file object's id. Files are decrypted and checksum-verified, same as --save-files.

bash
# a task input upload or reply file: taskId + the file object's id
sp download tsk_0193... inp_0193...
sp download tsk_0193... rfl_0193... --out ./inbox

# a submission file: submission id + file id
sp download sbm_0193... sbf_0193... --out report.pdf

A file's metadata (encryption, checksum, filename) lives on the event that announced it, so the command searches your event stream — the last 7 days by default. For older files pass --since 90d or an ISO timestamp.

FlagDescription
--out <PATH>Where to save: a file path, an existing directory (the upload's filename is used inside it), or omitted for the current directory.
--since <POINT>How far back to search for the file (24h, 90d, or ISO 8601). Default 7d.
--format <json|pretty>json (default) prints one downloaded line with the saved path; pretty prints just the path.

sp subtask

Append a follow-up to a task the recipient already has, using the append token printed by sp task. Accepts the same input flags as sp task plus --link, -f/--file, --markdown, and --submit. With a group append token it appends to every instance unless you pass --instance <tsk_...> to pick some. With -t/--topic it authenticates with your API token; without, it uses the org session.

bash
sp subtask --append-token "$TOKEN" \
  --content "One more approval needed" \
  -a "accept=Accept:primary,deny=Deny:destructive"

sp cancel

Withdraw a pending send. The id's prefix picks what to cancel: tsk_ cancels one task, sub_ one follow-up (the rest of the chain stays live), grptsk_ every still-pending instance of a group.

bash
sp cancel tsk_...

# tear down the rest of a group after the first useful answer
sp cancel grptsk_... --reason answered --note "already handled"
# → {"type":"canceled","groupId":"grptsk_...","canceled":2,"skipped":1}

# replace a follow-up with a corrected one (same chain only)
sp cancel sub_... --reason superseded --superseded-by sub_...

A group cancel prints the counts: canceled instances were still open, skipped had already finished and were left untouched. Canceling an already-answered task fails with task_already_completed; re-canceling is a no-op.

FlagDescription
--reason <canceled|answered|superseded>Why, rendered on the recipient's card. Default canceled.
--note <TEXT>Free-text explanation for the recipients. Sealed under the target's key when the CLI holds it (org vault, or the matching -p); plaintext with a warning otherwise.
--superseded-by <ID>The replacement's id. Requires --reason superseded.
-p, --password <VALUE>Seals the note under the chain's key: pw@topic for a topic send, a bare pw for a send to your own devices. Without it the note ships in plaintext, with a warning.
--no-encryptOrg cancels: send the note plaintext even when the vault is unlocked.

sp notify

Send a fire-and-forget notification. Same addressing as sp task. --content is required, and a notification carries at most one input.

bash
sp notify -t builds --title "Build succeeded" --content "main @ abc123 deployed in 47s"
sp notify -b --title "All hands at 3pm" --content "Link in your inbox"

# a notification with an input: pipe into sp collect to get the answer
sp notify -t ops --content "Deploy v2.1.0?" -c "Approve,Deny" --format json | sp collect
FlagDescription
--content <TEXT>Required body text.
--title <TEXT>Title.
--image <URL> / --audio <URL>One media item by URL (mutually exclusive). Images render on iOS and Android; audio is iOS-only. File uploads are SDK-only.
--text-inputAsk for a free-form text reply.
-c, --choice-input <opts>Comma-separated single-select options.
-a, --action-input <SPEC>Action buttons; styles default and destructive only.
-m / -b / -oOrg targets.
--format <text|json>text prints the bare id; json prints a sent line for piping into sp collect.
--tag, --shared, --no-encryptAs on sp task.

sp get

The read side: the same filters the MCP query tools offer, for scripts and agent harnesses. Output is one JSON object per line by default (--format pretty for humans). A cut-off page ends with {"type":"more","cursor":"…"}; pass it back with --cursor, or use --all to follow cursors to the end.

bash
sp get tasks --status expired --since 7d
sp get tasks --status pending,declined --member anna --all
sp get tsk_…
sp get sub_…
sp get grptsk_… --status pending
sp get submissions --since 24h
OptionApplies toMeaning
--statustasks, grptsk_ readspending, completed, canceled, declined, expired; repeatable or comma-separated
--since, --untiltasks, submissionsISO-8601 instant or a relative window (7d, 12h, 30m)
--topictasksa topic value (name), or a topic id
--membertasks, submissionsa usr_ id, or a member name on an org
--grouptasksonly the instances of one grptsk_ group
--limit, --cursor, --alltasks, submissionspage size, continuation, follow-to-end
--formatalljson (default) or pretty

On an org session the commands read the organization's tasks (recipients shown by member name); with --api-token they read the tasks you sent. Encrypted content is decrypted with the keys the CLI holds (-p passwords, or the org vault) and left as ciphertext otherwise, with one warning. For personal accounts these HTTP query commands need a subscription; sp events, sp collect and sp download stay free. sp events --member narrows the socket replay by actor.

Ranked search over everything the credential reads, across all time: tasks, subtasks, answers, replies, notifications and submissions. Words match literally and stemmed in the organization's configured languages. A radius or polygon searches by place instead of, or on top of, words. Output is one hit per line, --format pretty for humans.

bash
sp search leak pump                          # both words, anywhere
sp search '"pump 3"' --kind reply,answer     # exact phrase, only replies and answers
sp search --near 52.52,13.405 --radius 500   # everything within 500 m, nearest first
sp search leak --within "52.5,13.4;52.5,13.5;52.6,13.45"
sp search leak --member anna --since 30d
OptionMeaning
--kindtask, subtask, answer, reply, notification, notification_answer, submission; repeatable or comma-separated
--near <lat,lng>, --radius <METERS>Only hits within the radius, each with its point and distance. Both required together.
--within <lat,lng;lat,lng;...>Only hits inside the polygon (at least 3 corners). Not with --near
--since, --until, --memberSame as sp get
--limitBest hits to return; default 20, at most 100
--formatjson (default) or pretty

Without words, a radius search returns nearest first and a polygon search newest first. Credentials, decryption and the subscription rule are the same as for sp get.

sp events

Stream the raw event feed of your personal account. Requires an API token. With no time flags it streams live until interrupted; with --since it replays history and exits, unless --follow keeps it streaming.

bash
sp events                          # live, everything
sp events -t deploys --since 24h   # one topic, last 24 hours, then exit
sp events --type submission.photo  # only photo submissions
sp events --since 7d --follow -p "secret@alerts"
FlagDescription
--type <KIND>Event type filter, repeatable. See below.
--since, --until, --limit, -f, --followHistory range controls. --since accepts 24h, 7d, or ISO 8601.
--format <json|pretty|raw>Output format, default json.
--directOpen an independent connection instead of the shared broker that live streams use by default.

--type accepts coarse and fine-grained filters: task, task.input, task.input.text, task.input.choice, task.input.multi-choice, task.input.action, task.input.slider, task.input.photo, task.input.voice, task.input.file, task.input.location, task.deleted, task.deleted-by-recipient, submission, submission.text, submission.photo, submission.file. Unknown filters are warned about and ignored.

sp auth and sp org

sp auth login signs you in to your organization through the browser (or a device-code flow over SSH; force one with --web / --device). The session powers org sends and everything under sp org:

bash
sp org members invite "Alice"        # prints a one-time login code for Alice's app
sp org members invite "Bob" --role admin
sp org members list
sp org members remove "Bob" --yes

sp org invites list
sp org invites revoke <id>

sp org topics create field-crew      # admin-managed org topics
sp org topics assign field-crew "Alice"
sp org topics list

sp org api-key info                  # the org API key used by the SDKs
sp org api-key rotate

sp org encryption enable             # end-to-end org encryption with a versioned master key
sp org encryption status
sp org encryption key rotate

Members onboard with the printed login code: they install the app and enter the code, no email required. See the Organizations guide for the full walkthrough.