{"id":49882644,"url":"https://github.com/zikolach/pirelay","last_synced_at":"2026-05-15T16:01:45.725Z","repository":{"id":354745667,"uuid":"1224959923","full_name":"zikolach/pirelay","owner":"zikolach","description":"PiRelay is a Telegram/Discord/Slack bridge for Pi sessions.","archived":false,"fork":false,"pushed_at":"2026-05-14T20:55:01.000Z","size":1712,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-14T21:39:45.922Z","etag":null,"topics":["agent","ai","bot","development","discord","pi","relay","slack","telegram"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zikolach.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-29T20:02:00.000Z","updated_at":"2026-05-14T16:23:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zikolach/pirelay","commit_stats":null,"previous_names":["zikolach/pirelay"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/zikolach/pirelay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zikolach%2Fpirelay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zikolach%2Fpirelay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zikolach%2Fpirelay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zikolach%2Fpirelay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zikolach","download_url":"https://codeload.github.com/zikolach/pirelay/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zikolach%2Fpirelay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33071582,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","ai","bot","development","discord","pi","relay","slack","telegram"],"created_at":"2026-05-15T16:00:42.785Z","updated_at":"2026-05-15T16:01:45.717Z","avatar_url":"https://github.com/zikolach.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PiRelay\n\n![PiRelay logo](docs/logo.png)\n\n**PiRelay** is a messenger bridge for live Pi sessions.\n\nIt pairs Telegram, Discord, or Slack chats with the exact Pi session you are using, then lets you monitor progress, receive completion notifications, and send prompts or control commands back into that same local Pi session.\n\nThe npm package is `pirelay`. The canonical Pi command family is `/relay ...`; the old `/telegram-tunnel ...` namespace has been removed.\n\n## What PiRelay does\n\nPiRelay keeps Pi local-first. It does not replace the terminal UI or start a hidden standalone agent. Instead, it registers the current Pi session with a local relay runtime and lets an authorized messenger chat steer that session remotely.\n\nTypical flow:\n\n1. start working in Pi locally\n2. configure one messenger with `/relay setup \u003ctelegram|discord|slack\u003e`\n3. run `/relay connect \u003cmessenger\u003e`\n4. scan/open the QR/deep link or copy the pairing command\n5. approve or trust the remote user locally if prompted\n6. receive updates and send prompts while the Pi session stays usable locally\n\n## Supported messengers\n\n| Messenger | Current support | Reliable command form |\n|---|---|---|\n| Telegram | private bot chat, media bridge, inline actions, multi-session broker | `/status`, `/full`, `/progress verbose` |\n| Discord | live DM-first bot runtime, optional explicit guild/channel control | `relay status`, `relay full`, `relay progress verbose` |\n| Slack | Socket Mode live runtime, App Home DM pairing, optional explicit channel/thread control | `pirelay status`, `pirelay full`, `pirelay progress verbose` |\n\nDiscord and Slack avoid leading-slash instructions because those platforms can route slash text to app slash-command handlers instead of ordinary message events.\n\n## Features\n\n### Pairing and session routing\n\n- pairs Telegram private chats, Discord DMs, Slack App Home DMs, or explicitly enabled Slack/Discord channels with the current Pi session\n- uses expiring, single-use pairing payloads or mobile-friendly PIN commands\n- closes the local pairing QR/dialog automatically when pairing completes\n- shows local pairing completion notifications consistently across messengers\n- restores non-secret binding metadata when a Pi session resumes with `pi --continue`\n- supports multiple concurrently registered Pi sessions through a local broker\n- exposes session selection commands when more than one session is paired to the same chat\n\n### Remote prompting and control\n\n- plain authorized text becomes a Pi prompt when the session is idle\n- while Pi is busy, prompts are queued as follow-ups by default or steered explicitly when configured\n- Telegram photos and supported image documents become Pi image prompts when the selected model supports image input\n- Discord and Slack expose the same command semantics with platform-specific text prefixes\n- Slack channel/thread prompts are gated by `slack.allowChannelMessages` and an explicit channel pairing\n\n### Progress and activity feedback\n\n- Telegram uses Bot API chat actions such as `typing...` where available\n- Discord uses typing activity where available\n- Slack uses a `:thinking_face:` reaction on accepted prompts when the app has `reactions:write`\n- Slack falls back to a thread-aware ephemeral `Pi is working…` message when reactions are unavailable\n- progress notifications support `quiet`, `normal`, `verbose`, and `completion-only`\n- `/recent`, `relay recent`, and `pirelay recent` show recent safe activity\n\n### Long-output and answer workflow\n\n- sends summaries, full output, and latest assistant text with platform-aware chunking/fallbacks\n- detects structured choices and question sets in the latest completed assistant output\n- supports Telegram inline buttons for recognized choices\n- supports direct short option replies and explicit `answer` drafts\n- adds one-click **Show in chat** and **Download .md** actions where Telegram supports them\n- reformats Markdown tables into mobile-friendly blocks for Telegram chat\n\n### Image bridge\n\n- accepts Telegram photos and image documents (`image/jpeg`, `image/png`, `image/webp` by default) after authorization\n- uses the Telegram caption as prompt text, or a safe image-inspection fallback for image-only messages\n- rejects image prompts when the selected Pi model does not support image input\n- exposes latest tool-result image outputs and safe latest-turn workspace image references with `/images`\n- supports `/send-image \u003crelative-path\u003e` for explicit delivery of validated workspace PNG/JPEG/WebP files\n- does not browse arbitrary workspace files or automatically echo local/remote input images\n\n### Safety and resilience\n\n- authorization happens before prompt injection, media download, callbacks, or control actions\n- pairing links/PINs expire and are single-use\n- raw bot tokens, signing secrets, app tokens, hidden prompts, tool internals, and transcripts are not persisted in relay state\n- config writer stores secret env var names such as `tokenEnv`, `signingSecretEnv`, and `appTokenEnv`, not resolved secret values\n- Telegram/Discord/Slack API limitations are handled with chunking, retry/backoff, explicit unsupported-feature messages, and offline/busy responses\n\n## Requirements\n\n- Pi installed and working\n- Node.js compatible with this package (`\u003e=20.6.0`)\n- at least one configured messenger:\n  - Telegram bot token from BotFather\n  - Discord bot token and application id\n  - Slack bot token, signing secret, and usually Socket Mode app token\n\n## Installation\n\nInstall from npm when available:\n\n```bash\npi install npm:pirelay\n```\n\nInstall from GitHub or a local checkout during development:\n\n```bash\npi install https://github.com/zikolach/pirelay\npi install /absolute/path/to/pirelay\n```\n\nFor a one-off run without adding it to settings:\n\n```bash\npi -e npm:pirelay\n```\n\n## Quick start\n\n### 1. Choose and configure a messenger\n\nRun the setup wizard inside Pi:\n\n```text\n/relay setup telegram\n/relay setup discord\n/relay setup slack\n```\n\nThe setup wizard is secret-safe and tab-based. It shows diagnostics, env snippets, config snippets, links/QR guidance, troubleshooting, and for Slack a copyable app manifest.\n\nUseful setup actions:\n\n- `c` copies placeholder env exports to the system clipboard, with Pi editor fallback\n- `w` writes/updates canonical config from currently defined env vars without storing secret values\n- Slack-only `m` copies a secret-free Slack app manifest\n\n### 2. Pair the current session\n\n```text\n/relay connect telegram [name]\n/relay connect discord [name]\n/relay connect slack [name]\n```\n\nThen follow the messenger-specific instructions:\n\n- Telegram: scan/open the bot deep link and press **Start**\n- Discord: DM the bot with `relay pair 123-456`\n- Slack: open the App Home DM and send `pirelay pair 123-456`, or invite the app to a channel and paste the command there after enabling `slack.allowChannelMessages`\n\nIf the remote identity is not already allow-listed or trusted, Pi asks locally whether to allow, trust, or deny the pairing.\n\n### 3. Check status remotely\n\n```text\n/status               # Telegram\nrelay status          # Discord\npirelay status        # Slack\n```\n\n## Messenger setup details\n\n### Telegram\n\nCreate a bot with [BotFather](https://core.telegram.org/bots/features#botfather), then set one of:\n\n```bash\nexport PI_RELAY_TELEGRAM_BOT_TOKEN=\"123456789:AA...\"\n# legacy alias still supported:\nexport TELEGRAM_BOT_TOKEN=\"123456789:AA...\"\n```\n\nOptional allow-list:\n\n```bash\nexport PI_RELAY_TELEGRAM_ALLOW_USER_IDS=\"123456789\"\n```\n\n### Discord\n\nCreate an application/bot in the [Discord Developer Portal](https://discord.com/developers/docs/quick-start/getting-started).\n\nRecommended env:\n\n```bash\nexport PI_RELAY_DISCORD_BOT_TOKEN=\"...\"\nexport PI_RELAY_DISCORD_APPLICATION_ID=\"123456789012345678\"\nexport PI_RELAY_DISCORD_ALLOW_USER_IDS=\"123456789012345678\"\n```\n\nEnable **Message Content Intent** for plain DM prompts and `relay \u003ccommand\u003e` text controls. Invite with `bot` scope and `permissions=0` for DM-first operation. Guild/channel control requires explicit config.\n\n### Slack\n\nCreate a Slack app at \u003chttps://api.slack.com/apps\u003e. `/relay setup slack` can copy a ready-to-paste app manifest.\n\nRecommended Socket Mode env:\n\n```bash\nexport PI_RELAY_SLACK_BOT_TOKEN=\"xoxb-...\"\nexport PI_RELAY_SLACK_SIGNING_SECRET=\"...\"\nexport PI_RELAY_SLACK_APP_TOKEN=\"xapp-...\"\nexport PI_RELAY_SLACK_APP_ID=\"A0123456789\"\nexport PI_RELAY_SLACK_WORKSPACE_ID=\"T0123456789\"\nexport PI_RELAY_SLACK_ALLOW_USER_IDS=\"U0123456789\"\n```\n\nSlack app requirements:\n\n- Socket Mode enabled for local Pi usage\n- App Home → Messages Tab enabled so users can DM the app\n- `message.im` event for App Home DMs\n- bot scopes including `chat:write`, `im:history`, `im:read`, `reactions:write`, and `files:write` for image/file delivery\n- reinstall the app after scope or App Home changes\n\nSlack channel/thread control is explicit:\n\n```bash\nexport PI_RELAY_SLACK_ALLOW_CHANNEL_MESSAGES=true\n```\n\nThen invite the app to the channel and pair in that channel/thread with `pirelay pair \u003cpin\u003e`.\n\n## Local Pi commands\n\n| Command | Purpose |\n|---|---|\n| `/relay setup \u003ctelegram\\|discord\\|slack\u003e` | open setup wizard or show headless setup guidance |\n| `/relay connect \u003ctelegram\\|discord\\|slack\u003e [name]` | create an expiring pairing flow for the current session |\n| `/relay doctor` | diagnose configured relay channels, credentials, allow-lists, and config/state permissions |\n| `/relay status` | show local relay status for the current session |\n| `/relay send-file \u003ctelegram\\|discord\\|slack\\|messenger:instance\\|all\u003e \u003crelative-path\u003e [caption]` | send an explicit safe workspace file/artifact to paired messenger chat(s) |\n| `/relay trusted` | list locally trusted relay users |\n| `/relay untrust \u003cmessenger\u003e \u003cuserId\u003e` | revoke local relay trust |\n| `/relay disconnect` | locally disconnect the current Pi session from all paired messenger bindings |\n\n## Remote messenger commands\n\n| Purpose | Telegram | Discord | Slack |\n|---|---|---|---|\n| help | `/help` | `relay help` | `pirelay help` |\n| status dashboard | `/status` | `relay status` | `pirelay status` |\n| list sessions | `/sessions` | `relay sessions` | `pirelay sessions` |\n| select session | `/use \u003csession\u003e` | `relay use \u003csession\u003e` | `pirelay use \u003csession\u003e` |\n| forget offline session | `/forget \u003csession\u003e` | `relay forget \u003csession\u003e` | `pirelay forget \u003csession\u003e` |\n| one-shot prompt | `/to \u003csession\u003e \u003cprompt\u003e` | `relay to \u003csession\u003e \u003cprompt\u003e` | `pirelay to \u003csession\u003e \u003cprompt\u003e` |\n| progress mode | `/progress \u003cmode\u003e` | `relay progress \u003cmode\u003e` | `pirelay progress \u003cmode\u003e` |\n| current progress mode | `/progress` | `relay progress` | `pirelay progress` |\n| alias current session | `/alias \u003cname\\|clear\u003e` | `relay alias \u003cname\\|clear\u003e` | `pirelay alias \u003cname\\|clear\u003e` |\n| recent activity | `/recent` or `/activity` | `relay recent` or `relay activity` | `pirelay recent` or `pirelay activity` |\n| latest summary | `/summary` | `relay summary` | `pirelay summary` |\n| full output | `/full` | `relay full` | `pirelay full` |\n| latest images | `/images` | `relay images` | `pirelay images` |\n| send workspace image | `/send-image \u003cpath\u003e` | `relay send-image \u003cpath\u003e` | `pirelay send-image \u003cpath\u003e` |\n| send safe workspace file to requester | `/send-file \u003cpath\u003e [caption]` | `relay send-file \u003cpath\u003e [caption]` | `pirelay send-file \u003cpath\u003e [caption]` |\n| steer active run | `/steer \u003ctext\u003e` | `relay steer \u003ctext\u003e` | `pirelay steer \u003ctext\u003e` |\n| queue follow-up | `/followup \u003ctext\u003e` | `relay followup \u003ctext\u003e` | `pirelay followup \u003ctext\u003e` |\n| abort current run | `/abort` | `relay abort` | `pirelay abort` |\n| compact context | `/compact` | `relay compact` | `pirelay compact` |\n| pause delivery | `/pause` | `relay pause` | `pirelay pause` |\n| resume delivery | `/resume` | `relay resume` | `pirelay resume` |\n| disconnect binding | `/disconnect` | `relay disconnect` | `pirelay disconnect` |\n\n`quiet`, `normal`, `verbose`, and `completion-only` are valid progress modes. In quiet mode PiRelay keeps terminal notifications concise and offers `/full`/download actions for the full answer. In normal, verbose, and completion-only modes it sends the full final answer, splitting by paragraphs within platform limits and falling back to a Markdown document when an adapter supports files and the output is too large for a reasonable chat burst.\n\nRemote `/disconnect` is scoped to the requesting chat/conversation only: it revokes that Telegram, Discord, or Slack binding and suppresses future session output/buttons there, without disconnecting other messengers that remain paired to the same Pi session. Local `/relay disconnect` is broader and disconnects the current session from all paired messenger bindings.\n\nRemote `send-file` is requester-scoped: an authorized Telegram/Discord/Slack user may request a workspace-relative, validated path and PiRelay uploads it only back to that same conversation/thread. Targeted fan-out remains local-only via `/relay send-file \u003cmessenger|messenger:instance|all\u003e \u003crelative-path\u003e [caption]`; remote forms must not include messenger targets such as `all` or `slack`.\n\n## Prompt routing behavior\n\n### When Pi is idle\n\nA normal authorized message is delivered as a standard Pi prompt. For Slack channel/thread bindings, the sender must be the paired user, the current active selection, or must explicitly address the local machine bot depending on shared-room mode.\n\nExpected feedback:\n\n- Telegram: bot chat action such as `typing...`\n- Discord: typing activity where supported\n- Slack: `:thinking_face:` reaction, or ephemeral fallback if reactions are unavailable\n\n### When Pi is busy\n\nPrompts use the configured busy delivery mode. Default is `followUp`.\n\nExplicit controls:\n\n- Telegram: `/steer \u003ctext\u003e` or `/followup \u003ctext\u003e`\n- Discord: `relay steer \u003ctext\u003e` or `relay followup \u003ctext\u003e`\n- Slack: `pirelay steer \u003ctext\u003e` or `pirelay followup \u003ctext\u003e`\n\n## Guided answer flow\n\nPiRelay supports structured answering when the latest completed assistant output contains a reliable choice set or question set.\n\nExamples:\n\n```text\nChoose:\n1. sync specs now\n2. archive without syncing\n```\n\nTelegram can show inline options where available. All messengers can use short unambiguous replies or `answer` where supported by the command parser. Long or ambiguous messages are treated as normal prompts unless PiRelay can safely identify them as answers.\n\nUse `cancel` to leave an active answer flow.\n\n## Multi-session behavior\n\nPiRelay can track multiple live Pi sessions through a local broker. Pair sessions with labels when useful:\n\n```text\n/relay connect telegram docs\n/relay connect discord api\n/relay connect slack release\n```\n\nUse `/sessions`, `relay sessions`, or `pirelay sessions` to list targets. Use `/use`, `relay use`, or `pirelay use` to select an active target. Use `/to`, `relay to`, or `pirelay to` for one-shot prompts without switching sessions.\n\nIf the same bot/app is configured on multiple machines, use one ingress owner plus broker federation so other machines register routes instead of polling the same messenger concurrently.\n\n## Configuration\n\nCanonical config lives at:\n\n```text\n~/.pi/agent/pirelay/config.json\n```\n\nExample:\n\n```json\n{\n  \"relay\": {\n    \"machineId\": \"laptop\",\n    \"stateDir\": \"~/.pi/agent/pirelay\",\n    \"brokerGroup\": \"personal\"\n  },\n  \"defaults\": {\n    \"pairingExpiryMs\": 300000,\n    \"busyDeliveryMode\": \"followUp\"\n  },\n  \"messengers\": {\n    \"telegram\": {\n      \"default\": {\n        \"enabled\": true,\n        \"tokenEnv\": \"PI_RELAY_TELEGRAM_BOT_TOKEN\"\n      }\n    },\n    \"discord\": {\n      \"default\": {\n        \"enabled\": true,\n        \"tokenEnv\": \"PI_RELAY_DISCORD_BOT_TOKEN\",\n        \"applicationId\": \"123456789012345678\"\n      }\n    },\n    \"slack\": {\n      \"default\": {\n        \"enabled\": true,\n        \"tokenEnv\": \"PI_RELAY_SLACK_BOT_TOKEN\",\n        \"signingSecretEnv\": \"PI_RELAY_SLACK_SIGNING_SECRET\",\n        \"appTokenEnv\": \"PI_RELAY_SLACK_APP_TOKEN\",\n        \"appId\": \"A0123456789\",\n        \"workspaceId\": \"T0123456789\"\n      }\n    }\n  }\n}\n```\n\nRecommended permissions:\n\n```bash\nchmod 600 ~/.pi/agent/pirelay/config.json\n```\n\nLegacy Telegram tunnel config/state under `~/.pi/agent/telegram-tunnel` and `PI_TELEGRAM_TUNNEL_*` env vars are migration fallbacks only. Active non-secret bindings migrate to `telegram:default`; active pairing codes are not copied.\n\nFor more detail, see [docs/config.md](docs/config.md).\n\n## Security notes\n\nPlease treat PiRelay as a convenience/control channel, not a secret-safe transport.\n\nImportant points:\n\n- Telegram Bot API traffic is not end-to-end encrypted\n- Discord and Slack app/bot traffic follows their platform security model\n- bot tokens and app secrets must stay in env vars or local config, never in session history\n- pairing links/PINs are single-use and expire quickly\n- allow-lists and local trust should be configured before broad remote control\n- Slack/Discord channel control is disabled unless explicitly enabled\n- redaction patterns can scrub common secret shapes before remote text/document delivery\n- image files can contain visual secrets; PiRelay requires explicit image actions before sending latest image outputs/files remotely\n\n## Troubleshooting\n\n### Setup wizard cannot write config\n\nUse `c` to copy env snippets, export real values in your shell/profile, restart Pi or reload the environment, then run setup again and press `w`. Invalid boolean env values such as `sometimes` are rejected instead of silently skipped.\n\n### Telegram does not respond\n\n- run `/relay setup telegram`\n- verify `PI_RELAY_TELEGRAM_BOT_TOKEN` or `TELEGRAM_BOT_TOKEN`\n- confirm the bot exists and the username resolves in Telegram\n\n### Discord does not receive messages\n\n- run `/relay setup discord`\n- verify the bot token and application id\n- enable Message Content Intent for DM text prompts\n- ensure the bot and user can share a DM path, often by sharing a server\n\n### Slack says sending messages to the app is turned off\n\n- enable App Home → Messages Tab → Allow users to send messages to your app\n- add the `message.im` event\n- add `im:history`, `im:read`, `reactions:write`, and `files:write` scopes\n- reinstall the Slack app after changing settings\n\n### Slack still shows `Pi is working…` instead of a reaction\n\nThe Slack bot token likely lacks `reactions:write`/`files:write` or the app was not reinstalled after adding scopes. Add the needed scope, reinstall, then restart/reload PiRelay.\n\n### Pairing expired or points to the wrong session\n\nRun `/relay connect \u003cmessenger\u003e` again from the Pi session you want to control. Pairing commands are single-use and scoped to the selected messenger/session.\n\n### `pi --continue` and pairing\n\nIf you continue the same Pi session and PiRelay uses the same state directory, you usually do not need to pair again. Run the remote status command to verify. Reconnect only if the session key changed, state was deleted, or the binding was disconnected/revoked.\n\n### You changed code but behavior looks stale\n\nRestart the running Pi session/extension process. If a detached broker is still running, stop it before retesting:\n\n```bash\npkill -f 'extensions/relay/broker/process.js'\n```\n\nThen restart Pi or reload the package from the updated checkout.\n\n## Architecture overview\n\nPiRelay consists of:\n\n- a Pi extension at `extensions/relay/`\n- a companion skill at `skills/relay/`\n- adapter runtimes for Telegram, Discord, and Slack\n- a local broker process for multi-session routing\n- persisted local state under `~/.pi/agent/pirelay/`\n\nThe extension listens to Pi lifecycle events, tracks task state, publishes route updates, and injects authorized messenger input back into the session. Adapter modules own platform-specific I/O while shared core helpers own routing, authorization, formatting, media safety, redaction, progress, and setup metadata.\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm test\n```\n\nOpenSpec validation for active changes:\n\n```bash\nopenspec validate \u003cchange\u003e --strict\n```\n\nManual smoke-test steps and release notes live in:\n\n- [docs/testing.md](docs/testing.md)\n- [docs/releasing.md](docs/releasing.md)\n\n## Current limitations\n\n- native Slack typing bubbles are not available through supported Slack Web API / Socket Mode, so PiRelay uses reactions plus ephemeral fallback\n- Slack/Discord file upload delivery is still limited compared with Telegram text/image support\n- Telegram group-chat support is not the default runtime mode\n- no end-to-end encryption beyond each messenger platform's bot/app transport\n- answer workflow depends on conservative structured-output detection\n- image prompts require a Pi model that supports image input\n- image transfer is bounded by configured size and MIME-type limits\n- `/images` only considers captured image outputs and obvious image file paths mentioned in the latest Pi turn\n\n## Related files\n\n- adapter/runtime details: [docs/adapters.md](docs/adapters.md)\n- configuration reference: [docs/config.md](docs/config.md)\n- manual testing checklist: [docs/testing.md](docs/testing.md)\n- Pi skill entrypoint: [skills/relay/SKILL.md](skills/relay/SKILL.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzikolach%2Fpirelay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzikolach%2Fpirelay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzikolach%2Fpirelay/lists"}