Compare commits

..

2 Commits

Author SHA1 Message Date
Tobin South
f92ea66fa9 Update .claude-plugin/marketplace.json 2026-03-30 20:48:24 +01:00
tobin
fed46bd806 math-olympiad: add LICENSE, marketplace entry, and prettier formatting
- Add Apache 2.0 LICENSE file
- Register plugin in marketplace.json
- Run prettier (prose-wrap=always, 80 cols) over all plugin markdown
- Simplify model tier naming in reference docs

🏠 Remote-Dev: homespace
2026-03-30 19:22:03 +00:00
7 changed files with 51 additions and 110 deletions

View File

@@ -749,17 +749,6 @@
},
"homepage": "https://www.mintlify.com/"
},
{
"name": "mongodb",
"description": "Official Claude plugin for MongoDB (MCP Server + Skills). Connect to databases, explore data, manage collections, optimize queries, generate reliable code, implement best practices, develop advanced features, and more.",
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/mongodb/agent-skills.git",
"sha": "c47079f65e88a113c52d1ce0618684cef300246c"
},
"homepage": "https://www.mongodb.com/docs/mcp-server/overview/"
},
{
"name": "neon",
"description": "Manage your Neon projects and databases with the neon-postgres agent skill and the Neon MCP Server.",
@@ -923,7 +912,8 @@
"category": "monitoring",
"source": {
"source": "url",
"url": "https://github.com/PostHog/ai-plugin.git"
"url": "https://github.com/PostHog/ai-plugin.git",
"sha": "f2f37954ecef9f1afce4fa81b6a612454a96c410"
},
"homepage": "https://posthog.com/docs/model-context-protocol"
},
@@ -944,7 +934,7 @@
"source": {
"source": "url",
"url": "https://github.com/Postman-Devrel/postman-claude-code-plugin.git",
"sha": "40b11ac3466c500cf4625ac016d5c01cd00046f4"
"sha": "0714280351c1a137e79aad465a66730511ffbd57"
},
"homepage": "https://learning.postman.com/docs/developer/postman-mcp-server/"
},
@@ -1093,20 +1083,6 @@
}
}
},
{
"name": "runway-api",
"description": "Helps integrate Runway's API into projects. Guides API key setup, analyzes codebase compatibility, and provides hands-on integration assistance for video generation, image generation, audio, and file uploads.",
"category": "development",
"author": {
"name": "Runway"
},
"source": {
"source": "url",
"url": "https://github.com/runwayml/skills.git",
"sha": "cc609882edf0b65c94deccd42c841ed934d2d8d0"
},
"homepage": "https://docs.dev.runwayml.com/"
},
{
"name": "rust-analyzer-lsp",
"description": "Rust language server for code intelligence and analysis",
@@ -1214,17 +1190,6 @@
},
"homepage": "https://github.com/slackapi/slack-mcp-plugin/tree/main"
},
{
"name": "sonarqube-agent-plugins",
"description": "Integrate SonarQube code quality and security analysis into Claude Code: namespaced slash commands, a guided skill to setup the SonarQube CLI, and a startup check for CLI wiring. MCP server registration and secrets-scanning hooks are installed by the SonarQube CLI as part of setup.",
"category": "security",
"source": {
"source": "url",
"url": "https://github.com/SonarSource/sonarqube-agent-plugins.git",
"sha": "0cae644cee9318e6245b62ca779abdc60e6daa49"
},
"homepage": "https://github.com/SonarSource/sonarqube-agent-plugins"
},
{
"name": "sonatype-guide",
"description": "Sonatype Guide MCP server for software supply chain intelligence and dependency security. Analyze dependencies for vulnerabilities, get secure version recommendations, and check component quality metrics.",
@@ -1377,32 +1342,6 @@
}
}
},
{
"name": "ui5",
"description": "SAPUI5 / OpenUI5 plugin for Claude. Create and validate UI5 projects, access API documentation, run UI5 linter, get development guidelines and best practices for UI5 development.",
"category": "development",
"source": {
"source": "git-subdir",
"url": "UI5/plugins-claude",
"path": "plugins/ui5",
"ref": "main",
"sha": "5070dfc1cef711d6efad40beb43750027039d71f"
},
"homepage": "https://github.com/UI5/plugins-claude"
},
{
"name": "ui5-typescript-conversion",
"description": "SAPUI5 / OpenUI5 plugin for Claude. Convert JavaScript based UI5 projects to TypeScript.",
"category": "development",
"source": {
"source": "git-subdir",
"url": "UI5/plugins-claude",
"path": "plugins/ui5-typescript-conversion",
"ref": "main",
"sha": "5070dfc1cef711d6efad40beb43750027039d71f"
},
"homepage": "https://github.com/UI5/plugins-claude"
},
{
"name": "vercel",
"description": "Vercel deployment platform integration. Manage deployments, check build status, access logs, configure domains, and control your frontend infrastructure directly from Claude Code.",
@@ -1456,6 +1395,17 @@
"sha": "b93007e9a726c6ee93c57a949e732744ef5acbfd"
},
"homepage": "https://github.com/zapier/zapier-mcp/tree/main/plugins/zapier"
},
{
"name": "zoominfo",
"description": "Search companies and contacts, enrich leads, find lookalikes, and get AI-ranked contact recommendations. Pre-built skills chain multiple ZoomInfo tools into complete B2B sales workflows.",
"category": "productivity",
"source": {
"source": "url",
"url": "https://github.com/Zoominfo/zoominfo-mcp-plugin.git",
"sha": "0705316ef8a2d0c64f81e50d4612ccc6a74edf03"
},
"homepage": "https://zoominfo.com"
}
]
}

