mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-03-21 11:53:08 +00:00
Compare commits
4 Commits
add-plugin
...
kenneth/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f2a4feab9 | ||
|
|
90accf6fd2 | ||
|
|
562a27feec | ||
|
|
8140fbad22 |
@@ -25,7 +25,7 @@ import {
|
|||||||
type Attachment,
|
type Attachment,
|
||||||
} from 'discord.js'
|
} from 'discord.js'
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync } from 'fs'
|
import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import { join, sep } from 'path'
|
import { join, sep } from 'path'
|
||||||
|
|
||||||
@@ -37,6 +37,8 @@ const ENV_FILE = join(STATE_DIR, '.env')
|
|||||||
// Load ~/.claude/channels/discord/.env into process.env. Real env wins.
|
// Load ~/.claude/channels/discord/.env into process.env. Real env wins.
|
||||||
// Plugin-spawned servers don't get an env block — this is where the token lives.
|
// Plugin-spawned servers don't get an env block — this is where the token lives.
|
||||||
try {
|
try {
|
||||||
|
// Token is a credential — lock to owner. No-op on Windows (would need ACLs).
|
||||||
|
chmodSync(ENV_FILE, 0o600)
|
||||||
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
|
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
|
||||||
const m = line.match(/^(\w+)=(.*)$/)
|
const m = line.match(/^(\w+)=(.*)$/)
|
||||||
if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2]
|
if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2]
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ as the correct long-term choice. Don't skip the lockdown offer.
|
|||||||
2. `mkdir -p ~/.claude/channels/discord`
|
2. `mkdir -p ~/.claude/channels/discord`
|
||||||
3. Read existing `.env` if present; update/add the `DISCORD_BOT_TOKEN=` line,
|
3. Read existing `.env` if present; update/add the `DISCORD_BOT_TOKEN=` line,
|
||||||
preserve other keys. Write back, no quotes around the value.
|
preserve other keys. Write back, no quotes around the value.
|
||||||
4. Confirm, then show the no-args status so the user sees where they stand.
|
4. `chmod 600 ~/.claude/channels/discord/.env` — the token is a credential.
|
||||||
|
5. Confirm, then show the no-args status so the user sees where they stand.
|
||||||
|
|
||||||
### `clear` — remove the token
|
### `clear` — remove the token
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { Bot, InputFile, type Context } from 'grammy'
|
import { Bot, InputFile, type Context } from 'grammy'
|
||||||
import type { ReactionTypeEmoji } from 'grammy/types'
|
import type { ReactionTypeEmoji } from 'grammy/types'
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync } from 'fs'
|
import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import { join, extname, sep } from 'path'
|
import { join, extname, sep } from 'path'
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ const ENV_FILE = join(STATE_DIR, '.env')
|
|||||||
// Load ~/.claude/channels/telegram/.env into process.env. Real env wins.
|
// Load ~/.claude/channels/telegram/.env into process.env. Real env wins.
|
||||||
// Plugin-spawned servers don't get an env block — this is where the token lives.
|
// Plugin-spawned servers don't get an env block — this is where the token lives.
|
||||||
try {
|
try {
|
||||||
|
// Token is a credential — lock to owner. No-op on Windows (would need ACLs).
|
||||||
|
chmodSync(ENV_FILE, 0o600)
|
||||||
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
|
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
|
||||||
const m = line.match(/^(\w+)=(.*)$/)
|
const m = line.match(/^(\w+)=(.*)$/)
|
||||||
if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2]
|
if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2]
|
||||||
@@ -49,6 +51,15 @@ if (!TOKEN) {
|
|||||||
}
|
}
|
||||||
const INBOX_DIR = join(STATE_DIR, 'inbox')
|
const INBOX_DIR = join(STATE_DIR, 'inbox')
|
||||||
|
|
||||||
|
// Last-resort safety net — without these the process dies silently on any
|
||||||
|
// unhandled promise rejection. With them it logs and keeps serving tools.
|
||||||
|
process.on('unhandledRejection', err => {
|
||||||
|
process.stderr.write(`telegram channel: unhandled rejection: ${err}\n`)
|
||||||
|
})
|
||||||
|
process.on('uncaughtException', err => {
|
||||||
|
process.stderr.write(`telegram channel: uncaught exception: ${err}\n`)
|
||||||
|
})
|
||||||
|
|
||||||
const bot = new Bot(TOKEN)
|
const bot = new Bot(TOKEN)
|
||||||
let botUsername = ''
|
let botUsername = ''
|
||||||
|
|
||||||
@@ -575,7 +586,7 @@ async function handleInbound(
|
|||||||
|
|
||||||
// image_path goes in meta only — an in-content "[image attached — read: PATH]"
|
// image_path goes in meta only — an in-content "[image attached — read: PATH]"
|
||||||
// annotation is forgeable by any allowlisted sender typing that string.
|
// annotation is forgeable by any allowlisted sender typing that string.
|
||||||
void mcp.notification({
|
mcp.notification({
|
||||||
method: 'notifications/claude/channel',
|
method: 'notifications/claude/channel',
|
||||||
params: {
|
params: {
|
||||||
content: text,
|
content: text,
|
||||||
@@ -588,12 +599,25 @@ async function handleInbound(
|
|||||||
...(imagePath ? { image_path: imagePath } : {}),
|
...(imagePath ? { image_path: imagePath } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}).catch(err => {
|
||||||
|
process.stderr.write(`telegram channel: failed to deliver inbound to Claude: ${err}\n`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
void bot.start({
|
// Without this, any throw in a message handler stops polling permanently
|
||||||
|
// (grammy's default error handler calls bot.stop() and rethrows).
|
||||||
|
bot.catch(err => {
|
||||||
|
process.stderr.write(`telegram channel: handler error (polling continues): ${err.error}\n`)
|
||||||
|
})
|
||||||
|
|
||||||
|
bot.start({
|
||||||
onStart: info => {
|
onStart: info => {
|
||||||
botUsername = info.username
|
botUsername = info.username
|
||||||
process.stderr.write(`telegram channel: polling as @${info.username}\n`)
|
process.stderr.write(`telegram channel: polling as @${info.username}\n`)
|
||||||
},
|
},
|
||||||
|
}).catch(err => {
|
||||||
|
// bot.start() only rejects if polling can't begin or dies unrecoverably —
|
||||||
|
// bad token, 409 conflict, network gone. Log it so the user isn't left
|
||||||
|
// wondering why messages stopped arriving.
|
||||||
|
process.stderr.write(`telegram channel: polling stopped: ${err}\n`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ offer.
|
|||||||
2. `mkdir -p ~/.claude/channels/telegram`
|
2. `mkdir -p ~/.claude/channels/telegram`
|
||||||
3. Read existing `.env` if present; update/add the `TELEGRAM_BOT_TOKEN=` line,
|
3. Read existing `.env` if present; update/add the `TELEGRAM_BOT_TOKEN=` line,
|
||||||
preserve other keys. Write back, no quotes around the value.
|
preserve other keys. Write back, no quotes around the value.
|
||||||
4. Confirm, then show the no-args status so the user sees where they stand.
|
4. `chmod 600 ~/.claude/channels/telegram/.env` — the token is a credential.
|
||||||
|
5. Confirm, then show the no-args status so the user sees where they stand.
|
||||||
|
|
||||||
### `clear` — remove the token
|
### `clear` — remove the token
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user