mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-04-23 01:42:43 +00:00
Compare commits
2 Commits
add-sap-ca
...
telegram-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec682345d1 | ||
|
|
fce00fe3c3 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "discord",
|
"name": "discord",
|
||||||
"description": "Discord channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /discord:access.",
|
"description": "Discord channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /discord:access.",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"discord",
|
"discord",
|
||||||
"messaging",
|
"messaging",
|
||||||
|
|||||||
@@ -463,6 +463,9 @@ const mcp = new Server(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Stores full permission details for "See more" expansion keyed by request_id.
|
||||||
|
const pendingPermissions = new Map<string, { tool_name: string; description: string; input_preview: string }>()
|
||||||
|
|
||||||
// Receive permission_request from CC → format → send to all allowlisted DMs.
|
// Receive permission_request from CC → format → send to all allowlisted DMs.
|
||||||
// Groups are intentionally excluded — the security thread resolution was
|
// Groups are intentionally excluded — the security thread resolution was
|
||||||
// "single-user mode for official plugins." Anyone in access.allowFrom
|
// "single-user mode for official plugins." Anyone in access.allowFrom
|
||||||
@@ -479,12 +482,14 @@ mcp.setNotificationHandler(
|
|||||||
}),
|
}),
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const { request_id, tool_name, description, input_preview } = params
|
const { request_id, tool_name, description, input_preview } = params
|
||||||
|
pendingPermissions.set(request_id, { tool_name, description, input_preview })
|
||||||
const access = loadAccess()
|
const access = loadAccess()
|
||||||
const text =
|
const text = `🔐 Permission: ${tool_name}`
|
||||||
`🔐 Permission request [${request_id}]\n` +
|
|
||||||
`${tool_name}: ${description}\n` +
|
|
||||||
`${input_preview}`
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`perm:more:${request_id}`)
|
||||||
|
.setLabel('See more')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`perm:allow:${request_id}`)
|
.setCustomId(`perm:allow:${request_id}`)
|
||||||
.setLabel('Allow')
|
.setLabel('Allow')
|
||||||
@@ -734,11 +739,11 @@ client.on('error', err => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Button-click handler for permission requests. customId is
|
// Button-click handler for permission requests. customId is
|
||||||
// `perm:allow:<id>` or `perm:deny:<id>` — set when the request was sent.
|
// `perm:allow:<id>`, `perm:deny:<id>`, or `perm:more:<id>`.
|
||||||
// Security mirrors the text-reply path: allowFrom must contain the sender.
|
// Security mirrors the text-reply path: allowFrom must contain the sender.
|
||||||
client.on('interactionCreate', async (interaction: Interaction) => {
|
client.on('interactionCreate', async (interaction: Interaction) => {
|
||||||
if (!interaction.isButton()) return
|
if (!interaction.isButton()) return
|
||||||
const m = /^perm:(allow|deny):([a-km-z]{5})$/.exec(interaction.customId)
|
const m = /^perm:(allow|deny|more):([a-km-z]{5})$/.exec(interaction.customId)
|
||||||
if (!m) return
|
if (!m) return
|
||||||
const access = loadAccess()
|
const access = loadAccess()
|
||||||
if (!access.allowFrom.includes(interaction.user.id)) {
|
if (!access.allowFrom.includes(interaction.user.id)) {
|
||||||
@@ -746,10 +751,46 @@ client.on('interactionCreate', async (interaction: Interaction) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [, behavior, request_id] = m
|
const [, behavior, request_id] = m
|
||||||
|
|
||||||
|
if (behavior === 'more') {
|
||||||
|
const details = pendingPermissions.get(request_id)
|
||||||
|
if (!details) {
|
||||||
|
await interaction.reply({ content: 'Details no longer available.', ephemeral: true }).catch(() => {})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { tool_name, description, input_preview } = details
|
||||||
|
let prettyInput: string
|
||||||
|
try {
|
||||||
|
prettyInput = JSON.stringify(JSON.parse(input_preview), null, 2)
|
||||||
|
} catch {
|
||||||
|
prettyInput = input_preview
|
||||||
|
}
|
||||||
|
const expanded =
|
||||||
|
`🔐 Permission: ${tool_name}\n\n` +
|
||||||
|
`tool_name: ${tool_name}\n` +
|
||||||
|
`description: ${description}\n` +
|
||||||
|
`input_preview:\n${prettyInput}`
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`perm:allow:${request_id}`)
|
||||||
|
.setLabel('Allow')
|
||||||
|
.setEmoji('✅')
|
||||||
|
.setStyle(ButtonStyle.Success),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`perm:deny:${request_id}`)
|
||||||
|
.setLabel('Deny')
|
||||||
|
.setEmoji('❌')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
)
|
||||||
|
await interaction.update({ content: expanded, components: [row] }).catch(() => {})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
void mcp.notification({
|
void mcp.notification({
|
||||||
method: 'notifications/claude/channel/permission',
|
method: 'notifications/claude/channel/permission',
|
||||||
params: { request_id, behavior },
|
params: { request_id, behavior },
|
||||||
})
|
})
|
||||||
|
pendingPermissions.delete(request_id)
|
||||||
const label = behavior === 'allow' ? '✅ Allowed' : '❌ Denied'
|
const label = behavior === 'allow' ? '✅ Allowed' : '❌ Denied'
|
||||||
// Replace buttons with the outcome so the same request can't be answered
|
// Replace buttons with the outcome so the same request can't be answered
|
||||||
// twice and the chat history shows what was chosen.
|
// twice and the chat history shows what was chosen.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "telegram",
|
"name": "telegram",
|
||||||
"description": "Telegram channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /telegram:access.",
|
"description": "Telegram channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /telegram:access.",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"telegram",
|
"telegram",
|
||||||
"messaging",
|
"messaging",
|
||||||
|
|||||||
@@ -379,6 +379,9 @@ const mcp = new Server(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Stores full permission details for "See more" expansion keyed by request_id.
|
||||||
|
const pendingPermissions = new Map<string, { tool_name: string; description: string; input_preview: string }>()
|
||||||
|
|
||||||
// Receive permission_request from CC → format → send to all allowlisted DMs.
|
// Receive permission_request from CC → format → send to all allowlisted DMs.
|
||||||
// Groups are intentionally excluded — the security thread resolution was
|
// Groups are intentionally excluded — the security thread resolution was
|
||||||
// "single-user mode for official plugins." Anyone in access.allowFrom
|
// "single-user mode for official plugins." Anyone in access.allowFrom
|
||||||
@@ -395,12 +398,11 @@ mcp.setNotificationHandler(
|
|||||||
}),
|
}),
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const { request_id, tool_name, description, input_preview } = params
|
const { request_id, tool_name, description, input_preview } = params
|
||||||
|
pendingPermissions.set(request_id, { tool_name, description, input_preview })
|
||||||
const access = loadAccess()
|
const access = loadAccess()
|
||||||
const text =
|
const text = `🔐 Permission: ${tool_name}`
|
||||||
`🔐 Permission request [${request_id}]\n` +
|
|
||||||
`${tool_name}: ${description}\n` +
|
|
||||||
`${input_preview}`
|
|
||||||
const keyboard = new InlineKeyboard()
|
const keyboard = new InlineKeyboard()
|
||||||
|
.text('See more', `perm:more:${request_id}`)
|
||||||
.text('✅ Allow', `perm:allow:${request_id}`)
|
.text('✅ Allow', `perm:allow:${request_id}`)
|
||||||
.text('❌ Deny', `perm:deny:${request_id}`)
|
.text('❌ Deny', `perm:deny:${request_id}`)
|
||||||
for (const chat_id of access.allowFrom) {
|
for (const chat_id of access.allowFrom) {
|
||||||
@@ -686,11 +688,11 @@ bot.command('status', async ctx => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Inline-button handler for permission requests. Callback data is
|
// Inline-button handler for permission requests. Callback data is
|
||||||
// `perm:allow:<id>` or `perm:deny:<id>` — set when the request was sent.
|
// `perm:allow:<id>`, `perm:deny:<id>`, or `perm:more:<id>`.
|
||||||
// Security mirrors the text-reply path: allowFrom must contain the sender.
|
// Security mirrors the text-reply path: allowFrom must contain the sender.
|
||||||
bot.on('callback_query:data', async ctx => {
|
bot.on('callback_query:data', async ctx => {
|
||||||
const data = ctx.callbackQuery.data
|
const data = ctx.callbackQuery.data
|
||||||
const m = /^perm:(allow|deny):([a-km-z]{5})$/.exec(data)
|
const m = /^perm:(allow|deny|more):([a-km-z]{5})$/.exec(data)
|
||||||
if (!m) {
|
if (!m) {
|
||||||
await ctx.answerCallbackQuery().catch(() => {})
|
await ctx.answerCallbackQuery().catch(() => {})
|
||||||
return
|
return
|
||||||
@@ -702,10 +704,38 @@ bot.on('callback_query:data', async ctx => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [, behavior, request_id] = m
|
const [, behavior, request_id] = m
|
||||||
|
|
||||||
|
if (behavior === 'more') {
|
||||||
|
const details = pendingPermissions.get(request_id)
|
||||||
|
if (!details) {
|
||||||
|
await ctx.answerCallbackQuery({ text: 'Details no longer available.' }).catch(() => {})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { tool_name, description, input_preview } = details
|
||||||
|
let prettyInput: string
|
||||||
|
try {
|
||||||
|
prettyInput = JSON.stringify(JSON.parse(input_preview), null, 2)
|
||||||
|
} catch {
|
||||||
|
prettyInput = input_preview
|
||||||
|
}
|
||||||
|
const expanded =
|
||||||
|
`🔐 Permission: ${tool_name}\n\n` +
|
||||||
|
`tool_name: ${tool_name}\n` +
|
||||||
|
`description: ${description}\n` +
|
||||||
|
`input_preview:\n${prettyInput}`
|
||||||
|
const keyboard = new InlineKeyboard()
|
||||||
|
.text('✅ Allow', `perm:allow:${request_id}`)
|
||||||
|
.text('❌ Deny', `perm:deny:${request_id}`)
|
||||||
|
await ctx.editMessageText(expanded, { reply_markup: keyboard }).catch(() => {})
|
||||||
|
await ctx.answerCallbackQuery().catch(() => {})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
void mcp.notification({
|
void mcp.notification({
|
||||||
method: 'notifications/claude/channel/permission',
|
method: 'notifications/claude/channel/permission',
|
||||||
params: { request_id, behavior },
|
params: { request_id, behavior },
|
||||||
})
|
})
|
||||||
|
pendingPermissions.delete(request_id)
|
||||||
const label = behavior === 'allow' ? '✅ Allowed' : '❌ Denied'
|
const label = behavior === 'allow' ? '✅ Allowed' : '❌ Denied'
|
||||||
await ctx.answerCallbackQuery({ text: label }).catch(() => {})
|
await ctx.answerCallbackQuery({ text: label }).catch(() => {})
|
||||||
// Replace buttons with the outcome so the same request can't be answered
|
// Replace buttons with the outcome so the same request can't be answered
|
||||||
|
|||||||
Reference in New Issue
Block a user