omp packages

@zephyrdeng/mot0.4.1

Slack gateway for the pi coding agent (fork of pi-tag)

GitHub Actions Published 11 days ago MIT ~797 downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

Mot logo

Mot

The pi coding agent in your Slack — sessions, files, voice, and scheduled tasks from any channel, DM, or phone.

npm version CI license

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 /mot panel — 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
  1. Create the Slack app — api.slack.com/apps → Create New App → From a manifest → pick your workspace → paste manifest.yaml. The command is /mot.

  2. Get the two tokens — OAuth & Permissions → Install to Workspace → copy the Bot User OAuth Token (xoxb-…); Basic Information → App-Level Tokens → create one with the connections:write scope (xapp-…).

  3. 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 service
    

    Prefer a config file by hand? Copy .env.example to your config path (see Configuration) and fill in the tokens.

  4. Talk — open a DM with the bot, or /invite @mot to 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, default mot, matched only at the start):

    @mot fix the build
    mot summarize this thread
    
  • Threads — trigger pi inside a thread and the answer lands in that thread (REPLY_IN_THREAD=true by 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 send command with every message, and any file:// reference left in a reply is automatically uploaded as a real Slack attachment. Up to 10 files per message, subject to MAX_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 new rotates the session — the old one is archived and cleaned up after ARCHIVE_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 even open mode 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/app
    
  • Group DMs (mpim) behave like channels and follow CHANNEL_POLICY.

  • EXCLUDED_CHANNELS blocks specific channel IDs from auto-registration in open / open-trigger mode.

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 start in 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 --version works in the same shell
  • Set PI_BIN=/full/path/to/pi in config.env
  • Restart: mot daemon stop && mot daemon start
Missing auth.json

mot status shows "Pi auth: missing".

  • Run pi and complete the login flow
  • Confirm ~/.pi/agent/auth.json exists for the same user running the gateway
Daemon service won't start
  • mot daemon status — check for errors
  • mot daemon logs — see log output
  • Linux: for headless servers, run sudo loginctl enable-linger $USER
  • macOS: check daemon.stdout.log / daemon.stderr.log in 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
  • allowlist policy (the default): run mot channels — the channel must be registered by its ID
  • open policy: check EXCLUDED_CHANNELS doesn't include your channel
  • For trigger-only channels: mention the bot (@mot …) or start the message with the trigger name
  • DMs: check DM_POLICY isn't disabled
  • Verify both tokens with mot status (xoxb-… bot token and xapp-… 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 consider DM_POLICY=allowlist or disabled in 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. The xoxb- 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

  1. pitag daemon uninstall
  2. Rename your pitag/ config and data directory to mot/ (and update SESSIONS_DIR / DB_PATH in config.env if set)
  3. Change the Slack app's slash command to /mot (or re-create the app from this repo's manifest.yaml)
  4. 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.

Acknowledgments