@zephyrdeng/mot0.4.1
Slack gateway for the pi coding agent (fork of pi-tag)
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
Mot
The pi coding agent in your Slack — sessions, files, voice, and scheduled tasks from any channel, DM, or phone.
Mot is a fork of pi-tag by @Crokily. See Differences from pi-tag and Migrating from pi-tag.
What it does
Mot runs a Slack app on the machine where pi is installed and bridges Slack conversations to it. Chat with pi in DMs, channels, and threads — from your desktop or your phone. Each channel is its own persistent session with its own working directory and model; pi reads the files you drop into Slack and sends files back; recurring tasks run on a schedule; an interactive /mot panel switches models and thinking levels with a click.
The app uses Socket Mode: the gateway opens an outbound WebSocket, so no public URL or inbound ports are needed.
Features
- Per-channel pi sessions — each registered channel or DM maps to its own session, working directory, model, and thinking level
- Files both directions — drop a file in for pi, ask pi to send files back (up to 10 per message)
- Voice messages — Slack voice clips and audio attachments transcribed to text via ZenMux ASR (opt-in,
ZENMUX_API_KEY) - Scheduled tasks — cron schedules and one-shot reminders, asked for in plain language and driven by
mot task - Interactive
/motpanel — ephemeral control panel: session status, model and thinking-level dropdowns, New session / Stop - Mobile-friendly — Slack's mobile app is the client; kick off a task from your phone, read the result later
- Crash-safe queue — every event is acknowledged and written to a SQLite queue before processing
Requirements
- Node.js ≥ 22.19
- pi on
PATH, with login completed (~/.pi/agent/auth.json) — Mot drives your existing install - A Slack workspace where you can create apps (free plan is fine)
- Linux, macOS, or Windows
Quick start
npm install -g @zephyrdeng/mot
Create the Slack app — api.slack.com/apps → Create New App → From a manifest → pick your workspace → paste
manifest.yaml. The command is/mot.Get the two tokens — OAuth & Permissions → Install to Workspace → copy the Bot User OAuth Token (
xoxb-…); Basic Information → App-Level Tokens → create one with theconnections:writescope (xapp-…).Run the setup wizard — it checks prerequisites, takes both tokens, lets you pick an access policy, and offers to install a background service:
mot setup # or: npx @zephyrdeng/mot@latest setup mot daemon install # skip if the wizard already installed the servicePrefer a config file by hand? Copy
.env.exampleto your config path (see Configuration) and fill in the tokens.Talk — open a DM with the bot, or
/invite @motto a channel and register it (see Access control).
Using Mot
Talking to pi
DMs — every message reaches pi (default
DM_POLICY=open). Great from the Slack mobile app.Channels — register a channel and pi joins the conversation. In trigger-gated channels, summon it by @mentioning the bot or starting your message with its trigger name (
TRIGGER_NAME, defaultmot, matched only at the start):@mot fix the build mot summarize this threadThreads — trigger pi inside a thread and the answer lands in that thread (
REPLY_IN_THREAD=trueby default). Threads share the channel's session; they're a reply location, not a separate conversation. While pi works, the thread shows Slack's native status ("Mot is running bash…").Long answers are posted as Markdown and split at Slack's 4,000-character limit. Summoning the bot in an unregistered channel gets a short registration hint (rate-limited to once per 10 minutes per channel) instead of silence.
The /mot command
A bare /mot opens the interactive control panel (ephemeral — only you see it): session status at a glance, model and thinking-level dropdowns picked straight from pi's catalog, and New session / Stop buttons with confirmation. No subcommands or model refs to remember. Text subcommands still work for muscle memory:
| Subcommand | Description |
|---|---|
/mot |
Open the interactive panel |
/mot help |
Show text usage |
/mot status |
Show model, thinking, working directory, session info, token usage |
/mot model <ref> |
Set the channel's model (fuzzy-matched against pi's catalog) |
/mot models |
List the models pi can currently use |
/mot reset-model |
Clear the channel's model override |
/mot thinking <lv> |
Set thinking level: off / minimal / low / medium / high / xhigh |
/mot new |
Start a fresh session for this channel |
/mot stop |
Abort the current task and clear queued messages |
/mot bind [dir] |
Admins: register this channel with a working directory (name under PROJECT_ROOTS, absolute path, or the channel name by default) |
The model list comes from your installed pi and honors its enabledModels scope, so it stays in sync as pi adds or removes models.
Files, both directions
- To pi — drop a file into the chat. Mot downloads it and hands it to pi by local path, so pi can inspect, convert, or edit any file type without flooding its context.
- From pi — ask for a file and pi delivers it: pi is taught the gateway's
mot sendcommand with every message, and anyfile://reference left in a reply is automatically uploaded as a real Slack attachment. Up to 10 files per message, subject toMAX_ATTACHMENT_BYTES/MAX_TOTAL_ATTACHMENT_BYTES.
Voice messages
Drop a Slack voice clip or audio attachment (m4a/mp3/wav/flac/ogg/webm/aac) into a registered conversation and Mot transcribes it via ZenMux ASR (xiaomi/mimo-v2.5-asr, native Chinese–English code-switching) and hands the text to pi as the message. Opt-in:
# in config.env or process env
ZENMUX_API_KEY=zm-…
ZenMux only accepts wav/mp3, so non-mp3/wav input (Slack voice clips are m4a) needs ffmpeg on PATH for transcoding. Transcription is best-effort: on any failure pi still gets the file path. Without ZENMUX_API_KEY, audio attachments are skipped silently.
Scheduled tasks
Tell pi what you want, in plain language:
"Create a daily task at 9am UTC that generates a summary report" "Set a one-time reminder for the 2pm meeting today"
pi runs mot task add behind the scenes — 5-field cron for recurring tasks, an ISO 8601 datetime with --once for one-shots. Scheduled prompts go through the normal message queue, so they use the channel's model, thinking level, and working directory. Manage tasks the same way (mot task list | enable | disable | remove), by asking pi or from your terminal.
Sessions
- One session per channel. History persists across messages via
pi --continue. - Per-channel setup. Point a channel at a project with
mot register <id> <name> --cwd /path/to/repo, and give it its own model and thinking level via/mot— overrides stick to the channel. /mot newrotates the session — the old one is archived and cleaned up afterARCHIVE_RETENTION_DAYS(default 30).
Access control
Whoever can message the bot drives a coding agent with shell and filesystem access on the gateway machine — treat channel access accordingly. Two policies control who gets through:
Channels (CHANNEL_POLICY):
| Policy | Behavior |
|---|---|
allowlist |
Only manually registered channels are active. (default) |
open |
Channels auto-register on first message. No @mention needed. |
open-trigger |
Channels auto-register, but the bot only responds when summoned. |
DMs (DM_POLICY, independent of the channel policy):
| Policy | Behavior |
|---|---|
open |
DM channels auto-register on first message. (default) |
allowlist |
Only DMs registered via mot register get responses. |
disabled |
All DMs are ignored. |
Slack only delivers channel messages after the bot is invited (
/invite @mot), so evenopenmode is gated by the invite.Register channels by channel ID (
C…public,G…private,D…DM — bottom of the channel's About tab), not#name:mot register C0123456789 "team #general" --no-trigger mot register C0123456789 "team #general" --cwd /srv/repos/appGroup DMs (mpim) behave like channels and follow
CHANNEL_POLICY.EXCLUDED_CHANNELSblocks specific channel IDs from auto-registration inopen/open-triggermode.
How it works
Slack ──Socket Mode (@slack/bolt)──→ Gateway ──pi subprocess──→ Pi Agent
│ │
SQLite Session dirs
(message queue) (per channel)
Mot does not embed or replace pi — it shells out to your binary (PI_BIN or PATH), which reuses your own login and model access. Each message runs as pi --session-dir <dir> --continue -p <message>. Every Slack event is acknowledged immediately and written to the SQLite queue before processing, so a crash or restart never loses an accepted message. Channels are processed serially; global parallelism is capped by MAX_CONCURRENCY.
Running as a daemon
The setup wizard offers to install a background service; you can also manage it manually:
mot daemon install | start | status | logs | stop | uninstall
- Linux — systemd user service (
mot). Headless server? Enable lingering:sudo loginctl enable-linger $USER - macOS — launchd user agent (
com.mot) - Windows — no daemon management yet; run
mot startin a terminal or use Task Scheduler
Run exactly one gateway instance per Slack app — Socket Mode load-balances events across connections, so a second instance silently takes half your messages.
Configuration
mot setup generates the config file for you; mot status shows its path (Linux: ~/.config/mot/config.env, macOS: ~/Library/Application Support/mot/config.env, Windows: %APPDATA%\mot\config.env). The MOT_CONFIG environment variable overrides the location. After changes, restart: mot daemon stop && mot daemon start.
| Variable | Default | Description |
|---|---|---|
SLACK_BOT_TOKEN |
(required) | Bot User OAuth Token (xoxb-…) |
SLACK_APP_TOKEN |
(required) | App-level token with connections:write for Socket Mode (xapp-…) |
PI_BIN |
pi |
Path to pi binary |
PI_MODEL |
(none) | Default model override |
PI_THINKING |
(none) | Default thinking level |
PI_CWD |
$HOME |
Default working directory for pi; can be overridden per registered channel |
PI_EXTRA_FLAGS |
(none) | Extra flags passed to pi |
TRIGGER_NAME |
mot |
Bot trigger name for summoning in channels |
CHANNEL_POLICY |
allowlist |
Channel access: open, open-trigger, or allowlist |
PROJECT_ROOTS |
$HOME |
Comma-separated roots /mot bind <name> searches for <root>/<name> |
ADMIN_USERS |
(none) | Comma-separated Slack user IDs allowed to run /mot bind (empty disables it) |
EXCLUDED_CHANNELS |
(none) | Comma-separated channel IDs to exclude from auto-registration |
DM_POLICY |
open |
DM access: open, allowlist, or disabled |
REPLY_IN_THREAD |
true |
Post responses into the triggering message's thread when it has one |
ZENMUX_API_KEY |
(none) | ZenMux API key for voice transcription; unset disables the feature |
MAX_CONCURRENCY |
3 |
Max parallel pi invocations |
MAX_SCHEDULED_CONCURRENCY |
1 |
Max scheduled tasks enqueued per tick |
POLL_INTERVAL_MS |
1000 |
Queue poll interval (ms) |
SHUTDOWN_TIMEOUT_MS |
15000 |
Graceful shutdown timeout (ms) |
ARCHIVE_RETENTION_DAYS |
30 |
Days to keep archived sessions (0 = never clean) |
MAX_ATTACHMENT_BYTES |
26214400 |
Max size per attachment (0 = no limit) |
MAX_TOTAL_ATTACHMENT_BYTES |
52428800 |
Max combined attachment size per message (0 = no limit) |
MEDIA_RETENTION_HOURS |
168 |
Hours to keep downloaded attachment files for path-based agent access |
SESSIONS_DIR |
(platform default)/sessions | Session storage directory |
DB_PATH |
(platform default)/gateway.db | SQLite database path |
LOG_LEVEL |
info |
Log level: debug/info/warn/error |
Data lives next to the config: ~/.local/share/mot/ on Linux, ~/Library/Application Support/mot/ on macOS, %LOCALAPPDATA%\mot\ on Windows.
CLI reference
mot setup [bot-token] [app-token] Interactive setup wizard
mot start Start gateway (foreground)
mot status Show diagnostics
mot channels List registered channels
mot register <id> <name> [options] Register a channel
mot unregister <id> Unregister a channel
mot send --channel <jid> [--text <msg>] [--file <path> ...]
mot task add --name <n> --schedule <cron|iso> --channel <jid> --prompt <text> [--once]
mot task list | remove <id> | enable <id> | disable <id>
mot archive list List archived sessions
mot archive cleanup [--dry-run] Clean up expired archived sessions
mot daemon install | uninstall | start | stop | status | logs
Channel jids are the Slack channel ID with an sl: prefix (e.g. sl:C0123456789); bare IDs are accepted and prefixed automatically. Register options: --no-trigger (respond to all messages), --main (implies --no-trigger), --folder <name> (custom session folder), --cwd <path> (per-channel working directory).
mot send and mot task are also the gateway's tools for pi — pi invokes them itself when you ask it to send a file or schedule something. mot send works standalone, without a running gateway.
Troubleshooting
pi not found in PATH
mot status shows "Pi binary: not found".
- Check
pi --versionworks in the same shell - Set
PI_BIN=/full/path/to/piin config.env - Restart:
mot daemon stop && mot daemon start
Missing auth.json
mot status shows "Pi auth: missing".
- Run
piand complete the login flow - Confirm
~/.pi/agent/auth.jsonexists for the same user running the gateway
Daemon service won't start
mot daemon status— check for errorsmot daemon logs— see log output- Linux: for headless servers, run
sudo loginctl enable-linger $USER - macOS: check
daemon.stdout.log/daemon.stderr.login the data directory
Bot is online but doesn't respond
- The bot must be invited to a channel before Slack delivers its messages:
/invite @mot allowlistpolicy (the default): runmot channels— the channel must be registered by its IDopenpolicy: checkEXCLUDED_CHANNELSdoesn't include your channel- For trigger-only channels: mention the bot (
@mot …) or start the message with the trigger name - DMs: check
DM_POLICYisn'tdisabled - Verify both tokens with
mot status(xoxb-…bot token andxapp-…app token)
Replies are intermittent or sessions lose context
Almost always caused by two gateway instances running against the same app. Slack Socket Mode load-balances events across connections, so a second instance silently receives half your messages and splits your sessions. Stop all copies (mot daemon stop, check stray mot start terminals) and run exactly one.
Security
- Access to the bot is access to the machine. Anyone who can message a registered channel drives a coding agent with shell and filesystem rights of the gateway user — treat it like SSH access. The defaults are conservative (
CHANNEL_POLICY=allowlist); widen them only for channels and people you trust, and considerDM_POLICY=allowlistordisabledin shared workspaces. - Assume prompt injection. Everything in a registered channel reaches pi's context — other people's messages, attachment contents, pasted text — and pi may follow instructions embedded in it. Prefer trigger-gated channels; don't register busy public ones.
- Run one instance, as a normal user. Never run two gateways against the same Slack app (see Troubleshooting), and don't run the service as root.
- Protect
config.env(chmod 600) — it holds both Slack tokens. Thexoxb-token can read shared files and post as the bot in every channel it has joined.
The bundled manifest requests the minimal scope set the gateway uses (message events for channels/groups/DMs, chat:write, files read/write, reactions:write, users:read, conversation-read scopes for channel labels, commands). The gateway never pulls channel history — it only sees events Slack pushes to it.
To report a vulnerability, see SECURITY.md.
Differences from pi-tag
Mot tracks pi-tag v0.2.1 and adds:
- pi-coding-agent 1.x support — model catalog works with the 1.x SDK surface
- Native thread status — Slack's built-in "is running…" indicator replaces the busy reaction
/mot bind [dir]— admins register a channel and its working directory from Slack, no terminal needed- Voice messages — audio attachments transcribed via ZenMux ASR (see Voice messages)
Everything user-facing is named Mot: the mot command, the /mot slash command, the mot trigger name, MOT_CONFIG, the mot / com.mot service, and mot/ config and data directories.
Migrating from pi-tag
pitag daemon uninstall- Rename your
pitag/config and data directory tomot/(and updateSESSIONS_DIR/DB_PATHinconfig.envif set) - Change the Slack app's slash command to
/mot(or re-create the app from this repo'smanifest.yaml) npm install -g @zephyrdeng/mot && mot setup && mot daemon install && mot daemon start
Development
git clone https://github.com/ZephyrDeng/mot.git
cd mot
npm install
npm run dev # Start with tsx (no build needed)
npm run build # Compile TypeScript
npm test # Run Vitest suite
Contributions welcome — see CONTRIBUTING.md. Release notes live in the Changelog.
License
MIT — see LICENSE. Original pi-tag and piscord copyrights are retained.