agent-messenger 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/release.md +92 -0
- package/.claude-plugin/README.md +144 -0
- package/.claude-plugin/marketplace.json +37 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.github/workflows/ci.yml +30 -0
- package/CLAUDE.md +106 -0
- package/CONTRIBUTING.md +131 -0
- package/README.md +140 -0
- package/biome.json +34 -0
- package/bun.lock +252 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +140 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/channel.d.ts +3 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +118 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/file.d.ts +3 -0
- package/dist/commands/file.d.ts.map +1 -0
- package/dist/commands/file.js +113 -0
- package/dist/commands/file.js.map +1 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +9 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/message.d.ts +3 -0
- package/dist/commands/message.d.ts.map +1 -0
- package/dist/commands/message.js +214 -0
- package/dist/commands/message.js.map +1 -0
- package/dist/commands/reaction.d.ts +3 -0
- package/dist/commands/reaction.d.ts.map +1 -0
- package/dist/commands/reaction.js +100 -0
- package/dist/commands/reaction.js.map +1 -0
- package/dist/commands/snapshot.d.ts +3 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +88 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/user.d.ts +3 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +96 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +89 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/lib/credential-manager.d.ts +13 -0
- package/dist/lib/credential-manager.d.ts.map +1 -0
- package/dist/lib/credential-manager.js +58 -0
- package/dist/lib/credential-manager.js.map +1 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/ref-manager.d.ts +26 -0
- package/dist/lib/ref-manager.d.ts.map +1 -0
- package/dist/lib/ref-manager.js +92 -0
- package/dist/lib/ref-manager.js.map +1 -0
- package/dist/lib/slack-client.d.ts +37 -0
- package/dist/lib/slack-client.d.ts.map +1 -0
- package/dist/lib/slack-client.js +379 -0
- package/dist/lib/slack-client.js.map +1 -0
- package/dist/lib/token-extractor.d.ts +28 -0
- package/dist/lib/token-extractor.d.ts.map +1 -0
- package/dist/lib/token-extractor.js +401 -0
- package/dist/lib/token-extractor.js.map +1 -0
- package/dist/package.json +37 -0
- package/dist/src/cli.d.ts +5 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +22 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/platforms/discord/cli.d.ts +5 -0
- package/dist/src/platforms/discord/cli.d.ts.map +1 -0
- package/dist/src/platforms/discord/cli.js +22 -0
- package/dist/src/platforms/discord/cli.js.map +1 -0
- package/dist/src/platforms/discord/client.d.ts +34 -0
- package/dist/src/platforms/discord/client.d.ts.map +1 -0
- package/dist/src/platforms/discord/client.js +187 -0
- package/dist/src/platforms/discord/client.js.map +1 -0
- package/dist/src/platforms/discord/client.test.d.ts +2 -0
- package/dist/src/platforms/discord/client.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/client.test.js +367 -0
- package/dist/src/platforms/discord/client.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/auth.d.ts +13 -0
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/auth.js +155 -0
- package/dist/src/platforms/discord/commands/auth.js.map +1 -0
- package/dist/src/platforms/discord/commands/auth.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/auth.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/auth.test.js +65 -0
- package/dist/src/platforms/discord/commands/auth.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/channel.d.ts +13 -0
- package/dist/src/platforms/discord/commands/channel.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/channel.js +99 -0
- package/dist/src/platforms/discord/commands/channel.js.map +1 -0
- package/dist/src/platforms/discord/commands/channel.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/channel.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/channel.test.js +136 -0
- package/dist/src/platforms/discord/commands/channel.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/file.d.ts +13 -0
- package/dist/src/platforms/discord/commands/file.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/file.js +99 -0
- package/dist/src/platforms/discord/commands/file.js.map +1 -0
- package/dist/src/platforms/discord/commands/file.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/file.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/file.test.js +83 -0
- package/dist/src/platforms/discord/commands/file.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/guild.d.ts +15 -0
- package/dist/src/platforms/discord/commands/guild.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/guild.js +102 -0
- package/dist/src/platforms/discord/commands/guild.js.map +1 -0
- package/dist/src/platforms/discord/commands/guild.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/guild.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/guild.test.js +100 -0
- package/dist/src/platforms/discord/commands/guild.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/index.d.ts +9 -0
- package/dist/src/platforms/discord/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/index.js +9 -0
- package/dist/src/platforms/discord/commands/index.js.map +1 -0
- package/dist/src/platforms/discord/commands/message.d.ts +17 -0
- package/dist/src/platforms/discord/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/message.js +131 -0
- package/dist/src/platforms/discord/commands/message.js.map +1 -0
- package/dist/src/platforms/discord/commands/message.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/message.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/message.test.js +91 -0
- package/dist/src/platforms/discord/commands/message.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.d.ts +12 -0
- package/dist/src/platforms/discord/commands/reaction.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.js +99 -0
- package/dist/src/platforms/discord/commands/reaction.js.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/reaction.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.test.js +115 -0
- package/dist/src/platforms/discord/commands/reaction.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.d.ts +9 -0
- package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.js +80 -0
- package/dist/src/platforms/discord/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/snapshot.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.test.js +25 -0
- package/dist/src/platforms/discord/commands/snapshot.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/user.d.ts +3 -0
- package/dist/src/platforms/discord/commands/user.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/user.js +94 -0
- package/dist/src/platforms/discord/commands/user.js.map +1 -0
- package/dist/src/platforms/discord/commands/user.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/user.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/user.test.js +103 -0
- package/dist/src/platforms/discord/commands/user.test.js.map +1 -0
- package/dist/src/platforms/discord/credential-manager.d.ts +33 -0
- package/dist/src/platforms/discord/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/discord/credential-manager.js +73 -0
- package/dist/src/platforms/discord/credential-manager.js.map +1 -0
- package/dist/src/platforms/discord/credential-manager.test.d.ts +2 -0
- package/dist/src/platforms/discord/credential-manager.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/credential-manager.test.js +136 -0
- package/dist/src/platforms/discord/credential-manager.test.js.map +1 -0
- package/dist/src/platforms/discord/token-extractor.d.ts +55 -0
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/discord/token-extractor.js +462 -0
- package/dist/src/platforms/discord/token-extractor.js.map +1 -0
- package/dist/src/platforms/discord/token-extractor.test.d.ts +2 -0
- package/dist/src/platforms/discord/token-extractor.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/token-extractor.test.js +789 -0
- package/dist/src/platforms/discord/token-extractor.test.js.map +1 -0
- package/dist/src/platforms/discord/types.d.ts +251 -0
- package/dist/src/platforms/discord/types.d.ts.map +1 -0
- package/dist/src/platforms/discord/types.js +74 -0
- package/dist/src/platforms/discord/types.js.map +1 -0
- package/dist/src/platforms/discord/types.test.d.ts +2 -0
- package/dist/src/platforms/discord/types.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/types.test.js +211 -0
- package/dist/src/platforms/discord/types.test.js.map +1 -0
- package/dist/src/platforms/slack/cli.d.ts +5 -0
- package/dist/src/platforms/slack/cli.d.ts.map +1 -0
- package/dist/src/platforms/slack/cli.js +22 -0
- package/dist/src/platforms/slack/cli.js.map +1 -0
- package/dist/src/platforms/slack/client.d.ts +47 -0
- package/dist/src/platforms/slack/client.d.ts.map +1 -0
- package/dist/src/platforms/slack/client.js +412 -0
- package/dist/src/platforms/slack/client.js.map +1 -0
- package/dist/src/platforms/slack/commands/auth.d.ts +3 -0
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/auth.js +156 -0
- package/dist/src/platforms/slack/commands/auth.js.map +1 -0
- package/dist/src/platforms/slack/commands/channel.d.ts +3 -0
- package/dist/src/platforms/slack/commands/channel.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/channel.js +118 -0
- package/dist/src/platforms/slack/commands/channel.js.map +1 -0
- package/dist/src/platforms/slack/commands/file.d.ts +3 -0
- package/dist/src/platforms/slack/commands/file.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/file.js +113 -0
- package/dist/src/platforms/slack/commands/file.js.map +1 -0
- package/dist/src/platforms/slack/commands/index.d.ts +9 -0
- package/dist/src/platforms/slack/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/index.js +9 -0
- package/dist/src/platforms/slack/commands/index.js.map +1 -0
- package/dist/src/platforms/slack/commands/message.d.ts +3 -0
- package/dist/src/platforms/slack/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/message.js +263 -0
- package/dist/src/platforms/slack/commands/message.js.map +1 -0
- package/dist/src/platforms/slack/commands/reaction.d.ts +3 -0
- package/dist/src/platforms/slack/commands/reaction.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/reaction.js +100 -0
- package/dist/src/platforms/slack/commands/reaction.js.map +1 -0
- package/dist/src/platforms/slack/commands/snapshot.d.ts +3 -0
- package/dist/src/platforms/slack/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/snapshot.js +87 -0
- package/dist/src/platforms/slack/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/slack/commands/user.d.ts +3 -0
- package/dist/src/platforms/slack/commands/user.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/user.js +96 -0
- package/dist/src/platforms/slack/commands/user.js.map +1 -0
- package/dist/src/platforms/slack/commands/workspace.d.ts +3 -0
- package/dist/src/platforms/slack/commands/workspace.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/workspace.js +89 -0
- package/dist/src/platforms/slack/commands/workspace.js.map +1 -0
- package/dist/src/platforms/slack/credential-manager.d.ts +13 -0
- package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/slack/credential-manager.js +58 -0
- package/dist/src/platforms/slack/credential-manager.js.map +1 -0
- package/dist/src/platforms/slack/index.d.ts +3 -0
- package/dist/src/platforms/slack/index.d.ts.map +1 -0
- package/dist/src/platforms/slack/index.js +3 -0
- package/dist/src/platforms/slack/index.js.map +1 -0
- package/dist/src/platforms/slack/token-extractor.d.ts +28 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/slack/token-extractor.js +401 -0
- package/dist/src/platforms/slack/token-extractor.js.map +1 -0
- package/dist/src/platforms/slack/types.d.ts +369 -0
- package/dist/src/platforms/slack/types.d.ts.map +1 -0
- package/dist/src/platforms/slack/types.js +92 -0
- package/dist/src/platforms/slack/types.js.map +1 -0
- package/dist/src/shared/utils/concurrency.d.ts +2 -0
- package/dist/src/shared/utils/concurrency.d.ts.map +1 -0
- package/dist/src/shared/utils/concurrency.js +14 -0
- package/dist/src/shared/utils/concurrency.js.map +1 -0
- package/dist/src/shared/utils/concurrency.test.d.ts +2 -0
- package/dist/src/shared/utils/concurrency.test.d.ts.map +1 -0
- package/dist/src/shared/utils/concurrency.test.js +39 -0
- package/dist/src/shared/utils/concurrency.test.js.map +1 -0
- package/dist/src/shared/utils/error-handler.d.ts +2 -0
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -0
- package/dist/src/shared/utils/error-handler.js +5 -0
- package/dist/src/shared/utils/error-handler.js.map +1 -0
- package/dist/src/shared/utils/output.d.ts +2 -0
- package/dist/src/shared/utils/output.d.ts.map +1 -0
- package/dist/src/shared/utils/output.js +4 -0
- package/dist/src/shared/utils/output.js.map +1 -0
- package/dist/tests/cli.test.d.ts +2 -0
- package/dist/tests/cli.test.d.ts.map +1 -0
- package/dist/tests/cli.test.js +83 -0
- package/dist/tests/cli.test.js.map +1 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/CURRENT +1 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOCK +0 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG +3 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG.old +1 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/MANIFEST-000004 +0 -0
- package/dist/tests/commands/auth.test.d.ts +2 -0
- package/dist/tests/commands/auth.test.d.ts.map +1 -0
- package/dist/tests/commands/auth.test.js +304 -0
- package/dist/tests/commands/auth.test.js.map +1 -0
- package/dist/tests/commands/channel.test.d.ts +2 -0
- package/dist/tests/commands/channel.test.d.ts.map +1 -0
- package/dist/tests/commands/channel.test.js +166 -0
- package/dist/tests/commands/channel.test.js.map +1 -0
- package/dist/tests/commands/file.test.d.ts +2 -0
- package/dist/tests/commands/file.test.d.ts.map +1 -0
- package/dist/tests/commands/file.test.js +175 -0
- package/dist/tests/commands/file.test.js.map +1 -0
- package/dist/tests/commands/message.test.d.ts +2 -0
- package/dist/tests/commands/message.test.d.ts.map +1 -0
- package/dist/tests/commands/message.test.js +293 -0
- package/dist/tests/commands/message.test.js.map +1 -0
- package/dist/tests/commands/reaction.test.d.ts +2 -0
- package/dist/tests/commands/reaction.test.d.ts.map +1 -0
- package/dist/tests/commands/reaction.test.js +84 -0
- package/dist/tests/commands/reaction.test.js.map +1 -0
- package/dist/tests/commands/snapshot.test.d.ts +2 -0
- package/dist/tests/commands/snapshot.test.d.ts.map +1 -0
- package/dist/tests/commands/snapshot.test.js +280 -0
- package/dist/tests/commands/snapshot.test.js.map +1 -0
- package/dist/tests/commands/user.test.d.ts +2 -0
- package/dist/tests/commands/user.test.d.ts.map +1 -0
- package/dist/tests/commands/user.test.js +117 -0
- package/dist/tests/commands/user.test.js.map +1 -0
- package/dist/tests/commands/workspace.test.d.ts +2 -0
- package/dist/tests/commands/workspace.test.d.ts.map +1 -0
- package/dist/tests/commands/workspace.test.js +453 -0
- package/dist/tests/commands/workspace.test.js.map +1 -0
- package/dist/tests/credential-manager.test.d.ts +2 -0
- package/dist/tests/credential-manager.test.d.ts.map +1 -0
- package/dist/tests/credential-manager.test.js +199 -0
- package/dist/tests/credential-manager.test.js.map +1 -0
- package/dist/tests/slack-client.test.d.ts +2 -0
- package/dist/tests/slack-client.test.d.ts.map +1 -0
- package/dist/tests/slack-client.test.js +741 -0
- package/dist/tests/slack-client.test.js.map +1 -0
- package/dist/tests/types.test.d.ts +2 -0
- package/dist/tests/types.test.d.ts.map +1 -0
- package/dist/tests/types.test.js +215 -0
- package/dist/tests/types.test.js.map +1 -0
- package/dist/types/index.d.ts +369 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +92 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/error-handler.d.ts +2 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +5 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/output.d.ts +2 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +4 -0
- package/dist/utils/output.js.map +1 -0
- package/docs/discord.md +182 -0
- package/docs/slack.md +160 -0
- package/package.json +37 -0
- package/skills/agent-discord/SKILL.md +273 -0
- package/skills/agent-discord/references/authentication.md +294 -0
- package/skills/agent-discord/references/common-patterns.md +455 -0
- package/skills/agent-discord/templates/guild-summary.sh +167 -0
- package/skills/agent-discord/templates/monitor-channel.sh +180 -0
- package/skills/agent-discord/templates/post-message.sh +173 -0
- package/skills/agent-slack/SKILL.md +268 -0
- package/skills/agent-slack/references/authentication.md +332 -0
- package/skills/agent-slack/references/common-patterns.md +527 -0
- package/skills/agent-slack/templates/monitor-channel.sh +186 -0
- package/skills/agent-slack/templates/post-message.sh +130 -0
- package/skills/agent-slack/templates/workspace-summary.sh +149 -0
- package/src/cli.ts +29 -0
- package/src/platforms/discord/cli.ts +36 -0
- package/src/platforms/discord/client.test.ts +456 -0
- package/src/platforms/discord/client.ts +281 -0
- package/src/platforms/discord/commands/auth.test.ts +72 -0
- package/src/platforms/discord/commands/auth.ts +206 -0
- package/src/platforms/discord/commands/channel.test.ts +153 -0
- package/src/platforms/discord/commands/channel.ts +127 -0
- package/src/platforms/discord/commands/file.test.ts +98 -0
- package/src/platforms/discord/commands/file.ts +134 -0
- package/src/platforms/discord/commands/guild.test.ts +117 -0
- package/src/platforms/discord/commands/guild.ts +129 -0
- package/src/platforms/discord/commands/index.ts +8 -0
- package/src/platforms/discord/commands/message.test.ts +107 -0
- package/src/platforms/discord/commands/message.ts +182 -0
- package/src/platforms/discord/commands/reaction.test.ts +123 -0
- package/src/platforms/discord/commands/reaction.ts +156 -0
- package/src/platforms/discord/commands/snapshot.test.ts +29 -0
- package/src/platforms/discord/commands/snapshot.ts +104 -0
- package/src/platforms/discord/commands/user.test.ts +115 -0
- package/src/platforms/discord/commands/user.ts +124 -0
- package/src/platforms/discord/credential-manager.test.ts +173 -0
- package/src/platforms/discord/credential-manager.ts +95 -0
- package/src/platforms/discord/token-extractor.test.ts +918 -0
- package/src/platforms/discord/token-extractor.ts +549 -0
- package/src/platforms/discord/types.test.ts +245 -0
- package/src/platforms/discord/types.ts +158 -0
- package/src/platforms/slack/cli.ts +36 -0
- package/src/platforms/slack/client.ts +466 -0
- package/src/platforms/slack/commands/auth.ts +211 -0
- package/src/platforms/slack/commands/channel.ts +158 -0
- package/src/platforms/slack/commands/file.ts +153 -0
- package/src/platforms/slack/commands/index.ts +8 -0
- package/src/platforms/slack/commands/message.ts +369 -0
- package/src/platforms/slack/commands/reaction.ts +166 -0
- package/src/platforms/slack/commands/snapshot.ts +114 -0
- package/src/platforms/slack/commands/user.ts +110 -0
- package/src/platforms/slack/commands/workspace.ts +111 -0
- package/src/platforms/slack/credential-manager.ts +74 -0
- package/src/platforms/slack/index.ts +2 -0
- package/src/platforms/slack/token-extractor.ts +496 -0
- package/src/platforms/slack/types.ts +193 -0
- package/src/shared/utils/concurrency.test.ts +53 -0
- package/src/shared/utils/concurrency.ts +20 -0
- package/src/shared/utils/error-handler.ts +4 -0
- package/src/shared/utils/output.ts +3 -0
- package/tests/cli.test.ts +94 -0
- package/tests/commands/auth.test.ts +383 -0
- package/tests/commands/channel.test.ts +185 -0
- package/tests/commands/file.test.ts +204 -0
- package/tests/commands/message.test.ts +344 -0
- package/tests/commands/reaction.test.ts +101 -0
- package/tests/commands/snapshot.test.ts +308 -0
- package/tests/commands/user.test.ts +138 -0
- package/tests/commands/workspace.test.ts +528 -0
- package/tests/credential-manager.test.ts +241 -0
- package/tests/slack-client.test.ts +916 -0
- package/tests/types.test.ts +241 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DiscordChannel,
|
|
3
|
+
DiscordFile,
|
|
4
|
+
DiscordGuild,
|
|
5
|
+
DiscordMessage,
|
|
6
|
+
DiscordUser,
|
|
7
|
+
} from './types'
|
|
8
|
+
|
|
9
|
+
export class DiscordError extends Error {
|
|
10
|
+
code: string
|
|
11
|
+
|
|
12
|
+
constructor(message: string, code: string) {
|
|
13
|
+
super(message)
|
|
14
|
+
this.name = 'DiscordError'
|
|
15
|
+
this.code = code
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface RateLimitBucket {
|
|
20
|
+
remaining: number
|
|
21
|
+
resetAt: number
|
|
22
|
+
bucketHash: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BASE_URL = 'https://discord.com/api/v10'
|
|
26
|
+
const MAX_RETRIES = 3
|
|
27
|
+
const BASE_BACKOFF_MS = 100
|
|
28
|
+
|
|
29
|
+
export class DiscordClient {
|
|
30
|
+
private token: string
|
|
31
|
+
private buckets: Map<string, RateLimitBucket> = new Map()
|
|
32
|
+
private globalRateLimitUntil: number = 0
|
|
33
|
+
|
|
34
|
+
constructor(token: string) {
|
|
35
|
+
if (!token) {
|
|
36
|
+
throw new DiscordError('Token is required', 'missing_token')
|
|
37
|
+
}
|
|
38
|
+
this.token = token
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private getBucketKey(method: string, path: string): string {
|
|
42
|
+
const normalized = path
|
|
43
|
+
.replace(/\/channels\/\d+/, '/channels/{channel_id}')
|
|
44
|
+
.replace(/\/guilds\/\d+/, '/guilds/{guild_id}')
|
|
45
|
+
.replace(/\/users\/\d+/, '/users/{user_id}')
|
|
46
|
+
.replace(/\/messages\/\d+/, '/messages/{message_id}')
|
|
47
|
+
return `${method}:${normalized}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async waitForRateLimit(bucketKey: string): Promise<void> {
|
|
51
|
+
const now = Date.now()
|
|
52
|
+
|
|
53
|
+
if (this.globalRateLimitUntil > now) {
|
|
54
|
+
await this.sleep(this.globalRateLimitUntil - now)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const bucket = this.buckets.get(bucketKey)
|
|
58
|
+
if (bucket && bucket.remaining === 0 && bucket.resetAt * 1000 > now) {
|
|
59
|
+
await this.sleep(bucket.resetAt * 1000 - now)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private updateBucket(bucketKey: string, response: Response): void {
|
|
64
|
+
const remaining = response.headers.get('X-RateLimit-Remaining')
|
|
65
|
+
const reset = response.headers.get('X-RateLimit-Reset')
|
|
66
|
+
const bucketHash = response.headers.get('X-RateLimit-Bucket')
|
|
67
|
+
|
|
68
|
+
if (remaining !== null && reset !== null && bucketHash !== null) {
|
|
69
|
+
this.buckets.set(bucketKey, {
|
|
70
|
+
remaining: parseInt(remaining, 10),
|
|
71
|
+
resetAt: parseFloat(reset),
|
|
72
|
+
bucketHash,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async handleRateLimitResponse(response: Response): Promise<number> {
|
|
78
|
+
const retryAfter = response.headers.get('Retry-After')
|
|
79
|
+
const isGlobal = response.headers.get('X-RateLimit-Global') === 'true'
|
|
80
|
+
const waitMs = parseFloat(retryAfter || '1') * 1000
|
|
81
|
+
|
|
82
|
+
if (isGlobal) {
|
|
83
|
+
this.globalRateLimitUntil = Date.now() + waitMs
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await this.sleep(waitMs)
|
|
87
|
+
return waitMs
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private sleep(ms: number): Promise<void> {
|
|
91
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
95
|
+
const url = `${BASE_URL}${path}`
|
|
96
|
+
const bucketKey = this.getBucketKey(method, path)
|
|
97
|
+
let lastError: Error | undefined
|
|
98
|
+
|
|
99
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
100
|
+
await this.waitForRateLimit(bucketKey)
|
|
101
|
+
|
|
102
|
+
const headers: Record<string, string> = {
|
|
103
|
+
Authorization: this.token,
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const options: RequestInit = {
|
|
108
|
+
method,
|
|
109
|
+
headers,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (body !== undefined) {
|
|
113
|
+
options.body = JSON.stringify(body)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const response = await fetch(url, options)
|
|
117
|
+
this.updateBucket(bucketKey, response)
|
|
118
|
+
|
|
119
|
+
if (response.status === 429) {
|
|
120
|
+
if (attempt < MAX_RETRIES) {
|
|
121
|
+
await this.handleRateLimitResponse(response)
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
const errorBody = await response.json().catch(() => ({}))
|
|
125
|
+
throw new DiscordError((errorBody as any).message || 'Rate limited', 'rate_limited')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (response.status >= 500 && attempt < MAX_RETRIES) {
|
|
129
|
+
await this.sleep(BASE_BACKOFF_MS * 2 ** attempt)
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
const errorBody = await response.json().catch(() => ({}))
|
|
135
|
+
throw new DiscordError(
|
|
136
|
+
(errorBody as any).message || `HTTP ${response.status}`,
|
|
137
|
+
(errorBody as any).code?.toString() || `http_${response.status}`
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (response.status === 204) {
|
|
142
|
+
return undefined as T
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return response.json() as Promise<T>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
throw lastError || new DiscordError('Request failed after retries', 'max_retries')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async requestFormData<T>(path: string, formData: FormData): Promise<T> {
|
|
152
|
+
const url = `${BASE_URL}${path}`
|
|
153
|
+
const bucketKey = this.getBucketKey('POST', path)
|
|
154
|
+
|
|
155
|
+
await this.waitForRateLimit(bucketKey)
|
|
156
|
+
|
|
157
|
+
const response = await fetch(url, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: {
|
|
160
|
+
Authorization: this.token,
|
|
161
|
+
},
|
|
162
|
+
body: formData,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
this.updateBucket(bucketKey, response)
|
|
166
|
+
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const errorBody = await response.json().catch(() => ({}))
|
|
169
|
+
throw new DiscordError(
|
|
170
|
+
(errorBody as any).message || `HTTP ${response.status}`,
|
|
171
|
+
(errorBody as any).code?.toString() || `http_${response.status}`
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return response.json() as Promise<T>
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async testAuth(): Promise<DiscordUser> {
|
|
179
|
+
return this.request<DiscordUser>('GET', '/users/@me')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async listGuilds(): Promise<DiscordGuild[]> {
|
|
183
|
+
return this.request<DiscordGuild[]>('GET', '/users/@me/guilds')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async getGuild(guildId: string): Promise<DiscordGuild> {
|
|
187
|
+
return this.request<DiscordGuild>('GET', `/guilds/${guildId}`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async listChannels(guildId: string): Promise<DiscordChannel[]> {
|
|
191
|
+
return this.request<DiscordChannel[]>('GET', `/guilds/${guildId}/channels`)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async getChannel(channelId: string): Promise<DiscordChannel> {
|
|
195
|
+
return this.request<DiscordChannel>('GET', `/channels/${channelId}`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async sendMessage(channelId: string, content: string): Promise<DiscordMessage> {
|
|
199
|
+
return this.request<DiscordMessage>('POST', `/channels/${channelId}/messages`, { content })
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async getMessages(channelId: string, limit: number = 50): Promise<DiscordMessage[]> {
|
|
203
|
+
return this.request<DiscordMessage[]>('GET', `/channels/${channelId}/messages?limit=${limit}`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async getMessage(channelId: string, messageId: string): Promise<DiscordMessage> {
|
|
207
|
+
return this.request<DiscordMessage>('GET', `/channels/${channelId}/messages/${messageId}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async deleteMessage(channelId: string, messageId: string): Promise<void> {
|
|
211
|
+
return this.request<void>('DELETE', `/channels/${channelId}/messages/${messageId}`)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async addReaction(channelId: string, messageId: string, emoji: string): Promise<void> {
|
|
215
|
+
const encodedEmoji = encodeURIComponent(emoji)
|
|
216
|
+
return this.request<void>(
|
|
217
|
+
'PUT',
|
|
218
|
+
`/channels/${channelId}/messages/${messageId}/reactions/${encodedEmoji}/@me`
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async removeReaction(channelId: string, messageId: string, emoji: string): Promise<void> {
|
|
223
|
+
const encodedEmoji = encodeURIComponent(emoji)
|
|
224
|
+
return this.request<void>(
|
|
225
|
+
'DELETE',
|
|
226
|
+
`/channels/${channelId}/messages/${messageId}/reactions/${encodedEmoji}/@me`
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async listUsers(guildId: string): Promise<DiscordUser[]> {
|
|
231
|
+
interface GuildMember {
|
|
232
|
+
user: DiscordUser
|
|
233
|
+
}
|
|
234
|
+
const members = await this.request<GuildMember[]>(
|
|
235
|
+
'GET',
|
|
236
|
+
`/guilds/${guildId}/members?limit=1000`
|
|
237
|
+
)
|
|
238
|
+
return members.map((m) => m.user)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async getUser(userId: string): Promise<DiscordUser> {
|
|
242
|
+
return this.request<DiscordUser>('GET', `/users/${userId}`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async uploadFile(channelId: string, filePath: string): Promise<DiscordFile> {
|
|
246
|
+
const file = Bun.file(filePath)
|
|
247
|
+
const filename = filePath.split('/').pop() || 'file'
|
|
248
|
+
const blob = await file.arrayBuffer()
|
|
249
|
+
|
|
250
|
+
const formData = new FormData()
|
|
251
|
+
formData.append('files[0]', new Blob([blob]), filename)
|
|
252
|
+
|
|
253
|
+
interface MessageWithAttachments extends DiscordMessage {
|
|
254
|
+
attachments: DiscordFile[]
|
|
255
|
+
}
|
|
256
|
+
const message = await this.requestFormData<MessageWithAttachments>(
|
|
257
|
+
`/channels/${channelId}/messages`,
|
|
258
|
+
formData
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return message.attachments[0]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async listFiles(channelId: string): Promise<DiscordFile[]> {
|
|
265
|
+
interface MessageWithAttachments extends DiscordMessage {
|
|
266
|
+
attachments: DiscordFile[]
|
|
267
|
+
}
|
|
268
|
+
const messages = await this.request<MessageWithAttachments[]>(
|
|
269
|
+
'GET',
|
|
270
|
+
`/channels/${channelId}/messages?limit=100`
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
const files: DiscordFile[] = []
|
|
274
|
+
for (const msg of messages) {
|
|
275
|
+
if (msg.attachments && msg.attachments.length > 0) {
|
|
276
|
+
files.push(...msg.attachments)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return files
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { expect, mock, test } from 'bun:test'
|
|
2
|
+
import { DiscordClient } from '../client'
|
|
3
|
+
import { DiscordCredentialManager } from '../credential-manager'
|
|
4
|
+
import { DiscordTokenExtractor } from '../token-extractor'
|
|
5
|
+
|
|
6
|
+
// Mock modules
|
|
7
|
+
mock.module('../token-extractor', () => ({
|
|
8
|
+
DiscordTokenExtractor: mock(() => ({
|
|
9
|
+
extract: mock(async () => ({
|
|
10
|
+
token: 'test-token-123',
|
|
11
|
+
})),
|
|
12
|
+
})),
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
mock.module('../client', () => ({
|
|
16
|
+
DiscordClient: mock((_token: string) => ({
|
|
17
|
+
testAuth: mock(async () => ({
|
|
18
|
+
id: 'user-123',
|
|
19
|
+
username: 'testuser',
|
|
20
|
+
})),
|
|
21
|
+
listGuilds: mock(async () => [
|
|
22
|
+
{ id: 'guild-1', name: 'Guild One' },
|
|
23
|
+
{ id: 'guild-2', name: 'Guild Two' },
|
|
24
|
+
]),
|
|
25
|
+
})),
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
mock.module('../credential-manager', () => ({
|
|
29
|
+
DiscordCredentialManager: mock(() => ({
|
|
30
|
+
load: mock(async () => ({
|
|
31
|
+
token: null,
|
|
32
|
+
current_guild: null,
|
|
33
|
+
guilds: {},
|
|
34
|
+
})),
|
|
35
|
+
save: mock(async () => {}),
|
|
36
|
+
clearToken: mock(async () => {}),
|
|
37
|
+
})),
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
test('extract: calls DiscordTokenExtractor', async () => {
|
|
41
|
+
const extractor = new DiscordTokenExtractor()
|
|
42
|
+
const result = await extractor.extract()
|
|
43
|
+
expect(result).toBeDefined()
|
|
44
|
+
expect(result?.token).toBe('test-token-123')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('extract: validates token with DiscordClient', async () => {
|
|
48
|
+
const client = new DiscordClient('test-token-123')
|
|
49
|
+
const authInfo = await client.testAuth()
|
|
50
|
+
expect(authInfo).toBeDefined()
|
|
51
|
+
expect(authInfo.id).toBe('user-123')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('extract: discovers guilds', async () => {
|
|
55
|
+
const client = new DiscordClient('test-token-123')
|
|
56
|
+
const guilds = await client.listGuilds()
|
|
57
|
+
expect(guilds).toHaveLength(2)
|
|
58
|
+
expect(guilds[0].id).toBe('guild-1')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('logout: clears credentials', async () => {
|
|
62
|
+
const credManager = new DiscordCredentialManager()
|
|
63
|
+
await credManager.clearToken()
|
|
64
|
+
expect(credManager.clearToken).toHaveBeenCalled()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('status: returns auth state', async () => {
|
|
68
|
+
const credManager = new DiscordCredentialManager()
|
|
69
|
+
const config = await credManager.load()
|
|
70
|
+
expect(config.token).toBeNull()
|
|
71
|
+
expect(config.current_guild).toBeNull()
|
|
72
|
+
})
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { handleError } from '../../../shared/utils/error-handler'
|
|
3
|
+
import { formatOutput } from '../../../shared/utils/output'
|
|
4
|
+
import { DiscordClient } from '../client'
|
|
5
|
+
import { DiscordCredentialManager } from '../credential-manager'
|
|
6
|
+
import { DiscordTokenExtractor } from '../token-extractor'
|
|
7
|
+
|
|
8
|
+
export async function extractAction(options: { pretty?: boolean; debug?: boolean }): Promise<void> {
|
|
9
|
+
try {
|
|
10
|
+
const extractor = new DiscordTokenExtractor()
|
|
11
|
+
|
|
12
|
+
if (process.platform === 'darwin') {
|
|
13
|
+
console.log('')
|
|
14
|
+
console.log(' Extracting your Discord credentials...')
|
|
15
|
+
console.log('')
|
|
16
|
+
console.log(' Your Mac may ask for your password to access Keychain.')
|
|
17
|
+
console.log(' This is required because Discord encrypts your login token')
|
|
18
|
+
console.log(' using macOS Keychain for security.')
|
|
19
|
+
console.log('')
|
|
20
|
+
console.log(' What happens:')
|
|
21
|
+
console.log(" 1. We read the encrypted token from Discord's local storage")
|
|
22
|
+
console.log(' 2. macOS Keychain decrypts it (requires your password)')
|
|
23
|
+
console.log(' 3. The token is stored locally in ~/.config/agent-messenger/')
|
|
24
|
+
console.log('')
|
|
25
|
+
console.log(' Your password is never stored or transmitted anywhere.')
|
|
26
|
+
console.log('')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (options.debug) {
|
|
30
|
+
console.error(`[debug] Extracting Discord token...`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const extracted = await extractor.extract()
|
|
34
|
+
|
|
35
|
+
if (!extracted) {
|
|
36
|
+
console.log(
|
|
37
|
+
formatOutput(
|
|
38
|
+
{
|
|
39
|
+
error:
|
|
40
|
+
'No Discord token found. Make sure Discord desktop app is installed and logged in.',
|
|
41
|
+
hint: options.debug ? undefined : 'Run with --debug for more info.',
|
|
42
|
+
},
|
|
43
|
+
options.pretty
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options.debug) {
|
|
50
|
+
console.error(`[debug] Token extracted: ${extracted.token.substring(0, 20)}...`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const client = new DiscordClient(extracted.token)
|
|
55
|
+
|
|
56
|
+
if (options.debug) {
|
|
57
|
+
console.error(`[debug] Testing token validity...`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const authInfo = await client.testAuth()
|
|
61
|
+
|
|
62
|
+
if (options.debug) {
|
|
63
|
+
console.error(`[debug] ✓ Token valid for user: ${authInfo.username}`)
|
|
64
|
+
console.error(`[debug] Discovering guilds...`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const guilds = await client.listGuilds()
|
|
68
|
+
|
|
69
|
+
if (options.debug) {
|
|
70
|
+
console.error(`[debug] ✓ Found ${guilds.length} guild(s)`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (guilds.length === 0) {
|
|
74
|
+
console.log(
|
|
75
|
+
formatOutput(
|
|
76
|
+
{
|
|
77
|
+
error: 'No guilds found. Make sure you are a member of at least one Discord server.',
|
|
78
|
+
},
|
|
79
|
+
options.pretty
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const credManager = new DiscordCredentialManager()
|
|
86
|
+
const guildMap: Record<string, { guild_id: string; guild_name: string }> = {}
|
|
87
|
+
|
|
88
|
+
for (const guild of guilds) {
|
|
89
|
+
guildMap[guild.id] = {
|
|
90
|
+
guild_id: guild.id,
|
|
91
|
+
guild_name: guild.name,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const config = {
|
|
96
|
+
token: extracted.token,
|
|
97
|
+
current_guild: guilds[0].id,
|
|
98
|
+
guilds: guildMap,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await credManager.save(config)
|
|
102
|
+
|
|
103
|
+
if (options.debug) {
|
|
104
|
+
console.error(`[debug] ✓ Credentials saved`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const output = {
|
|
108
|
+
guilds: guilds.map((g) => `${g.id}/${g.name}`),
|
|
109
|
+
current: guilds[0].id,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(formatOutput(output, options.pretty))
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.log(
|
|
115
|
+
formatOutput(
|
|
116
|
+
{
|
|
117
|
+
error: `Token validation failed: ${(error as Error).message}`,
|
|
118
|
+
hint: 'Make sure your Discord token is valid and has not expired.',
|
|
119
|
+
},
|
|
120
|
+
options.pretty
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
handleError(error as Error)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function logoutAction(options: { pretty?: boolean }): Promise<void> {
|
|
131
|
+
try {
|
|
132
|
+
const credManager = new DiscordCredentialManager()
|
|
133
|
+
const config = await credManager.load()
|
|
134
|
+
|
|
135
|
+
if (!config.token) {
|
|
136
|
+
console.log(
|
|
137
|
+
formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty)
|
|
138
|
+
)
|
|
139
|
+
process.exit(1)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await credManager.clearToken()
|
|
143
|
+
|
|
144
|
+
console.log(formatOutput({ removed: 'discord', success: true }, options.pretty))
|
|
145
|
+
} catch (error) {
|
|
146
|
+
handleError(error as Error)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function statusAction(options: { pretty?: boolean }): Promise<void> {
|
|
151
|
+
try {
|
|
152
|
+
const credManager = new DiscordCredentialManager()
|
|
153
|
+
const config = await credManager.load()
|
|
154
|
+
|
|
155
|
+
if (!config.token) {
|
|
156
|
+
console.log(
|
|
157
|
+
formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty)
|
|
158
|
+
)
|
|
159
|
+
process.exit(1)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let authInfo: { id: string; username: string } | null = null
|
|
163
|
+
let valid = false
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const client = new DiscordClient(config.token)
|
|
167
|
+
authInfo = await client.testAuth()
|
|
168
|
+
valid = true
|
|
169
|
+
} catch {
|
|
170
|
+
valid = false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const output = {
|
|
174
|
+
authenticated: valid,
|
|
175
|
+
user: authInfo?.username,
|
|
176
|
+
current_guild: config.current_guild,
|
|
177
|
+
guilds_count: Object.keys(config.guilds).length,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(formatOutput(output, options.pretty))
|
|
181
|
+
} catch (error) {
|
|
182
|
+
handleError(error as Error)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const authCommand = new Command('auth')
|
|
187
|
+
.description('Authentication commands')
|
|
188
|
+
.addCommand(
|
|
189
|
+
new Command('extract')
|
|
190
|
+
.description('Extract token from Discord desktop app')
|
|
191
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
192
|
+
.option('--debug', 'Show debug output for troubleshooting')
|
|
193
|
+
.action(extractAction)
|
|
194
|
+
)
|
|
195
|
+
.addCommand(
|
|
196
|
+
new Command('logout')
|
|
197
|
+
.description('Logout from Discord')
|
|
198
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
199
|
+
.action(logoutAction)
|
|
200
|
+
)
|
|
201
|
+
.addCommand(
|
|
202
|
+
new Command('status')
|
|
203
|
+
.description('Show authentication status')
|
|
204
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
205
|
+
.action(statusAction)
|
|
206
|
+
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { expect, mock, test } from 'bun:test'
|
|
2
|
+
import { DiscordClient } from '../client'
|
|
3
|
+
|
|
4
|
+
// Mock modules
|
|
5
|
+
mock.module('../client', () => ({
|
|
6
|
+
DiscordClient: mock((_token: string) => ({
|
|
7
|
+
listChannels: mock(async (guildId: string) => [
|
|
8
|
+
{ id: 'ch-1', guild_id: guildId, name: 'general', type: 0, topic: 'General discussion' },
|
|
9
|
+
{ id: 'ch-2', guild_id: guildId, name: 'announcements', type: 0, topic: 'Announcements' },
|
|
10
|
+
{ id: 'ch-3', guild_id: guildId, name: 'voice-channel', type: 2, topic: null },
|
|
11
|
+
]),
|
|
12
|
+
getChannel: mock(async (channelId: string) => {
|
|
13
|
+
if (channelId === 'ch-1') {
|
|
14
|
+
return {
|
|
15
|
+
id: 'ch-1',
|
|
16
|
+
guild_id: 'guild-1',
|
|
17
|
+
name: 'general',
|
|
18
|
+
type: 0,
|
|
19
|
+
topic: 'General discussion',
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (channelId === 'ch-2') {
|
|
23
|
+
return {
|
|
24
|
+
id: 'ch-2',
|
|
25
|
+
guild_id: 'guild-1',
|
|
26
|
+
name: 'announcements',
|
|
27
|
+
type: 0,
|
|
28
|
+
topic: 'Announcements',
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error('Channel not found')
|
|
32
|
+
}),
|
|
33
|
+
getMessages: mock(async (channelId: string, _limit: number) => [
|
|
34
|
+
{
|
|
35
|
+
id: 'msg-1',
|
|
36
|
+
channel_id: channelId,
|
|
37
|
+
author: { id: 'user-1', username: 'alice' },
|
|
38
|
+
content: 'Hello world',
|
|
39
|
+
timestamp: '2024-01-29T10:00:00Z',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'msg-2',
|
|
43
|
+
channel_id: channelId,
|
|
44
|
+
author: { id: 'user-2', username: 'bob' },
|
|
45
|
+
content: 'Hi there',
|
|
46
|
+
timestamp: '2024-01-29T09:00:00Z',
|
|
47
|
+
},
|
|
48
|
+
]),
|
|
49
|
+
})),
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
mock.module('../credential-manager', () => ({
|
|
53
|
+
DiscordCredentialManager: mock(() => ({
|
|
54
|
+
load: mock(async () => ({
|
|
55
|
+
token: 'test-token',
|
|
56
|
+
current_guild: 'guild-1',
|
|
57
|
+
guilds: {
|
|
58
|
+
'guild-1': { guild_id: 'guild-1', guild_name: 'Guild One' },
|
|
59
|
+
},
|
|
60
|
+
})),
|
|
61
|
+
})),
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
test('list: returns text channels (type=0) from guild', async () => {
|
|
65
|
+
// given: discord client with channels
|
|
66
|
+
const client = new DiscordClient('test-token')
|
|
67
|
+
const channels = await client.listChannels('guild-1')
|
|
68
|
+
|
|
69
|
+
// when: filtering text channels
|
|
70
|
+
const textChannels = channels.filter((ch) => ch.type === 0)
|
|
71
|
+
|
|
72
|
+
// then: only text channels are returned
|
|
73
|
+
expect(textChannels).toHaveLength(2)
|
|
74
|
+
expect(textChannels[0].name).toBe('general')
|
|
75
|
+
expect(textChannels[1].name).toBe('announcements')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('list: includes channel metadata', async () => {
|
|
79
|
+
// given: discord client with channels
|
|
80
|
+
const client = new DiscordClient('test-token')
|
|
81
|
+
const channels = await client.listChannels('guild-1')
|
|
82
|
+
const textChannels = channels.filter((ch) => ch.type === 0)
|
|
83
|
+
|
|
84
|
+
// when: checking channel properties
|
|
85
|
+
const channel = textChannels[0]
|
|
86
|
+
|
|
87
|
+
// then: channel has id, name, type, topic
|
|
88
|
+
expect(channel.id).toBeDefined()
|
|
89
|
+
expect(channel.name).toBeDefined()
|
|
90
|
+
expect(channel.type).toBe(0)
|
|
91
|
+
expect(channel.topic).toBeDefined()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('info: returns channel details', async () => {
|
|
95
|
+
// given: discord client with channel data
|
|
96
|
+
const client = new DiscordClient('test-token')
|
|
97
|
+
const channel = await client.getChannel('ch-1')
|
|
98
|
+
|
|
99
|
+
// when: getting channel info
|
|
100
|
+
expect(channel).toBeDefined()
|
|
101
|
+
|
|
102
|
+
// then: channel details are returned
|
|
103
|
+
expect(channel.id).toBe('ch-1')
|
|
104
|
+
expect(channel.name).toBe('general')
|
|
105
|
+
expect(channel.type).toBe(0)
|
|
106
|
+
expect(channel.topic).toBe('General discussion')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('info: throws error for non-existent channel', async () => {
|
|
110
|
+
// given: discord client
|
|
111
|
+
const client = new DiscordClient('test-token')
|
|
112
|
+
|
|
113
|
+
// when: getting non-existent channel
|
|
114
|
+
// then: error is thrown
|
|
115
|
+
try {
|
|
116
|
+
await client.getChannel('non-existent')
|
|
117
|
+
expect(true).toBe(false) // should not reach here
|
|
118
|
+
} catch (error) {
|
|
119
|
+
expect((error as Error).message).toContain('Channel not found')
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('history: returns messages in reverse chronological order', async () => {
|
|
124
|
+
// given: discord client with messages
|
|
125
|
+
const client = new DiscordClient('test-token')
|
|
126
|
+
const messages = await client.getMessages('ch-1', 50)
|
|
127
|
+
|
|
128
|
+
// when: getting message history
|
|
129
|
+
expect(messages).toBeDefined()
|
|
130
|
+
|
|
131
|
+
// then: messages are returned (Discord API returns newest first)
|
|
132
|
+
expect(messages).toHaveLength(2)
|
|
133
|
+
expect(messages[0].id).toBe('msg-1')
|
|
134
|
+
expect(messages[0].author.username).toBe('alice')
|
|
135
|
+
expect(messages[1].id).toBe('msg-2')
|
|
136
|
+
expect(messages[1].author.username).toBe('bob')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('history: includes message metadata', async () => {
|
|
140
|
+
// given: discord client with messages
|
|
141
|
+
const client = new DiscordClient('test-token')
|
|
142
|
+
const messages = await client.getMessages('ch-1', 50)
|
|
143
|
+
|
|
144
|
+
// when: checking message properties
|
|
145
|
+
const message = messages[0]
|
|
146
|
+
|
|
147
|
+
// then: message has id, content, author, timestamp
|
|
148
|
+
expect(message.id).toBeDefined()
|
|
149
|
+
expect(message.content).toBeDefined()
|
|
150
|
+
expect(message.author).toBeDefined()
|
|
151
|
+
expect(message.author.username).toBeDefined()
|
|
152
|
+
expect(message.timestamp).toBeDefined()
|
|
153
|
+
})
|