CLI Tool
Installation
npm install -g @simplepush/cliThe 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-tokenor$SP_API_TOKEN. Used for personal topic sends, sends to your own devices,sp events, and personalsp 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), allsp orgcommands, and orgsp 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
| Flag | Description |
|---|---|
-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, --quiet | Suppress 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.
# 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.
| Flag | Description |
|---|---|
--title, --content | Task 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 / -o | Org targets: one member by name, every member, or an org topic. |
--submit | Require explicit submission instead of auto-completing when required inputs are filled. |
--wait | Block 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. |
--markdown | Render --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. |
--shared | One shared task for all recipients instead of one each, see above. |
--no-encrypt | Org 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.
# 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 ./inboxOutput shape:
{"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}}| Flag | Description |
|---|---|
--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. |
--replies | Collect only reply-composer replies. Without a mode flag, collect streams the group's full activity (inputs + replies + completions). |
--inputs | Collect only input fills and completions. |
--submissions | Collect 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.
# 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.pdfA 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.
| Flag | Description |
|---|---|
--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.
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.
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.
| Flag | Description |
|---|---|
--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-encrypt | Org 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.
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| Flag | Description |
|---|---|
--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-input | Ask 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 / -o | Org targets. |
--format <text|json> | text prints the bare id; json prints a sent line for piping into sp collect. |
--tag, --shared, --no-encrypt | As 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.
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| Option | Applies to | Meaning |
|---|---|---|
--status | tasks, grptsk_ reads | pending, completed, canceled, declined, expired; repeatable or comma-separated |
--since, --until | tasks, submissions | ISO-8601 instant or a relative window (7d, 12h, 30m) |
--topic | tasks | a topic value (name), or a topic id |
--member | tasks, submissions | a usr_ id, or a member name on an org |
--group | tasks | only the instances of one grptsk_ group |
--limit, --cursor, --all | tasks, submissions | page size, continuation, follow-to-end |
--format | all | json (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.
sp search
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.
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| Option | Meaning |
|---|---|
--kind | task, 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, --member | Same as sp get |
--limit | Best hits to return; default 20, at most 100 |
--format | json (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.
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"| Flag | Description |
|---|---|
--type <KIND> | Event type filter, repeatable. See below. |
--since, --until, --limit, -f, --follow | History range controls. --since accepts 24h, 7d, or ISO 8601. |
--format <json|pretty|raw> | Output format, default json. |
--direct | Open 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:
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 rotateMembers 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.