# @kody/discord — agent notes Human setup and intent live in [`README.md`](./README.md). This file is for agents: imports, smoke/dryRun snippets, and edge cases. Secrets by **name** only — never paste token values. Do not disable live webhooks or jobs. ## Secrets / integrations | Lane | Credential | Default name | | --- | --- | --- | | Bot token | User secret | `discordBotToken` | | User OAuth | Saved integration | `discord` | Override with `secretName` (bot) or `integration` (OAuth). Extra accounts: `discord-work` / `discordBotTokenWork` — never hard-code personal aliases. ## Import paths | Export | Import | | --- | --- | | overview | `kody:@kody/discord` | | smoke-test | `kody:@kody/discord/smoke-test` | | get-me | `kody:@kody/discord/get-me` | | list-guilds | `kody:@kody/discord/list-guilds` | | fetch-channel | `kody:@kody/discord/fetch-channel` | | fetch-channel-messages | `kody:@kody/discord/fetch-channel-messages` | | post-message | `kody:@kody/discord/post-message` | | edit-message | `kody:@kody/discord/edit-message` | | add-reaction | `kody:@kody/discord/add-reaction` | | create-thread | `kody:@kody/discord/create-thread` | | list-channel-threads | `kody:@kody/discord/list-channel-threads` | Prefer static `kody:@kody/discord/...` imports from `execute`. Static imports run in this package's runtime so secret mounts resolve. ## Smoke test (read-only) Bot lane (reads approved `discordBotToken`): ```js import smokeTest from 'kody:@kody/discord/smoke-test' export default async function main() { return await smokeTest({ lane: 'bot' }) } ``` OAuth lane: ```js import smokeTest from 'kody:@kody/discord/smoke-test' export default async function main() { return await smokeTest({ lane: 'oauth', integration: 'discord' }) } ``` Static identity read (OAuth): ```js import getMe from 'kody:@kody/discord/get-me' export default async function main() { return getMe({ lane: 'oauth', integration: 'discord' }) } ``` ## dryRun / confirm (mutations) Mutations (`post-message`, `edit-message`, `add-reaction`, `create-thread`) accept `dryRun: true` (preview payload, no API write). Live writes require `confirm: true` after the user approved the exact channel and content. ```js import postMessage from 'kody:@kody/discord/post-message' export default async function main() { return postMessage({ channelId: '1234567890', content: 'Hello from Kody', dryRun: true, }) } ``` ## Edge cases - OAuth never grants channel content — use the bot lane for messages/reactions/threads. - Empty message `content` on an otherwise successful read: enable **Message Content** on the Bot tab. - Helpers throw `DiscordApiError` with `missingScope` and `nextStep` on 401/403. - **OAuth 401/403:** reconnect at `/connect/oauth?provider=…&scopes=…`. Do not cache raw tokens (7-day expiry). - **Bot 401:** rotate/save `discordBotToken`. Package always sends the `Bot ` prefix. - **Bot 403 code 50001 Missing Access:** bot not in server / cannot see channel — reinstall with `scope=bot`. - **Bot 403 code 50013 Missing Permissions:** grant missing channel permission or privileged intent, then retry. - `list-channel-threads` requires both `channelId` and `guildId`. - Discord rate-limits reactions aggressively — add sequentially and honor `retry_after` on HTTP 429. - Only messages posted by the same bot can be edited. ## Fork / adapt 1. Fork this listing into the caller's account (kody id stays `discord`). 2. Connect **their** bot secret and/or OAuth integration; approve `discord.com`. 3. Run `./smoke-test` for each lane before treating the copy as ready. 4. Keep `LICENSE`, `## Intent`, and `community-icon.svg` intact for community listing.