View File

@@ -47,7 +47,6 @@ These are Claude Code commands — run `claude` to start a session first.
Install the plugin:
```
/plugin install discord@claude-plugins-official
/reload-plugins
```
**5. Give the server the token.**

View File

@@ -1,7 +1,7 @@
{
"name": "imessage",
"description": "iMessage channel for Claude Code \u2014 reads chat.db directly, sends via AppleScript. Built-in access control; manage pairing, allowlists, and policy via /imessage:access.",
"version": "0.1.0",
"version": "0.0.1",
"keywords": [
"imessage",
"messaging",

View File

@@ -62,7 +62,6 @@ Handles are phone numbers (`+15551234567`) or Apple ID emails (`them@icloud.com`
| Variable | Default | Effect |
| --- | --- | --- |
| `IMESSAGE_APPEND_SIGNATURE` | `true` | Appends `\nSent by Claude` to outbound messages. Set to `false` to disable. |
| `IMESSAGE_ALLOW_SMS` | `false` | Accept inbound SMS/RCS in addition to iMessage. **Off by default because SMS sender IDs are spoofable** — a forged SMS from your own number would otherwise bypass access control. Only enable if you understand the risk. |
| `IMESSAGE_ACCESS_MODE` | — | Set to `static` to disable runtime pairing and read `access.json` only. |
| `IMESSAGE_STATE_DIR` | `~/.claude/channels/imessage` | Override where `access.json` and pairing state live. |

View File

@@ -1,6 +1,6 @@
{
"name": "claude-channel-imessage",
"version": "0.1.0",
"version": "0.0.1",
"license": "Apache-2.0",
"type": "module",
"bin": "./server.ts",

View File

@@ -32,10 +32,6 @@ import { join, basename, sep } from 'path'
const STATIC = process.env.IMESSAGE_ACCESS_MODE === 'static'
const APPEND_SIGNATURE = process.env.IMESSAGE_APPEND_SIGNATURE !== 'false'
// SMS sender IDs are spoofable; iMessage is Apple-ID-authenticated. Default
// drops SMS/RCS so a forged sender can't reach the gate. Opt in only if you
// understand the risk.
const ALLOW_SMS = process.env.IMESSAGE_ALLOW_SMS === 'true'
const SIGNATURE = '\nSent by Claude'
const CHAT_DB =
process.env.IMESSAGE_DB_PATH ?? join(homedir(), 'Library', 'Messages', 'chat.db')
@@ -109,7 +105,6 @@ type Row = {
date: number
is_from_me: number
cache_has_attachments: number
service: string | null
handle_id: string | null
chat_guid: string
chat_style: number | null
@@ -119,7 +114,7 @@ const qWatermark = db.query<{ max: number | null }, []>('SELECT MAX(ROWID) AS ma
const qPoll = db.query<Row, [number]>(`
SELECT m.ROWID AS rowid, m.guid, m.text, m.attributedBody, m.date, m.is_from_me,
m.cache_has_attachments, m.service, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
m.cache_has_attachments, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
FROM message m
JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
JOIN chat c ON c.ROWID = cmj.chat_id
@@ -130,7 +125,7 @@ const qPoll = db.query<Row, [number]>(`
const qHistory = db.query<Row, [string, number]>(`
SELECT m.ROWID AS rowid, m.guid, m.text, m.attributedBody, m.date, m.is_from_me,
m.cache_has_attachments, m.service, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
m.cache_has_attachments, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
FROM message m
JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
JOIN chat c ON c.ROWID = cmj.chat_id
@@ -170,10 +165,12 @@ const qAttachments = db.query<AttRow, [number]>(`
WHERE maj.message_id = ?
`)
// Your own addresses, from message.account ("E:you@icloud.com" / "p:+1555...")
// on rows you sent. Don't supplement with chat.last_addressed_handle — on
// machines with SMS history that column is polluted with short codes and
// other people's numbers, not just your own identities.
// Your own addresses. message.account ("E:you@icloud.com" / "p:+1555...") is
// the identity you sent *from* on each row — but an Apple ID can be reachable
// at both an email and a phone, and account only shows whichever you sent
// from. chat.last_addressed_handle covers the rest: it's the per-chat "which
// of your addresses reaches this person" field, so it accumulates every
// identity you've actually used. Union both.
const SELF = new Set<string>()
{
type R = { addr: string }
@@ -181,6 +178,9 @@ const SELF = new Set<string>()
for (const { addr } of db.query<R, []>(
`SELECT DISTINCT account AS addr FROM message WHERE is_from_me = 1 AND account IS NOT NULL AND account != '' LIMIT 50`,
).all()) SELF.add(norm(addr))
for (const { addr } of db.query<R, []>(
`SELECT DISTINCT last_addressed_handle AS addr FROM chat WHERE last_addressed_handle IS NOT NULL AND last_addressed_handle != '' LIMIT 50`,
).all()) SELF.add(norm(addr))
}
process.stderr.write(`imessage channel: self-chat addresses: ${[...SELF].join(', ') || '(none)'}\n`)
@@ -432,14 +432,7 @@ const ECHO_WINDOW_MS = 15000
const echo = new Map<string, number>()
function echoKey(raw: string): string {
return raw
.replace(/\s*Sent by Claude\s*$/, '')
.replace(/[\u200d\ufe00-\ufe0f]/g, '') // ZWJ + variation selectors — chat.db is inconsistent about these
.replace(/[\u2018\u2019]/g, "'")
.replace(/[\u201c\u201d]/g, '"')
.trim()
.replace(/\s+/g, ' ')
.slice(0, 120)
return raw.trim().replace(/\s+/g, ' ').slice(0, 120)
}
function trackEcho(chatGuid: string, key: string): void {
@@ -547,10 +540,11 @@ const mcp = new Server(
tools: {},
experimental: {
'claude/channel': {},
// Permission-relay opt-in. Declaring this asserts we authenticate the
// replier — which we do: prompts go to self-chat only and replies are
// accepted from self-chat only (see handleInbound). A server that
// can't authenticate the replier should NOT declare this.
// Permission-relay opt-in (anthropics/claude-cli-internal#23061).
// Declaring this asserts we authenticate the replier — which we do:
// gate()/access.allowFrom already drops non-allowlisted senders before
// handleInbound delivers. Self-chat is the owner by definition. A
// server that can't authenticate the replier should NOT declare this.
'claude/channel/permission': {},
},
},
@@ -568,9 +562,11 @@ const mcp = new Server(
},
)
// Permission prompts go to self-chat only. A "yes" grants tool execution on
// this machine — that authority is the owner's alone, not allowlisted
// contacts'.
// Receive permission_request from CC → format → send to all allowlisted DMs.
// Groups are intentionally excluded — the security thread resolution was
// "single-user mode for official plugins." Anyone in access.allowFrom
// already passed explicit pairing; group members haven't. Self-chat is
// always included (owner).
mcp.setNotificationHandler(
z.object({
method: z.literal('notifications/claude/channel/permission_request'),
@@ -583,6 +579,7 @@ mcp.setNotificationHandler(
}),
async ({ params }) => {
const { request_id, tool_name, description, input_preview } = params
const access = loadAccess()
// input_preview is unbearably long for Write/Edit; show only for Bash
// where the command itself is the dangerous part.
const preview = tool_name === 'Bash' ? `${input_preview}\n\n` : '\n'
@@ -591,17 +588,14 @@ mcp.setNotificationHandler(
`${tool_name}: ${description}\n` +
preview +
`Reply "yes ${request_id}" to allow or "no ${request_id}" to deny.`
// allowFrom holds handle IDs, not chat GUIDs — resolve via qChatsForHandle.
// Include SELF addresses so the owner's self-chat gets the prompt even
// when allowFrom is empty (default config).
const handles = new Set([...access.allowFrom.map(h => h.toLowerCase()), ...SELF])
const targets = new Set<string>()
for (const h of SELF) {
for (const h of handles) {
for (const { guid } of qChatsForHandle.all(h)) targets.add(guid)
}
if (targets.size === 0) {
process.stderr.write(
`imessage channel: permission_request ${request_id} not relayed — no self-chat found. ` +
`Send yourself an iMessage to create one.\n`,
)
return
}
for (const guid of targets) {
const err = sendText(guid, text)
if (err) {
@@ -776,7 +770,6 @@ function expandTilde(p: string): string {
function handleInbound(r: Row): void {
if (!r.chat_guid) return
if (!ALLOW_SMS && r.service !== 'iMessage') return
// style 45 = DM, 43 = group. Drop unknowns rather than risk routing a
// group message through the DM gate and leaking a pairing code.
@@ -788,9 +781,7 @@ function handleInbound(r: Row): void {
const text = messageText(r)
const hasAttachments = r.cache_has_attachments === 1
// trim() catches tapbacks/receipts synced from other devices — those land
// as whitespace-only rows.
if (!text.trim() && !hasAttachments) return
if (!text && !hasAttachments) return
// Never deliver our own sends. In self-chat the is_from_me=1 rows are empty
// sent-receipts anyway — the content lands on the is_from_me=0 copy below.
@@ -826,9 +817,12 @@ function handleInbound(r: Row): void {
}
}
// Permission replies: emit the structured event instead of relaying as
// chat. Owner-only — same gate as the send side.
const permMatch = isSelfChat ? PERMISSION_REPLY_RE.exec(text) : null
// Permission-reply intercept: if this looks like "yes xxxxx" for a
// pending permission request, emit the structured event instead of
// relaying as chat. The sender is already gate()-approved at this point
// (non-allowlisted senders were dropped above; self-chat is the owner),
// so we trust the reply.
const permMatch = PERMISSION_REPLY_RE.exec(text)
if (permMatch) {
void mcp.notification({
method: 'notifications/claude/channel/permission',

View File

@@ -27,7 +27,6 @@ These are Claude Code commands — run `claude` to start a session first.
Install the plugin:
```
/plugin install telegram@claude-plugins-official
/reload-plugins
```
**3. Give the server the token.**