Skip to content
Development
Skill

/x-chat

Read, summarize, or send encrypted X Chat (XChat) DMs via the X plugin MCP plus local chatxdk / xchat_lite.py. Use when the user mentions X Chat, xchat, encrypted DMs, Chat PIN, juicebox, inbox messages, or wants to reply in X Chat. Not for classic unencrypted DMs, posting

From plugin
cursor-plugins
8.1k90 skills13 agents
Install
$ npx -y skills add cursor/plugins --skill x-chat --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/x-chat

Context preview

The summary Claude sees to decide when to auto-load this skill.

Read, summarize, or send encrypted X Chat (XChat) DMs via the X plugin MCP plus local chatxdk / xchat_lite.py. Use when the user mentions X Chat, xchat, encrypted DMs, Chat PIN, juicebox, inbox messages, or wants to reply in X Chat. Not for classic unencrypted DMs, posting

SKILL.md

x-chat.SKILL.md
name: X Chat
description: >-
  Read, summarize, or send encrypted X Chat (XChat) DMs via the X plugin MCP
  plus local chatxdk / xchat_lite.py. Use when the user mentions X Chat, xchat,
  encrypted DMs, Chat PIN, juicebox, inbox messages, or wants to reply in X
  Chat. Not for classic unencrypted DMs, posting tweets, or always-on daemons.
  If chat tools or dm.read/dm.write are missing, tell them to reconnect the X
  plugin — do not create an app or ask for Bearer tokens.

X Chat (X MCP + local `xchat_lite.py`)

Encrypted X Chat only. MCP holds OAuth and ciphertext. Local `xchat_lite.py` unlocks Juicebox, decrypts, and encrypts. Never decrypt on the server. Never paste PIN, juicebox tokens, private keys, or raw key blobs into chat.

Classic unencrypted DMs (`/2/dm_conversations/...`) are a different product. If a peer has no Chat encryption (no usable public keys / no KeyChange history / `add_conversation_keys` fails with `UNAUTHORIZED_REQUESTING_USER` and events never decrypt), stop and tell the user — do not fake a classic DM send through `send_chat_message`.

Posting tweets is still not supported.

If chat permission is missing

Chat tools need **`dm.read`** and **`dm.write`**. Other X tools (timeline, search, `get_users_me`) can work while Chat does not.

**When:** chat tools missing (`get_chat_conversations`, `send_chat_message`, `get_users_public_key`, …) while other X tools work; 403 / missing-scope on a Chat call; catalog looks stale but a real Chat call fails scopes.

This is **not** [account not ready](../x-api-mcp-guide/SKILL.md#2-account-not-ready) (do not tell them to create a Project or App).

**Say:**

> X Chat needs an extra sign-in. Reconnect the X plugin in this chat and approve access (including messages). Then I'll retry. Don't paste keys, tokens, or your Chat PIN here.

Stop. After they reconnect, retry Chat. Do not invent a second OAuth / Bearer / `xurl auth` path.

Local helper

Clone and install **on the bot computer** (not in git with this plugin):

git clone https://github.com/xdevplatform/xchat-grokbot-helper.git xchat-lite
cd xchat-lite
python3 -m venv .venv && .venv/bin/pip install -U pip chatxdk

`xchat_lite.py` is in that repo. Typical paths: `./xchat-lite/` or `$HOME/xchat-lite/`.

**Chat PIN only.** Request it via secret-request into `CHAT_PIN`. Never echo it. Never ask them to paste the PIN into the transcript. The helper also reads Grok Bot `box-secrets.json` → `card.CHAT_PIN` if env is empty. Do not ask for anything else (no Bearer token, no password, no juicebox dump).

HELPER=/path/to/xchat-lite/.venv/bin/python
SCRIPT=/path/to/xchat-lite/xchat_lite.py

$HELPER $SCRIPT --user-id "$X_USER_ID" --key-version "$VER" --juicebox "$JUICEBOX_PATH" unlock-check

Split of duties

| Layer | Owns | | --- | --- | | **X MCP connector** | OAuth (`dm.read` / `dm.write`), HTTP, ciphertext only | | **Local `chatxdk` + `xchat_lite.py`** | Juicebox unlock, decrypt, encrypt, prepare-keys |

MCP tools (wire)

| Tool | Use | | --- | --- | | `get_users_me` | Numeric `user_id` | | `get_users_public_key` | Self: `juicebox_config`, `public_key_version`, `public_key`, `signing_public_key`, `identity_public_key_signature` | | `get_users_public_keys` | Peer keys (batch) | | `get_chat_conversations` | Inbox (paginate) | | `get_chat_conversation` | One thread metadata | | `get_chat_conversation_events` | `data[].encoded_event` + **`meta.conversation_key_events`** | | `send_chat_message` | Pre-encrypted `message_id` + `encoded_message_create_event` (+ signature) | | `add_conversation_keys` | Output of local `prepare-keys` / `session-encrypt.add_conversation_keys` | | `send_chat_typing_indicator` | Optional UX (do not hammer) | | `mark_chat_conversation_read` | Optional after handling |

**Valid `public_key.fields`:** `public_key_version,public_key,signing_public_key,identity_public_key_signature,juicebox_config` Do **not** pass `identity_public_key` as a fields token. Map MCP `public_key` → SDK `identity_public_key`, and MCP `signing_public_key` → SDK `public_key` (signing) when building `signing_keys`.

MCP must never accept plaintext message bodies to encrypt server-side. If a tool asks for plaintext send, it is not XChat — do not use it for this skill.

Helper commands

# decrypt (always prepend meta.conversation_key_events when present)
$HELPER $SCRIPT ... decrypt <<'JSON'
{"events":["..."], "conversation_key_events":["..."], "signing_keys":[...]}
JSON

# first contact / empty thread — stdout is add_conversation_keys body
$HELPER $SCRIPT ... prepare-keys <<'JSON'
{"conversation_id":"AAA-BBB","public_keys":[
  {"user_id":"AAA","public_key":"<identity>","key_version":"<ver>"},
  {"user_id":"BBB","public_key":"<identity>","key_version":"<ver>"}
]}
JSON

# preferred send: warm keys + encrypt in ONE process
$HELPER $SCRIPT ... session-encrypt <<'JSON'
{
  "conversation_id": "AAA-BBB",
  "text": "hello",
  "events": ["...encoded_event..."],
  "conversation_key_events": ["..."],
  "signing_keys": [...],
  "prepare": null
}
JSON

`session-encrypt` for **empty / first message** threads: set `prepare` (same shape as `prepare-keys` stdin). Output includes `add_conversation_keys` + `needs_add_conversation_keys_before_send: true`. Call MCP `add_conversation_keys` **before** `send_chat_message`. Strip any `_local_*` fields — never send those to MCP.

Standalone `encrypt CONV_ID TEXT` fails if the Chat session has no conversation key. Prefer `session-encrypt`.

Never `echo $CHAT_PIN`. Never `cat` PIN files. Write juicebox config from MCP to a mode-`600` file; mention in chat only “juicebox config saved”.

Session bootstrap (once per working session)

1. Confirm Chat tools exist. If not, missing-permission line above. 2. `get_users_me` → numeric X user id (`$X_USER_ID`). Do not use the shell’s `$UID` (Unix account id). 3. `get_users_public_key` for self with the valid `public_key.fields` list. 4. Persist `juicebox_config` as JSO

Read more
Ships withcursor-plugins

Official Cursor plugins for popular developer tools, frameworks, and SaaS products. Each plugin is a standalone directory at the repository root with its own .cursor-plugin/plugin.json manifest.

Get the whole plugin

Other skills on cursor-plugins.