# Pushyou receiver Forward replies and approved tasks from Pushyou to a program on your computer, then return the result to the same conversation. Requires **Node 22 or later**. Use a custom handler or the built-in Codex adapter. Codex mode creates a dedicated session for each conversation; it does not attach to an existing Desktop chat. ## Install and verify a reply The web connection guide provides the archive and commands for your environment. Install the downloaded package with `npm install --global ./pushyou-receiver-0.4.0.tgz`, or use `npm install --global ./receiver` from this repository. The package is not published to the public npm registry. In Pushyou connection settings, issue a program key with the **Conversation access** profile, limited to your chosen conversation. The receiver uses REST and requires this key separately from MCP OAuth. The primary account key also works, but individual program keys let you revoke one receiver independently. Commands below use bash or zsh on macOS/Linux. Windows commands and process-tree termination have not been verified. Replace the API URL with the value from your connection guide. ```sh export PUSHYOU_API_URL='https://YOUR_API_BASE/pushyouApi' printf 'Program API key: ' >&2 IFS= read -r -s PUSHYOU_API_KEY printf '\n' >&2 export PUSHYOU_API_KEY pushyou-receiver doctor --conversation my-project pushyou-receiver run --conversation my-project \ --state-dir './.pushyou-receiver/my-project' --start now --echo ``` Send a **new reply** in that conversation after starting. Receiving the same text back verifies the round trip. Echo is a transport test; it does not execute AI or complete real tasks. Stop with Ctrl+C. Closing the terminal stops the receiver unless you configure a service below. To use a private key file, set `PUSHYOU_API_KEY_FILE` instead of `PUSHYOU_API_KEY`. Do not set both. Store one key line, with POSIX file permissions `600`. Keep keys out of arguments, repositories and logs. ## Connect Codex Install Codex CLI and complete `codex login` on this computer. Stop the echo receiver, then run these commands in the terminal with your API URL and key configured: ```sh pushyou-receiver doctor --conversation my-project \ --agent codex --project '/Users/me/projects/my-app' pushyou-receiver run --conversation my-project \ --state-dir './.pushyou-receiver/my-project-codex-read-only' --start now \ --agent codex --project '/Users/me/projects/my-app' ``` Send a new request in the same conversation to receive Codex's final response. The default sandbox is `read-only`. For file editing, explicitly use `--sandbox workspace-write` with a new state directory. The receiver offers no permission-bypass option. `--model` is optional; omitting it uses the CLI default. Use `--codex-bin` to specify the executable. Verified against Codex CLI **0.153.4**. `doctor` checks local login, options and Pushyou permissions without invoking a model. The adapter uses `codex exec --json --ignore-user-config`, so it does not import your personal model/MCP configuration. Managed policies and project instructions may still apply. It follows the [official non-interactive execution and session resume protocol](https://learn.chatgpt.com/docs/non-interactive-mode). - The same account, conversation, project, sandbox, model and state directory resume the saved session ID. The receiver never uses `--last` to select an unrelated session. Changing the Codex login/session storage location also requires new state. - Use a new state directory when changing the project, model or permissions. Do not operate the same session concurrently from another CLI. Existing Desktop session import and a Claude adapter are not supported. - The final response is saved before sending and ACK. If delivery fails, the saved result is resent without calling Codex again, even if local Codex login subsequently fails. - Interrupted execution, timeout, protocol errors or responses over 8,000 characters stop for explicit review. The receiver does not infer completion from CLI session files or automatically rerun uncertain work. - Attachments currently provide metadata only; the adapter does not send original photos or videos to the model. Execution uses your computer and Codex account, subject to that provider's usage, pricing and retention. Requests and project content read by Codex may be sent to the model provider. Pushyou key variables are excluded from the Codex process. Local receiver state includes the Codex session ID; Codex's own logs may retain requests, results and work. Deleting a Pushyou account does not delete provider or local Codex records. ## Connect your own program ```sh pushyou-receiver run --conversation my-project \ --state-dir './.pushyou-receiver/production' --start now \ -- node ./my-handler.mjs ``` The handler runs once per event. Arguments after `--` are a fixed executable and argument array, not a shell expression. Conversation content arrives as one JSON line on stdin: ```json {"version":1,"account_id":"…","conversation_id":"my-project","idempotency_key":"stable-sha256","event":{"id":"…","sequence":1,"type":"message","text":"Please review this","acknowledged":false}} ``` Events contain replies, confirmed actions and uploaded-file metadata. Original media bytes are not automatically downloaded. Use `idempotency_key` in the external system to prevent duplicate side effects. Your handler owns its API calls, file changes and other external effects. After completing work, print one of these objects to stdout and exit with code 0. Write diagnostics to stderr; the receiver does not copy them into its default logs. Pushyou key environment variables are not passed to handlers. ```json {"reply":{"text":"Review complete.","notify":true}} ``` ```json {"ack":true} ``` `reply` supports `text` (up to 8,000 characters), `card`, `attachment_ids` and `notify`. Upload attachments first. The receiver assigns message IDs. Return `{ack:true}` only after the work is actually complete and no reply is needed. ## Start position, restart and recovery - On first use, specify `--start now` (only events after the current server sequence) or `--start pending` (include existing unfinished requests). - Existing state resumes its saved cursor regardless of `--start`. Changing the API address, account, conversation, execution command or working directory is rejected. Key rotation preserves state for the same account and conversation. - Results are saved locally before sending a stable-ID reply and ACK. After a delivery or ACK failure, restarting resends the stored result without rerunning the handler. - If the process exits during execution, times out or returns invalid output, the external result may be unknown. The event is preserved and automatic execution stops. Inspect the external system first. **Only if rerunning is safe**, add `--retry-event ID` to the original command for an ordinary message/action event. If you completed the work manually or delivered the saved reply separately, resolve that event explicitly: ```sh pushyou-receiver resolve --conversation my-project \ --state-dir './.pushyou-receiver/production' --event EVENT_ID --handled ``` `--once` processes one page and exits. `--poll-ms` accepts 500–60000 (default 3000); `--timeout-ms` accepts 1000–86400000 (default 300000). Network errors, 429 and server errors retry with backoff up to 60 seconds. Invalid keys, permissions, events and results are not silently skipped. ## Status and coordination ```sh pushyou-receiver status --state-dir './.pushyou-receiver/production' ``` Status includes the local process lock, last poll time, cursor and unfinished event IDs, without reply bodies or keys. The conversation header also shows recent receiver reports: starting, waiting, processing, retrying, paused or stopped. Active processes report every 15 seconds; after 90 seconds without a report, the response becomes unconfirmed. Failed status queries are never shown as running. Ordinary REST/MCP calls do not count as receiver reports. Each run gets a distinct `receiver_instance`; compare its short ID with the app if programs share a name. Up to eight recent runs per conversation are retained, with state, report sequence/time and the authenticating key's internal ID for up to one day. Reports exclude message bodies, commands, device names, paths and raw keys. Account/conversation deletion removes server-side presence. Presence is a recent report, not proof of task completion or ownership. Reporting failures appear in diagnostics while event processing and ACK continue independently. Key revocation takes effect on later requests; it cannot undo external work already in progress. One process uses each state directory. After a crash, lock expiry can take up to 30 seconds. Do not manually remove a live lock. **Local locks do not coordinate different directories or computers. Use one responder per conversation for ordinary replies/actions.** Their shared ACK does not guarantee exactly-once execution. Tasks use server claims, but still need external idempotency keys for side effects. State can include account/conversation identifiers, cursors and undelivered replies. Directories/files use POSIX `700`/`600`. Raw API keys and received conversation bodies are not stored in execution records. Delivered temporary replies are removed; crash leftovers or backups may remain. Keep state out of Git and shared drives. Deleting a Pushyou account cannot remotely remove local files; resolve work and stop the receiver before removing them yourself. ## Approval-based tasks (0.4.0) API tasks and conversation actions arrive after the user reviews and approves them in the app or web. The receiver claims a server execution lease before starting a handler and renews it while running. Claim tokens are saved before transmission; final results are saved before completion and ACK. Restarting after a delivery failure sends the stored result. Task inputs have `event.type: "task"`, with approved inputs and an `attempt`. Ordinary `{reply:…}` or `{ack:true}` cannot complete a task. Return an observed final result: ```json {"task":{"status":"succeeded","result":"Report generated successfully."}} ``` Use `succeeded`, `failed` or `needs_review`. Uncertain results require `needs_review`. Execution interrupted by termination or lease-renewal failure never automatically reruns. Review external results in the task detail, then resolve or request a retry. Tasks cannot use `--retry-event`; `resolve --handled` reports only while ownership can be verified. After lease expiry, use the app. Echo records a failure stating that no task was executed. When upgrading from 0.3.x, stop the receiver and restart with the same state directory. It checks previously missed task events once, without replaying ordinary replies skipped by `--start now`. Use an account key or a program key with `messages:read/write` and `events:read/ack` for the conversation; legacy conversation-only keys cannot handle tasks. Runnable examples included under `examples/`: - `request-task.mjs`: create an approval request with `--template deploy|incident|report --conversation ROOM --id STABLE_ID`. Reuse the same ID when retrying the same creation request. - `workflow-handler.mjs`: count top-level project entries, inspect basic runtime metrics, or validate deployment inputs in a **dry run**. It does not deploy infrastructure. - `n8n-deploy-approval.json`: import an inactive manual-trigger workflow, set the API URL, conversation and request ID, then select HTTP Header Auth credentials. Verify execution in your own n8n installation. - `webhook-inbox.mjs`: verify signatures/timestamps, durably save events and then return 200. It listens on loopback and needs your HTTPS proxy. It does not execute external work. See **Task and automation examples** in the web connection guide for the API sequence. ## Keep running after closing the terminal `pushyou-receiver-service` generates a macOS launchd or Linux systemd user service. It **only creates files**. Review them before registering and starting the service. Stop the terminal receiver first and make sure no other service handles the same ordinary replies. Store the key as one line in a separate private file. Save this configuration, replacing all paths with real absolute paths. Use `chmod 600` for the configuration/key and `chmod 700` for their private parent directory. `command` is an executable/argument array, not a shell string. ```json { "api_url": "https://YOUR_API_BASE/pushyouApi", "conversation_id": "my-project", "key_file": "/Users/me/.config/pushyou/program-key", "state_dir": "/Users/me/.local/state/pushyou/my-project", "start": "now", "cwd": "/Users/me/projects/my-app", "command": ["/opt/homebrew/bin/node", "/Users/me/projects/my-app/handler.mjs"] } ``` For Codex, replace `cwd`/`command` with `agent:"codex"`, absolute `project` and `codex_bin` paths, and explicit `sandbox:"read-only"` or `"workspace-write"`. `model` is optional. Complete Codex login as the same operating-system user. macOS: ```sh mkdir -p "$HOME/Library/LaunchAgents" pushyou-receiver-service --platform launchd \ --config "$HOME/.config/pushyou/config.json" \ --output "$HOME/Library/LaunchAgents/com.pushyou.pushyou-receiver.plist" plutil -lint "$HOME/Library/LaunchAgents/com.pushyou.pushyou-receiver.plist" launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.pushyou.pushyou-receiver.plist" launchctl print "gui/$(id -u)/com.pushyou.pushyou-receiver" # Stop and unregister launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.pushyou.pushyou-receiver.plist" ``` Linux (also update configuration paths for Linux): ```sh mkdir -p "$HOME/.config/systemd/user" pushyou-receiver-service --platform systemd \ --config "$HOME/.config/pushyou/config.json" \ --output "$HOME/.config/systemd/user/pushyou-receiver.service" systemctl --user daemon-reload systemctl --user enable --now pushyou-receiver.service systemctl --user status pushyou-receiver.service # Stop and disable automatic startup systemctl --user disable --now pushyou-receiver.service ``` User services follow OS login/session policies. Use an always-on computer or server; a sleeping or powered-off computer cannot receive work. Network errors retry, but uncertain external work, invalid credentials and configuration errors also stop automatic supervisor restarts. Inspect `pushyou-receiver status` and the app's receiver/task state, resolve the cause, then restart explicitly. Regenerate service files if the Node or package installation path changes. ## Request revisions and review receipts — 0.5.0 The receiver now includes the approved request snapshot in handler input as `request` (title, body, inputs, content revision and approved version). Use this exact approved content when performing the operation. A completion may return `{task:{status:"succeeded",result:"Actual outcome",result_links:[{label:"Open result",url:"https://example.com/result"}]}}`. URLs must be HTTPS and must not contain credentials. A message event with `task_intent:"request_changes"` requests a draft revision only. Its input includes the original request. Return `{revision:{body:"Full revised content",title:"Optional title"}}`, with optional `fields` and `inputs`. Do not publish or execute the task at this step. The receiver writes this result durably before calling the revision endpoint, and ACKs only after it is accepted. A plain reply or ACK cannot satisfy a revision request. Run one responder per conversation for these ordinary message events; task claims do not coordinate draft generation across machines. Use `examples/request-task.mjs --template check --conversation ROOM --id STABLE_ID` with the bundled `examples/workflow-handler.mjs` to exercise the complete loop: request → changes → new draft → owner approval → result. Stop any echo receiver first and use a separate test conversation. The example echoes the requested text into a new draft and returns the approved text as a receipt. It does not run AI or publish anything. Keep the request ID stable across retries; choose a new ID for a new check.