CLI Tool
Installation
npm install -g @simplepush/cliThe package installs two identical binaries, sp and simplepush. It needs Node.js 20 or newer. 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. You find it in the app settings under API Token. Pass it with
--api-tokenor$SP_API_TOKEN. It is used for personal topic sends, sends to your own devices,sp events, and personalsp collect. - Organization session.
sp auth logincreates it (browser or device-code flow) and stores it in~/.config/simplepush/auth.json. It is used for organization-targeted sends (--member,--broadcast,--org-topic), allsp orgcommands, and organizationsp collect.
sp collect and sp download pick the credential automatically. With an API token they work on your personal stream. Without one they use the logged-in organization 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 sets the password for the topic alerts. A bare secret is your personal password, which decrypts your submissions and what you send to your own devices. The password encrypts on send and 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. Address it with -t/--topic (personal), or with -m/--member, -b/--broadcast, or -o/--org-topic (organization, requires sp auth login). With no target the task goes 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 one append token per instance. --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. Write \; for a literal semicolon. |
-a, --action-input <SPEC> | Actions input. SPEC: [description;]key=Label[:style],...[;required=...]. Style is default, primary, or destructive. Write \, 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. Any scheme, deep links included. The first one becomes an Open link button on the push when the task has no inline input. Repeatable. |
-f, --file <PATH> | Local file attachment. It is uploaded, and encrypted when the send is encrypted (topic password or organization master key). Repeatable. |
-m / -b / -o | Organization targets: one member by name, every member, or an organization 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. |
--priority <1-5> | How loudly the push interrupts, 1 (silent) to 5 (critical, sounds even on a muted phone). Default 3. See Priority. |
--critical-volume <0-1> | Volume of the iOS critical alert sound. Only with --priority 5. |
--tag <NAME> | Tag for receiver-side filtering. Falls back to $SP_TAG. |
--expires <DUR|ISO> | Deadline, as a duration from now (2h, 7d) or an ISO 8601 timestamp. An unanswered task expires once the deadline passes. |
--shared | One shared task for all recipients instead of one each, see above. |
--no-encrypt | Organization sends: plaintext even when the organization 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
Collect the answers to a task group, or the answer to a notification, as machine-readable NDJSON. Every line is one JSON object. The first line is a sent line that describes the group. When the run stops, it prints a final end line that says why. A consuming script or agent never has to parse 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. They 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. The piped sent line usually supplies it. Requires --instance ids alongside it. |
--instance <tsk_...|ntf_...> | Collect specific instances by id, as 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, sp collect streams the group's full activity (inputs, replies, and 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. The first condition that is met stops the run. Conditions: complete, idle:<dur>, count:<n>, timeout:<dur>, or forever. Default is complete for group collects. --replies and --submissions watch forever. |
--save-files <DIR> | Download every collected file (photo, voice, and file uploads, reply and submission files) into DIR as it arrives. Files are decrypted and checksum-verified. Each file object on the printed line gets a path field with the saved location, or null if the download failed. |
--format <json|pretty> | NDJSON (default) or human-readable lines. |
sp download
Fetch one file that a collect line referenced, after the fact. Task files, meaning input uploads (inp_...) and reply files (rfl_...), are addressed by their task id. Submission files (sbf_...) are addressed by their submission id. These are 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 with --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) is stored on the event that announced it, so the command searches your event stream. By default it searches the last 7 days. For older files pass --since 90d or an ISO timestamp.
| Flag | Description |
|---|---|
--out <PATH> | Where to save. A file path, or an existing directory (the upload's filename is used inside it). Omit it to save into 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, --priority, and --submit. With a group append token it appends to every instance. Pass --instance <tsk_...> to pick some. With -t/--topic it authenticates with your API token. Without it, it uses the organization 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 decides what is canceled. tsk_ cancels one task. sub_ cancels one follow-up, and the rest of the chain stays live. grptsk_ cancels 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 two counts. canceled is the number of instances that were still open. skipped is the number that had already finished and were left untouched. Canceling an already-answered task fails with task_already_completed. Canceling twice is a no-op.
| Flag | Description |
|---|---|
--reason <canceled|answered|superseded> | The reason, shown on the recipient's card. Default canceled. |
--note <TEXT> | Free-text explanation for the recipients. Encrypted with the target's key when the CLI holds it (organization vault, or the matching -p). Otherwise it is sent as plaintext, with a warning. |
--superseded-by <ID> | The replacement's id. Requires --reason superseded. |
-p, --password <VALUE> | Encrypts the note with the chain's key. Use pw@topic for a topic send and a bare pw for a send to your own devices. Without it the note is sent as plaintext, with a warning. |
--no-encrypt | Organization 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. |
-l, --link <URL> | A URL shown as an Open link button. Any scheme, deep links included. Not combinable with an input flag. |
--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 | Organization targets. |
--format <text|json> | text prints the bare id. json prints a sent line for piping into sp collect. |
--tag, --shared, --priority, --critical-volume, --no-encrypt | As on sp task. |
sp get
Read what came back. sp get offers the same filters as the MCP query tools, 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 the cursor 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 organization |
--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 organization session the commands read the organization's tasks, with 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 organization vault). Otherwise it is left as ciphertext, 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 filters the socket replay by actor.
sp search
Ranked search over everything the credential can read, 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, either instead of words or in addition to them. Output is one hit per line. Use --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). Cannot be combined with --near |
--since, --until, --member | Same as sp get |
--limit | Number of 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. Add --follow to keep streaming after the replay.
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 produce a warning and are ignored.
sp auth and sp org
sp auth login signs you in to your organization through the browser, or with a device-code flow over SSH. Force one or the other with --web / --device. The session is used for organization 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 join with the printed login code. They install the app and enter the code. No email is required. See the Organizations guide for the full walkthrough.