Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions external_plugins/discord/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
{
"name": "discord",
"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.4",
"version": "0.0.5",
"keywords": [
"discord",
"messaging",
"channel",
"mcp"
]
],
"userConfig": {
"DISCORD_BOT_TOKEN": {
"type": "string",
"title": "Bot Token",
"description": "Bot token from the Discord Developer Portal. Stored in keychain (macOS) or ~/.claude/.credentials.json with 0600 permissions elsewhere. Never written to settings.json.",
"required": true,
"sensitive": true
}
}
}
5 changes: 4 additions & 1 deletion external_plugins/discord/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"mcpServers": {
"discord": {
"command": "bun",
"args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}", "--shell=bun", "--silent", "start"]
"args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}", "--shell=bun", "--silent", "start"],
"env": {
"DISCORD_BOT_TOKEN": "${user_config.DISCORD_BOT_TOKEN}"
}
}
}
}
12 changes: 7 additions & 5 deletions external_plugins/discord/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ const ACCESS_FILE = join(STATE_DIR, 'access.json')
const APPROVED_DIR = join(STATE_DIR, 'approved')
const ENV_FILE = join(STATE_DIR, '.env')

// 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.
// Token is injected via ${user_config.DISCORD_BOT_TOKEN} from .mcp.json —
// prompted at enable time, stored in keychain (macOS) or .credentials.json 0600
// elsewhere. The .env file below is a legacy fallback for users configured
// before H1 #3617646 — real env wins, so the injected value takes precedence.
try {
// Token is a credential — lock to owner. No-op on Windows (would need ACLs).
// Defensive chmod for legacy .env files (no-op on Windows).
chmodSync(ENV_FILE, 0o600)
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
const m = line.match(/^(\w+)=(.*)$/)
Expand All @@ -56,8 +58,8 @@ const STATIC = process.env.DISCORD_ACCESS_MODE === 'static'
if (!TOKEN) {
process.stderr.write(
`discord channel: DISCORD_BOT_TOKEN required\n` +
` set in ${ENV_FILE}\n` +
` format: DISCORD_BOT_TOKEN=MTIz...\n`,
` re-enter via: /plugin manage → discord → Configure options\n` +
` (stored in keychain/credentials.json, not settings.json)\n`,
)
process.exit(1)
}
Expand Down
13 changes: 11 additions & 2 deletions external_plugins/telegram/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
{
"name": "telegram",
"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.4",
"version": "0.0.5",
"keywords": [
"telegram",
"messaging",
"channel",
"mcp"
]
],
"userConfig": {
"TELEGRAM_BOT_TOKEN": {
"type": "string",
"title": "Bot Token",
"description": "Bot token from @BotFather — format is 123456789:AAH... Stored in keychain (macOS) or ~/.claude/.credentials.json with 0600 permissions elsewhere. Never written to settings.json.",
"required": true,
"sensitive": true
}
}
}
5 changes: 4 additions & 1 deletion external_plugins/telegram/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"mcpServers": {
"telegram": {
"command": "bun",
"args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}", "--shell=bun", "--silent", "start"]
"args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}", "--shell=bun", "--silent", "start"],
"env": {
"TELEGRAM_BOT_TOKEN": "${user_config.TELEGRAM_BOT_TOKEN}"
}
}
}
}
12 changes: 7 additions & 5 deletions external_plugins/telegram/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ const ACCESS_FILE = join(STATE_DIR, 'access.json')
const APPROVED_DIR = join(STATE_DIR, 'approved')
const ENV_FILE = join(STATE_DIR, '.env')

// 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.
// Token is injected via ${user_config.TELEGRAM_BOT_TOKEN} from .mcp.json —
// prompted at enable time, stored in keychain (macOS) or .credentials.json 0600
// elsewhere. The .env file below is a legacy fallback for users configured
// before H1 #3617646 — real env wins, so the injected value takes precedence.
try {
// Token is a credential — lock to owner. No-op on Windows (would need ACLs).
// Defensive chmod for legacy .env files (no-op on Windows).
chmodSync(ENV_FILE, 0o600)
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
const m = line.match(/^(\w+)=(.*)$/)
Expand All @@ -45,8 +47,8 @@ const STATIC = process.env.TELEGRAM_ACCESS_MODE === 'static'
if (!TOKEN) {
process.stderr.write(
`telegram channel: TELEGRAM_BOT_TOKEN required\n` +
` set in ${ENV_FILE}\n` +
` format: TELEGRAM_BOT_TOKEN=123456789:AAH...\n`,
` re-enter via: /plugin manage → telegram → Configure options\n` +
` (stored in keychain/credentials.json, not settings.json)\n`,
)
process.exit(1)
}
Expand Down