# Ops Inbox

> Full inbox management across all channels — WhatsApp (whatsmeow bridge via mcp__whatsapp__*), iMessage (chat.db reader + AppleScript send via mcp__plugin_imessage_imessage__*), Email (Gmail MCP), Slack (MCP), Telegram (user-auth MCP), Discord (webhook + REST read), Notion (MCP — comments, mentions, assigned tasks). Scans FULL inbox (not just unread), identifies messages needing replies, archives…

- **Type:** Skill
- **Install:** `agentstack add skill-lifecycle-innovations-limited-claude-ops-ops-inbox`
- **Verified:** Pending review
- **Seller:** [Lifecycle-Innovations-Limited](https://agentstack.voostack.com/s/lifecycle-innovations-limited)
- **Installs:** 0
- **Category:** [Cloud & Infrastructure](https://agentstack.voostack.com/c/cloud-infrastructure)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Lifecycle-Innovations-Limited](https://github.com/Lifecycle-Innovations-Limited)
- **Source:** https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-inbox
- **Website:** https://github.com/Lifecycle-Innovations-Limited/claude-ops/wiki

## Install

```sh
agentstack add skill-lifecycle-innovations-limited-claude-ops-ops-inbox
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# OPS ► INBOX ZERO

## ⚠️ WHATSAPP TRANSPORT — MCP ONLY, NEVER `wacli`

For **all** WhatsApp operations in this skill (list chats, read messages, search contacts, send replies, archive chats), use the `mcp__whatsapp__*` tool family backed by the whatsmeow (Go) whatsapp-bridge — upstream `lharries/whatsapp-mcp`. (Earlier docs misnamed this as "Baileys" — Baileys is the Node.js WhatsApp library; this bridge uses `go.mau.fi/whatsmeow`.)

**NEVER call the legacy `wacli` CLI** (`wacli chats list`, `wacli messages list`, `wacli send`, `wacli doctor`, `wacli history backfill`, etc). The wacli store and keepalive daemon are deprecated for this skill.

If you find yourself reaching for any `wacli ...` shell command, stop and use the MCP tool with the same intent:

| Intent                        | ✅ Use this                                                                                                                                                                           | ❌ Do NOT use                       |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| List recent chats             | `mcp__whatsapp__list_chats {sort_by: "last_active", limit: 25}`                                                                                                                       | `wacli chats list`                  |
| Read full thread              | `mcp__whatsapp__list_messages {chat_jid, limit: 20}`                                                                                                                                  | `wacli messages list`               |
| Full-text search              | `mcp__whatsapp__list_messages {query: "", limit: 20}`                                                                                                                           | `wacli messages search`             |
| Resolve a contact             | `mcp__whatsapp__search_contacts {query: ""}`                                                                                                                                    | `wacli contacts`                    |
| Send a reply (after approval) | `mcp__whatsapp__send_message {recipient: "", message: ""}`                                                                                                                 | `wacli send`                        |
| Health check                  | `lsof -i :8080 \| grep LISTEN` + (macOS) `launchctl print "gui/$(id -u)/com.${USER}.whatsapp-bridge"` / (Linux) `systemctl --user is-active whatsapp-bridge.service`                  | `wacli doctor` / `~/.wacli/.health` |
| Trigger history backfill      | `curl -fsS -X POST http://127.0.0.1:8080/api/backfill` (claude-ops patch — runs per-chat against the 50 most-recent chats; bridge also auto-backfills 5s after every Connected event) | —                                   |

**Rationale:** the bridge exposes a typed MCP surface, returns consistent JSON shapes (`is_from_me`, `content`, `timestamp`, `sender`), supports FTS5 search natively, and avoids store-lock contention with the wacli keepalive daemon. Mixing the two surfaces caused inconsistent state in past sessions.

**Sole exception:** the `~/.wacli/.health` file is still readable for legacy daemon-health surfacing in other skills, but no `wacli` command should be invoked from this skill.

## Runtime Context

Before executing, load available context:

0. **Auto-sync WhatsApp in the background (DEFAULT — every invocation)** — the FIRST thing this skill does, before any scan or menu, is guarantee the store is fresh, then fire a recent-conversation history backfill **and** a contacts-link in the background, non-blocking.

   **0a. Freshness gate (run FIRST, blocking, bounded).** Before classifying anything, run `~/bin/wa-inbox-fresh.sh` (shipped by `scripts/install-whatsapp-bridge-linux.sh`). It probes the bridge with a real **curl connection probe** (`curl -s -m4 http://127.0.0.1:8080/`), forces a backfill, triggers voice-note transcription, and waits (bounded ~32s) for the newest message to settle, then prints a FRESHNESS report (`newest message = … (N min old)`). It **only restarts the bridge if the curl probe genuinely fails twice** — do NOT gate liveness on `ss | grep :8080`, because `ss` renders port 8080 as the service name `webcache`, so the grep never matches and you'd needlessly bounce a healthy bridge. Exit 2 means the bridge is down and unrecoverable → the store is STALE, do not trust last-sender classification.

### Mac WhatsApp.app fallback (bridge-miss recovery)

The whatsmeow bridge can **silently miss inbound messages** when its history/app-state sync lags — most often on `@lid` chats (e.g. 2026-06-11 it missed a reply from a contact that the Mac WhatsApp.app had). The Mac app keeps an **unencrypted** local Core Data store at `~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared/ChatStorage.sqlite`, readable over Tailscale SSH, so it is a reliable ground-truth backstop.

- **When it runs AUTOMATICALLY:** `wa-inbox-fresh.sh` now invokes the Mac cross-check itself whenever the bridge store looks stale — on exit 2 (store unreadable) or when the newest message is >2h old, it prints a `MAC GROUND TRUTH` block (latest 10 messages from the Mac app store) inline in the freshness report. No orchestration needed.
- **When to use manually:** a contact's _known_ reply is missing from the bridge (common on `@lid` chats) — cross-check before classifying that thread as "no reply".
- **Command:** `bin/wa-mac-latest.sh --contact  [N]` (also `--recent [N]`, `--since "YYYY-MM-DD HH:MM"`, add `--json` for machine-readable output). It reads `~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared/ChatStorage.sqlite` over SSH. Schema: `ZWAMESSAGE` (`ZTEXT`, `ZISFROMME`, `ZMESSAGEDATE` = seconds since 2001-01-01) joined to `ZWACHATSESSION` (`ZPARTNERNAME`, `ZCONTACTJID`).
- **Transport chain (`bin/wa-mac-transport.sh`, shared by all wa-mac-\* scripts):** ① Tailscale/direct SSH (`WA_MAC_SSH=user@host`) → ② Cloudflare-tunnel SSH (`WA_MAC_CF_HOST=ssh-mac.example.com`, via `cloudflared access ssh` ProxyCommand) when Tailscale is down. One-time wiring: `scripts/setup-wa-mac-cf-tunnel.sh` (installs cloudflared locally + the Mac LaunchDaemon from a remotely-managed tunnel token, then verifies end-to-end). Both env vars live in the shell profile, never in the repo.
- **READ-ONLY ground truth for reads.** The reader never writes and never sends. Sends still go through the whatsmeow bridge (`mcp__whatsapp__send_message`) under the Rule-6 outbound-approval gate — the Mac store is only consulted to confirm what actually arrived. The ONLY write-capable Mac surface is `wa-mac-archive.sh` (archive-only, see Tier 4 of the archive ladder).
- **Why no Linux-native alternative:** there is no official WhatsApp Linux desktop app; the third-party Flatpak clients (`whatsapp-for-linux`, ZapZap) are Electron WhatsApp-Web wrappers that need a GUI, consume a linked-device slot, and store data in encrypted IndexedDB (not a queryable SQLite) — so the Mac `ChatStorage.sqlite` is the preferred backstop.

  **The FULL-THREAD AWARENESS GATE (in "Processing each channel") depends on this step having run first.** That gate's "read both directions incl. `[voice]`" only works once `wa-inbox-fresh.sh` (freshness + backfill) and the voice-note transcription pass (step 0c) have completed and the store has settled — otherwise outbound rows and `[voice]` bodies are still missing and the gate reads an incomplete thread.

  **0b. Background backfill + contacts-link** (idempotent, safe every time). The backfill pulls recent messages for the 50 most-active chats; the link populates `messages.db.contacts` from the whatsmeow session store so both `@s.whatsapp.net` and `@lid` chat JIDs resolve to names (without it the `contacts` table is empty and LID-format chats show raw phone numbers):

  ```bash
  BR="${WHATSAPP_BRIDGE_DIR:-$HOME/.local/share/whatsapp-mcp/whatsapp-bridge}"
  if curl -s -o /dev/null -m 4 http://127.0.0.1:8080/ 2>/dev/null; then
    curl -fsS -m 10 -X POST http://127.0.0.1:8080/api/backfill >/dev/null 2>&1 &   # recent-conversation backfill
    [ -f "$BR/link_contacts.py" ] && python3 "$BR/link_contacts.py" >/dev/null 2>&1 &  # contacts link (phone + LID aliases)
  fi
  ```

  Kick this off, then continue with the steps below while it runs — give the link ~2s before name-resolving chats. `link_contacts.py` resolves names via `whatsmeow_contacts` + `whatsmeow_lid_map` (name preference: full_name → first_name → push_name → business_name). It ships via `scripts/install-whatsapp-bridge-linux.sh` into the bridge dir; recreate it from `whatsmeow_contacts`/`whatsmeow_lid_map` if absent.

  **0c. Voice notes are first-class.** Incoming voice notes (`media_type='audio'`, empty `content`) are auto-transcribed into `content` as `[voice] ` by the `whatsapp-transcribe.timer` (systemd-user, every 10 min, OpenAI `whisper-1`) — and `wa-inbox-fresh.sh` triggers a transcribe pass on every scan. So a voice note shows up in NEEDS_REPLY / thread scans exactly like a text message; treat a `[voice] …` body as the sender's words. Transcription is idempotent (only ever fills empty audio rows, never clobbers real text) and capped per run, so it never re-bills or stacks.

  **0c-bis. ALL media is now first-class, not just voice.** Beyond voice→`[voice]` (transcribe above), incoming **video / image / document** media (empty `content`) is auto-enriched into `content` as `[video] …` / `[image] …` / `[document] …` by `transcriber/enrich_media.py` (vision for stills/video frames + Whisper for any audio track) on the `whatsapp-enrich.timer` (systemd-user, every 10 min) — and `wa-inbox-fresh.sh` queues an enrich pass on every scan. So an image, clip, or PDF shows up in NEEDS_REPLY / thread scans with a real, readable body, exactly like text. Enrichment is idempotent (only fills empty media rows) and capped per run. The bridge also **self-heals media that 403/404/410s** (stale `directPath`, common for larger media) by asking the sender's phone to re-upload via `SendMediaRetryReceipt` (`apply-patches.py` Fix M), so large media never silently drops.

  **0d. The scan engine self-refreshes + self-reconciles on EVERY run — this is automatic, you do not orchestrate it.** `bin/ops-inbox-scan` (the primary classifier, step "Scan engine" below) now does the refresh/pull itself, BLOCKING and bounded, before it classifies — so the data is converged by the time you read its JSON, regardless of whether the background `ops-inbox-autosync` hook has finished. On each invocation the scan:
  - **Refreshes (frontfill/backfill):** if the bridge is reachable on `:8080`, it fires `POST /api/backfill` + `link_contacts.py`, then **waits (bounded ~18s) for the newest stored message timestamp to stop advancing** so the classify pass reads a settled store. This is the blocking guarantee the background hook alone does NOT give. Skip with `OIS_NO_REFRESH=1` (set automatically on repeat calls in one session to avoid re-waiting).
  - **Reconciles outbound sends (owner directive 2026-06-05 "include all things I sent to all people"):** it reads the bridge's outbound-send journal (`journalctl --user -u whatsapp-bridge.service`, or the bridge log file on non-systemd hosts) into a `{recipient_jid → latest_send_epoch}` map, and **demotes any NEEDS_REPLY thread whose last inbound is older than a send to any of that person's JIDs** (`reconciled` flag set, moved to WAITING). This catches replies that went out via `/api/send` or a phone send that has not yet landed in `messages.db` — the single most common false-NEEDS_REPLY. Only epoch-stamped send lines drive demotion (a send that genuinely predates the inbound never demotes).

  Net effect: running `/ops:ops-inbox` autonomously pulls the latest state AND folds in everything the user already sent, with **zero extra orchestration on your part** — just read the scan JSON. A `reconciled` field on a WAITING item means "already answered, reply not yet in the store"; never re-draft it. You still clear the FULL-THREAD AWARENESS GATE on whatever genuine NEEDS_REPLY candidates remain.

1. **Self-heal plugin version pin** — if any `${CLAUDE_PLUGIN_DATA_DIR}` file or `~/.claude/plugins/installed_plugins.json` references a `cache/ops-marketplace/ops/X.Y.Z/` path that no longer exists on disk, downstream hooks (`stop-all.sh`, `ops-post-session-cleanup`) emit `Plugin directory does not exist`. Resolve before scanning:

   ```bash
   INSTALLED="$HOME/.claude/plugins/installed_plugins.json"
   CACHE_DIR="$HOME/.claude/plugins/cache/ops-marketplace/ops"
   PINNED=$(python3 -c "import json; d=json.load(open('$INSTALLED')); print(d.get('plugins',{}).get('ops@ops-marketplace',[{}])[0].get('version',''))")
   LATEST=$(ls "$CACHE_DIR" 2>/dev/null | sort -V | tail -1)
   if [ -n "$PINNED" ] && [ -n "$LATEST" ] && [ "$PINNED" != "$LATEST" ] && [ ! -d "$CACHE_DIR/$PINNED" ]; then
     python3 -c "
   import json
   p='$INSTALLED'; d=json.load(open(p))
   for e in d.get('plugins',{}).get('ops@ops-marketplace',[]):
     if e.get('version')=='$PINNED':
       e['version']='$LATEST'
       e['installPath']='$CACHE_DIR/$LATEST'
   json.dump(d, open(p,'w'), indent=2)
   "
     bash "$HOME/.claude/scripts/hooks/ops-plugin-version-heal.sh"   # rewrites daemon-services.json + mcp-proxy/servers.json
   fi
   ```

   The existing `ops-plugin-version-heal.sh` only rewrites _downstream_ targets from `installed_plugins.json` (the source of truth). When the source itself is stale, the heal hook is a no-op — patch it first, then re-run the hook.

2. **Preferences**: Read `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json`
   - `default_channels` — which channels to scan by default
   - `secrets_manager` / `doppler` — how to resolve channel credentials if not in env

3. **Daemon health**: Read `${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json`
   - Check `whatsapp-bridge` status — verify `com.${USER}.whatsapp-bridge` is running (`lsof -i :8080` or `launchctl print "gui/$(id -u)/com.${USER}.whatsapp-bridge"`)
   - Also verify the **ops mcp-proxy** is up on `:8090` (`lsof -i :8090 | grep LISTEN`) — Claude's MCP client connects through the proxy SSE endpoint, not directly to the bridge. If :8080 is up but :8090 is down, `mcp__whatsapp__*` tools will never load.
   - If either layer is down, surface the issue before WhatsApp operations
   - **Do not declare WhatsApp MCP unavailable purely because tools haven't loaded yet** — when both ports are LISTEN, retry `ToolSearch select:mcp__whatsapp__list_chats,...` up to 3× at 5s intervals to let the SSE handshake complete

4. **Ops memories**: Check `${CLAUDE_PLUGIN_DATA_DIR}/memories/` before drafting any reply:
   - `contact_*.md` — load profile for the contact you're about to reply to
   - `preferences.md` — apply user's communication style and language preferences
   - `topics_active.md` — check for active threads or deadlines related to this contact
   - `donts.md` — never violate these restrictions in drafts

## CLI/API Reference

### whatsapp-bridge (WhatsApp — mcp**whatsapp**\*)

**Bridge health** — check bridge is running before any WhatsApp operation. Same `lsof` probe across platforms; supervisor command differs:

```bash
lsof -i :8080 | grep LISTEN   # bridge listens on :8080 (same on macOS + Linux)

# macOS — launchd:
launchctl print "gui/$(id -u)/com.${USER}.whatsapp-bridge" 2>&1 | head -3   # use print, NOT list — list only shows already-loaded services

# Linux — systemd-user (installed by scripts/install-whatsapp-bridge-linux.sh):
systemctl --user is-active whatsapp-bridge.service
journalctl --user -u whatsapp-bridge.service -n 10 --no-pager
```

**One-line cross-platform restart** — use the in-repo wrapper when you

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Lifecycle-Innovations-Limited](https://github.com/Lifecycle-Innovations-Limited)
- **Source:** [Lifecycle-Innovations-Limited/claude-ops](https://github.com/Lifecycle-Innovations-Limited/claude-ops)
- **License:** MIT
- **Homepage:** https://github.com/Lifecycle-Innovations-Limited/claude-ops/wiki

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** yes
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-lifecycle-innovations-limited-claude-ops-ops-inbox
- Seller: https://agentstack.voostack.com/s/lifecycle-innovations-limited
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
