agent-messenger 2.10.1 → 2.11.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-plugin/plugin.json +1 -1
- package/.env.template +4 -1
- package/README.md +77 -27
- package/bun.lock +26 -0
- package/dist/package.json +14 -1
- package/dist/src/platforms/channeltalk/commands/auth.d.ts +2 -1
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +5 -3
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +2 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +22 -6
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.js +11 -1
- package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/auth.js +1 -5
- package/dist/src/platforms/channeltalkbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/bot.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/bot.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/bot.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/chat.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/chat.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/chat.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/group.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/group.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/group.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/manager.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/manager.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/manager.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/message.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/message.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/whoami.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/credential-manager.d.ts +5 -0
- package/dist/src/platforms/channeltalkbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/credential-manager.js +34 -4
- package/dist/src/platforms/channeltalkbot/credential-manager.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts +1 -0
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +3 -1
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/listener.d.ts +2 -0
- package/dist/src/platforms/discord/listener.d.ts.map +1 -1
- package/dist/src/platforms/discord/listener.js +51 -21
- package/dist/src/platforms/discord/listener.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +2 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +21 -6
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +12 -1
- package/dist/src/platforms/discordbot/cli.js.map +1 -1
- package/dist/src/platforms/discordbot/client.d.ts +3 -0
- package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/client.js +3 -0
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/auth.js +1 -5
- package/dist/src/platforms/discordbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.js +1 -6
- package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/server.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/server.js +1 -4
- package/dist/src/platforms/discordbot/commands/server.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/whoami.js +1 -6
- package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/discordbot/index.d.ts +3 -1
- package/dist/src/platforms/discordbot/index.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/index.js +2 -1
- package/dist/src/platforms/discordbot/index.js.map +1 -1
- package/dist/src/platforms/discordbot/listener.d.ts +43 -0
- package/dist/src/platforms/discordbot/listener.d.ts.map +1 -0
- package/dist/src/platforms/discordbot/listener.js +292 -0
- package/dist/src/platforms/discordbot/listener.js.map +1 -0
- package/dist/src/platforms/discordbot/types.d.ts +161 -0
- package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/types.js +34 -0
- package/dist/src/platforms/discordbot/types.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +3 -1
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +2 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.js +11 -2
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +4 -2
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +4 -1
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +64 -15
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/cli.js +15 -3
- package/dist/src/platforms/slackbot/cli.js.map +1 -1
- package/dist/src/platforms/slackbot/client.d.ts +22 -1
- package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/client.js +104 -1
- package/dist/src/platforms/slackbot/client.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/auth.js +1 -5
- package/dist/src/platforms/slackbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/file.d.ts +3 -0
- package/dist/src/platforms/slackbot/commands/file.d.ts.map +1 -0
- package/dist/src/platforms/slackbot/commands/file.js +164 -0
- package/dist/src/platforms/slackbot/commands/file.js.map +1 -0
- package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/index.js +1 -0
- package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/message.js +19 -0
- package/dist/src/platforms/slackbot/commands/message.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/whoami.js +1 -6
- package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/slackbot/credential-manager.d.ts +1 -0
- package/dist/src/platforms/slackbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/credential-manager.js +30 -2
- package/dist/src/platforms/slackbot/credential-manager.js.map +1 -1
- package/dist/src/platforms/slackbot/index.d.ts +4 -1
- package/dist/src/platforms/slackbot/index.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/index.js +1 -0
- package/dist/src/platforms/slackbot/index.js.map +1 -1
- package/dist/src/platforms/slackbot/listener.d.ts +44 -0
- package/dist/src/platforms/slackbot/listener.d.ts.map +1 -0
- package/dist/src/platforms/slackbot/listener.js +313 -0
- package/dist/src/platforms/slackbot/listener.js.map +1 -0
- package/dist/src/platforms/slackbot/types.d.ts +196 -1
- package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/types.js +4 -1
- package/dist/src/platforms/slackbot/types.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +1 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +37 -6
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +31 -9
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +4 -1
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +80 -31
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts +1 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +3 -1
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +3 -1
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/webex/token-extractor.js +16 -2
- package/dist/src/platforms/webex/token-extractor.js.map +1 -1
- package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/cli.js +11 -1
- package/dist/src/platforms/wechatbot/cli.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/auth.js +1 -5
- package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/message.js +1 -6
- package/dist/src/platforms/wechatbot/commands/message.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/template.js +1 -6
- package/dist/src/platforms/wechatbot/commands/template.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/user.js +1 -6
- package/dist/src/platforms/wechatbot/commands/user.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/whoami.js +1 -6
- package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +11 -1
- package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/auth.js +1 -5
- package/dist/src/platforms/whatsappbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/message.js +1 -6
- package/dist/src/platforms/whatsappbot/commands/message.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/template.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/template.js +1 -6
- package/dist/src/platforms/whatsappbot/commands/template.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/whoami.js +1 -6
- package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -1
- package/dist/src/shared/chromium/browsers.d.ts +8 -0
- package/dist/src/shared/chromium/browsers.d.ts.map +1 -1
- package/dist/src/shared/chromium/browsers.js +58 -3
- package/dist/src/shared/chromium/browsers.js.map +1 -1
- package/dist/src/shared/chromium/cli-options.d.ts +5 -0
- package/dist/src/shared/chromium/cli-options.d.ts.map +1 -0
- package/dist/src/shared/chromium/cli-options.js +8 -0
- package/dist/src/shared/chromium/cli-options.js.map +1 -0
- package/dist/src/shared/chromium/decryptor.d.ts +6 -0
- package/dist/src/shared/chromium/decryptor.d.ts.map +1 -1
- package/dist/src/shared/chromium/decryptor.js +26 -6
- package/dist/src/shared/chromium/decryptor.js.map +1 -1
- package/dist/src/shared/chromium/index.d.ts +3 -1
- package/dist/src/shared/chromium/index.d.ts.map +1 -1
- package/dist/src/shared/chromium/index.js +2 -1
- package/dist/src/shared/chromium/index.js.map +1 -1
- package/dist/src/shared/utils/cli-output.d.ts +7 -0
- package/dist/src/shared/utils/cli-output.d.ts.map +1 -0
- package/dist/src/shared/utils/cli-output.js +7 -0
- package/dist/src/shared/utils/cli-output.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +73 -20
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/cli/channeltalk.mdx +4 -0
- package/docs/content/docs/cli/discord.mdx +5 -0
- package/docs/content/docs/cli/instagram.mdx +3 -0
- package/docs/content/docs/cli/slack.mdx +5 -0
- package/docs/content/docs/cli/slackbot.mdx +60 -22
- package/docs/content/docs/cli/teams.mdx +5 -0
- package/docs/content/docs/cli/webex.mdx +3 -0
- package/docs/content/docs/sdk/channeltalkbot.mdx +38 -1
- package/docs/content/docs/sdk/discordbot.mdx +501 -0
- package/docs/content/docs/sdk/meta.json +2 -0
- package/docs/content/docs/sdk/slackbot.mdx +576 -0
- package/e2e/README.md +1 -1
- package/e2e/channeltalk.e2e.test.ts +13 -13
- package/e2e/channeltalkbot.e2e.test.ts +13 -13
- package/e2e/config.ts +9 -4
- package/e2e/discord.e2e.test.ts +24 -24
- package/e2e/discordbot.e2e.test.ts +16 -16
- package/e2e/instagram.e2e.test.ts +10 -10
- package/e2e/kakaotalk.e2e.test.ts +7 -7
- package/e2e/line.e2e.test.ts +8 -8
- package/e2e/slack.e2e.test.ts +34 -34
- package/e2e/slackbot.e2e.test.ts +14 -14
- package/e2e/teams.e2e.test.ts +23 -23
- package/e2e/telegram.e2e.test.ts +8 -8
- package/e2e/webex.e2e.test.ts +14 -14
- package/e2e/whatsapp.e2e.test.ts +8 -8
- package/e2e/whatsappbot.e2e.test.ts +6 -6
- package/examples/discordbot-listen.ts +65 -0
- package/examples/slackbot-listen.ts +65 -0
- package/package.json +14 -1
- package/skills/agent-channeltalk/SKILL.md +5 -1
- package/skills/agent-channeltalk/references/authentication.md +5 -1
- package/skills/agent-channeltalkbot/SKILL.md +17 -3
- package/skills/agent-channeltalkbot/references/authentication.md +7 -5
- package/skills/agent-discord/SKILL.md +5 -1
- package/skills/agent-discord/references/authentication.md +7 -1
- package/skills/agent-discordbot/SKILL.md +13 -2
- package/skills/agent-discordbot/references/common-patterns.md +1 -1
- package/skills/agent-instagram/SKILL.md +7 -1
- package/skills/agent-instagram/references/authentication.md +6 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -1
- package/skills/agent-slack/references/authentication.md +7 -1
- package/skills/agent-slackbot/SKILL.md +56 -4
- package/skills/agent-slackbot/references/authentication.md +4 -0
- package/skills/agent-teams/SKILL.md +5 -1
- package/skills/agent-teams/references/authentication.md +7 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +7 -1
- package/skills/agent-webex/references/authentication.md +6 -0
- package/skills/agent-wechatbot/SKILL.md +16 -1
- package/skills/agent-wechatbot/references/authentication.md +219 -0
- package/skills/agent-wechatbot/references/common-patterns.md +358 -0
- package/skills/agent-wechatbot/templates/account-summary.sh +122 -0
- package/skills/agent-wechatbot/templates/post-message.sh +122 -0
- package/skills/agent-wechatbot/templates/send-template.sh +152 -0
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +30 -1
- package/src/platforms/channeltalk/client.test.ts +26 -26
- package/src/platforms/channeltalk/commands/auth.test.ts +31 -19
- package/src/platforms/channeltalk/commands/auth.ts +15 -5
- package/src/platforms/channeltalk/commands/bot.test.ts +2 -2
- package/src/platforms/channeltalk/commands/chat.test.ts +3 -3
- package/src/platforms/channeltalk/commands/group.test.ts +4 -4
- package/src/platforms/channeltalk/commands/manager.test.ts +2 -2
- package/src/platforms/channeltalk/commands/message.test.ts +17 -17
- package/src/platforms/channeltalk/commands/snapshot.test.ts +7 -7
- package/src/platforms/channeltalk/commands/whoami.test.ts +3 -3
- package/src/platforms/channeltalk/credential-manager.test.ts +18 -18
- package/src/platforms/channeltalk/ensure-auth.test.ts +5 -5
- package/src/platforms/channeltalk/index.test.ts +23 -23
- package/src/platforms/channeltalk/token-extractor.test.ts +21 -21
- package/src/platforms/channeltalk/token-extractor.ts +24 -5
- package/src/platforms/channeltalk/types.test.ts +12 -12
- package/src/platforms/channeltalkbot/cli.ts +9 -0
- package/src/platforms/channeltalkbot/client.test.ts +14 -14
- package/src/platforms/channeltalkbot/commands/auth.test.ts +16 -16
- package/src/platforms/channeltalkbot/commands/auth.ts +1 -5
- package/src/platforms/channeltalkbot/commands/bot.test.ts +6 -6
- package/src/platforms/channeltalkbot/commands/bot.ts +1 -6
- package/src/platforms/channeltalkbot/commands/chat.test.ts +9 -9
- package/src/platforms/channeltalkbot/commands/chat.ts +1 -6
- package/src/platforms/channeltalkbot/commands/group.test.ts +6 -6
- package/src/platforms/channeltalkbot/commands/group.ts +1 -6
- package/src/platforms/channeltalkbot/commands/manager.test.ts +3 -3
- package/src/platforms/channeltalkbot/commands/manager.ts +1 -6
- package/src/platforms/channeltalkbot/commands/message.test.ts +10 -10
- package/src/platforms/channeltalkbot/commands/message.ts +1 -6
- package/src/platforms/channeltalkbot/commands/snapshot.test.ts +7 -7
- package/src/platforms/channeltalkbot/commands/whoami.test.ts +6 -4
- package/src/platforms/channeltalkbot/commands/whoami.ts +1 -6
- package/src/platforms/channeltalkbot/credential-manager.test.ts +123 -29
- package/src/platforms/channeltalkbot/credential-manager.ts +37 -4
- package/src/platforms/channeltalkbot/index.test.ts +15 -15
- package/src/platforms/discord/client.test.ts +28 -28
- package/src/platforms/discord/commands/auth.test.ts +7 -7
- package/src/platforms/discord/commands/auth.ts +13 -2
- package/src/platforms/discord/commands/channel.test.ts +7 -7
- package/src/platforms/discord/commands/dm.test.ts +4 -4
- package/src/platforms/discord/commands/file.test.ts +4 -4
- package/src/platforms/discord/commands/friend.test.ts +6 -6
- package/src/platforms/discord/commands/member.test.ts +5 -5
- package/src/platforms/discord/commands/mention.test.ts +5 -5
- package/src/platforms/discord/commands/message.test.ts +9 -9
- package/src/platforms/discord/commands/note.test.ts +6 -6
- package/src/platforms/discord/commands/profile.test.ts +4 -4
- package/src/platforms/discord/commands/reaction.test.ts +5 -5
- package/src/platforms/discord/commands/server.test.ts +7 -7
- package/src/platforms/discord/commands/snapshot.test.ts +6 -6
- package/src/platforms/discord/commands/thread.test.ts +6 -6
- package/src/platforms/discord/commands/user.test.ts +5 -5
- package/src/platforms/discord/commands/whoami.test.ts +6 -6
- package/src/platforms/discord/credential-manager.test.ts +16 -16
- package/src/platforms/discord/ensure-auth.test.ts +8 -8
- package/src/platforms/discord/index.test.ts +17 -17
- package/src/platforms/discord/listener.test.ts +92 -34
- package/src/platforms/discord/listener.ts +43 -19
- package/src/platforms/discord/token-extractor.test.ts +53 -53
- package/src/platforms/discord/token-extractor.ts +30 -6
- package/src/platforms/discord/types.test.ts +26 -26
- package/src/platforms/discordbot/cli.ts +10 -0
- package/src/platforms/discordbot/client.test.ts +31 -31
- package/src/platforms/discordbot/client.ts +4 -0
- package/src/platforms/discordbot/commands/auth.test.ts +18 -18
- package/src/platforms/discordbot/commands/auth.ts +1 -5
- package/src/platforms/discordbot/commands/channel.test.ts +11 -11
- package/src/platforms/discordbot/commands/file.test.ts +7 -7
- package/src/platforms/discordbot/commands/message.test.ts +25 -25
- package/src/platforms/discordbot/commands/message.ts +1 -6
- package/src/platforms/discordbot/commands/reaction.test.ts +6 -6
- package/src/platforms/discordbot/commands/server.test.ts +12 -12
- package/src/platforms/discordbot/commands/server.ts +1 -5
- package/src/platforms/discordbot/commands/snapshot.test.ts +13 -13
- package/src/platforms/discordbot/commands/thread.test.ts +10 -10
- package/src/platforms/discordbot/commands/user.test.ts +9 -9
- package/src/platforms/discordbot/commands/whoami.test.ts +4 -4
- package/src/platforms/discordbot/commands/whoami.ts +1 -6
- package/src/platforms/discordbot/credential-manager.test.ts +28 -28
- package/src/platforms/discordbot/index.test.ts +82 -0
- package/src/platforms/discordbot/index.ts +27 -9
- package/src/platforms/discordbot/listener.test.ts +1002 -0
- package/src/platforms/discordbot/listener.ts +321 -0
- package/src/platforms/discordbot/types.ts +163 -0
- package/src/platforms/instagram/client.test.ts +18 -18
- package/src/platforms/instagram/commands/auth.test.ts +11 -11
- package/src/platforms/instagram/commands/auth.ts +9 -1
- package/src/platforms/instagram/commands/chat.test.ts +6 -6
- package/src/platforms/instagram/commands/message.test.ts +11 -11
- package/src/platforms/instagram/commands/shared.test.ts +12 -12
- package/src/platforms/instagram/commands/whoami.test.ts +3 -3
- package/src/platforms/instagram/credential-manager.test.ts +21 -21
- package/src/platforms/instagram/ensure-auth.test.ts +4 -4
- package/src/platforms/instagram/index.test.ts +9 -9
- package/src/platforms/instagram/listener.test.ts +8 -8
- package/src/platforms/instagram/token-extractor.test.ts +35 -35
- package/src/platforms/instagram/token-extractor.ts +13 -1
- package/src/platforms/kakaotalk/client.test.ts +33 -33
- package/src/platforms/kakaotalk/commands/auth.test.ts +11 -11
- package/src/platforms/kakaotalk/commands/chat.test.ts +6 -6
- package/src/platforms/kakaotalk/commands/message.test.ts +7 -7
- package/src/platforms/kakaotalk/commands/whoami.test.ts +5 -5
- package/src/platforms/kakaotalk/credential-manager.test.ts +15 -15
- package/src/platforms/kakaotalk/index.test.ts +15 -15
- package/src/platforms/kakaotalk/listener.test.ts +17 -17
- package/src/platforms/line/client.test.ts +17 -17
- package/src/platforms/line/commands/auth.test.ts +8 -8
- package/src/platforms/line/commands/chat.test.ts +7 -7
- package/src/platforms/line/commands/friend.test.ts +6 -6
- package/src/platforms/line/commands/message.test.ts +7 -7
- package/src/platforms/line/commands/whoami.test.ts +6 -6
- package/src/platforms/line/credential-manager.test.ts +17 -17
- package/src/platforms/line/index.test.ts +10 -10
- package/src/platforms/line/listener.test.ts +15 -15
- package/src/platforms/line/types.test.ts +14 -14
- package/src/platforms/slack/cli.test.ts +8 -8
- package/src/platforms/slack/client.test.ts +151 -151
- package/src/platforms/slack/commands/activity.test.ts +13 -13
- package/src/platforms/slack/commands/auth.test.ts +34 -34
- package/src/platforms/slack/commands/auth.ts +11 -2
- package/src/platforms/slack/commands/bookmark.test.ts +9 -9
- package/src/platforms/slack/commands/channel.test.ts +17 -17
- package/src/platforms/slack/commands/drafts.test.ts +7 -7
- package/src/platforms/slack/commands/emoji.test.ts +3 -3
- package/src/platforms/slack/commands/file.test.ts +12 -12
- package/src/platforms/slack/commands/message.test.ts +19 -19
- package/src/platforms/slack/commands/pin.test.ts +7 -7
- package/src/platforms/slack/commands/reaction.test.ts +10 -10
- package/src/platforms/slack/commands/reminder.test.ts +9 -9
- package/src/platforms/slack/commands/saved.test.ts +7 -7
- package/src/platforms/slack/commands/sections.test.ts +5 -5
- package/src/platforms/slack/commands/snapshot.test.ts +13 -13
- package/src/platforms/slack/commands/unread.test.ts +6 -6
- package/src/platforms/slack/commands/user.test.ts +10 -10
- package/src/platforms/slack/commands/usergroup.test.ts +15 -15
- package/src/platforms/slack/commands/whoami.test.ts +6 -6
- package/src/platforms/slack/commands/workspace.test.ts +26 -26
- package/src/platforms/slack/credential-manager.test.ts +14 -14
- package/src/platforms/slack/ensure-auth.test.ts +21 -21
- package/src/platforms/slack/index.test.ts +12 -12
- package/src/platforms/slack/listener.test.ts +17 -17
- package/src/platforms/slack/token-extractor-node.test.ts +2 -2
- package/src/platforms/slack/token-extractor.test.ts +133 -37
- package/src/platforms/slack/token-extractor.ts +76 -13
- package/src/platforms/slack/types.test.ts +21 -21
- package/src/platforms/slackbot/cli.ts +13 -1
- package/src/platforms/slackbot/client.test.ts +296 -22
- package/src/platforms/slackbot/client.ts +130 -2
- package/src/platforms/slackbot/commands/auth.test.ts +14 -14
- package/src/platforms/slackbot/commands/auth.ts +1 -5
- package/src/platforms/slackbot/commands/channel.test.ts +7 -7
- package/src/platforms/slackbot/commands/file.test.ts +201 -0
- package/src/platforms/slackbot/commands/file.ts +212 -0
- package/src/platforms/slackbot/commands/index.ts +1 -0
- package/src/platforms/slackbot/commands/message.test.ts +13 -13
- package/src/platforms/slackbot/commands/message.ts +22 -0
- package/src/platforms/slackbot/commands/reaction.test.ts +6 -6
- package/src/platforms/slackbot/commands/user.test.ts +7 -7
- package/src/platforms/slackbot/commands/whoami.test.ts +4 -4
- package/src/platforms/slackbot/commands/whoami.ts +1 -6
- package/src/platforms/slackbot/credential-manager.test.ts +83 -23
- package/src/platforms/slackbot/credential-manager.ts +32 -2
- package/src/platforms/slackbot/index.test.ts +59 -0
- package/src/platforms/slackbot/index.ts +31 -7
- package/src/platforms/slackbot/listener.test.ts +1012 -0
- package/src/platforms/slackbot/listener.ts +362 -0
- package/src/platforms/slackbot/types.test.ts +7 -7
- package/src/platforms/slackbot/types.ts +224 -1
- package/src/platforms/teams/client.test.ts +30 -30
- package/src/platforms/teams/commands/auth.test.ts +9 -9
- package/src/platforms/teams/commands/auth.ts +66 -7
- package/src/platforms/teams/commands/channel.test.ts +7 -7
- package/src/platforms/teams/commands/file.test.ts +4 -4
- package/src/platforms/teams/commands/message.test.ts +5 -5
- package/src/platforms/teams/commands/reaction.test.ts +4 -4
- package/src/platforms/teams/commands/snapshot.test.ts +7 -7
- package/src/platforms/teams/commands/team.test.ts +8 -8
- package/src/platforms/teams/commands/user.test.ts +4 -4
- package/src/platforms/teams/commands/whoami.test.ts +6 -6
- package/src/platforms/teams/credential-manager.test.ts +17 -17
- package/src/platforms/teams/ensure-auth.test.ts +69 -18
- package/src/platforms/teams/ensure-auth.ts +39 -11
- package/src/platforms/teams/index.test.ts +15 -15
- package/src/platforms/teams/token-extractor.test.ts +251 -69
- package/src/platforms/teams/token-extractor.ts +94 -31
- package/src/platforms/teams/types.test.ts +26 -26
- package/src/platforms/telegram/app-config.test.ts +4 -4
- package/src/platforms/telegram/chat-utils.test.ts +12 -12
- package/src/platforms/telegram/client.test.ts +4 -4
- package/src/platforms/telegram/commands/auth.test.ts +16 -16
- package/src/platforms/telegram/commands/chat.test.ts +9 -9
- package/src/platforms/telegram/commands/message.test.ts +6 -6
- package/src/platforms/telegram/commands/shared.test.ts +3 -3
- package/src/platforms/telegram/commands/whoami.test.ts +3 -3
- package/src/platforms/telegram/credential-manager.test.ts +10 -10
- package/src/platforms/telegram/types.test.ts +6 -6
- package/src/platforms/webex/app-config.test.ts +8 -8
- package/src/platforms/webex/cli.test.ts +5 -5
- package/src/platforms/webex/client.test.ts +65 -65
- package/src/platforms/webex/commands/auth.test.ts +18 -18
- package/src/platforms/webex/commands/auth.ts +13 -2
- package/src/platforms/webex/commands/member.test.ts +5 -5
- package/src/platforms/webex/commands/message.test.ts +12 -12
- package/src/platforms/webex/commands/snapshot.test.ts +5 -5
- package/src/platforms/webex/commands/space.test.ts +10 -10
- package/src/platforms/webex/commands/whoami.test.ts +6 -6
- package/src/platforms/webex/credential-manager.test.ts +22 -22
- package/src/platforms/webex/encryption.test.ts +4 -4
- package/src/platforms/webex/ensure-auth.test.ts +5 -5
- package/src/platforms/webex/index.test.ts +5 -5
- package/src/platforms/webex/markdown-to-html.test.ts +33 -33
- package/src/platforms/webex/token-extractor.test.ts +23 -23
- package/src/platforms/webex/token-extractor.ts +25 -3
- package/src/platforms/webex/types.test.ts +27 -27
- package/src/platforms/wechatbot/cli.ts +9 -0
- package/src/platforms/wechatbot/client.test.ts +27 -27
- package/src/platforms/wechatbot/commands/auth.test.ts +15 -15
- package/src/platforms/wechatbot/commands/auth.ts +1 -5
- package/src/platforms/wechatbot/commands/message.test.ts +8 -8
- package/src/platforms/wechatbot/commands/message.ts +1 -6
- package/src/platforms/wechatbot/commands/template.test.ts +9 -9
- package/src/platforms/wechatbot/commands/template.ts +1 -6
- package/src/platforms/wechatbot/commands/user.test.ts +7 -7
- package/src/platforms/wechatbot/commands/user.ts +1 -6
- package/src/platforms/wechatbot/commands/whoami.test.ts +5 -5
- package/src/platforms/wechatbot/commands/whoami.ts +1 -6
- package/src/platforms/wechatbot/credential-manager.test.ts +18 -18
- package/src/platforms/wechatbot/index.test.ts +10 -10
- package/src/platforms/wechatbot/types.test.ts +25 -25
- package/src/platforms/whatsapp/commands/auth.test.ts +13 -13
- package/src/platforms/whatsapp/commands/chat.test.ts +8 -8
- package/src/platforms/whatsapp/commands/message.test.ts +10 -10
- package/src/platforms/whatsapp/commands/whoami.test.ts +3 -3
- package/src/platforms/whatsapp/credential-manager.test.ts +23 -23
- package/src/platforms/whatsapp/ensure-auth.test.ts +4 -4
- package/src/platforms/whatsapp/index.test.ts +8 -8
- package/src/platforms/whatsapp/types.test.ts +42 -42
- package/src/platforms/whatsappbot/cli.ts +9 -0
- package/src/platforms/whatsappbot/client.test.ts +27 -27
- package/src/platforms/whatsappbot/commands/auth.test.ts +14 -14
- package/src/platforms/whatsappbot/commands/auth.ts +1 -5
- package/src/platforms/whatsappbot/commands/message.test.ts +16 -16
- package/src/platforms/whatsappbot/commands/message.ts +1 -6
- package/src/platforms/whatsappbot/commands/template.test.ts +9 -9
- package/src/platforms/whatsappbot/commands/template.ts +1 -6
- package/src/platforms/whatsappbot/commands/whoami.test.ts +5 -5
- package/src/platforms/whatsappbot/commands/whoami.ts +1 -6
- package/src/platforms/whatsappbot/credential-manager.test.ts +18 -18
- package/src/platforms/whatsappbot/index.test.ts +7 -7
- package/src/platforms/whatsappbot/types.test.ts +18 -18
- package/src/shared/chromium/browsers.test.ts +102 -22
- package/src/shared/chromium/browsers.ts +72 -3
- package/src/shared/chromium/cli-options.test.ts +22 -0
- package/src/shared/chromium/cli-options.ts +12 -0
- package/src/shared/chromium/cookie-reader.test.ts +13 -13
- package/src/shared/chromium/decryptor.test.ts +97 -32
- package/src/shared/chromium/decryptor.ts +27 -6
- package/src/shared/chromium/index.ts +3 -0
- package/src/shared/utils/cli-output.test.ts +57 -0
- package/src/shared/utils/cli-output.ts +8 -0
- package/src/shared/utils/concurrency.test.ts +6 -6
- package/src/shared/utils/derived-key-cache.test.ts +11 -11
- package/src/tui/app.ts +129 -20
- package/src/tui/utils.test.ts +31 -31
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite'
|
|
2
|
-
import { afterEach, describe, expect, spyOn,
|
|
2
|
+
import { afterEach, describe, expect, spyOn, it } from 'bun:test'
|
|
3
3
|
import { createCipheriv, randomBytes } from 'node:crypto'
|
|
4
4
|
import * as fs from 'node:fs'
|
|
5
5
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
6
6
|
import { tmpdir } from 'node:os'
|
|
7
7
|
import { join } from 'node:path'
|
|
8
8
|
|
|
9
|
+
import { ChromiumCookieDecryptor } from '@/shared/chromium'
|
|
10
|
+
|
|
9
11
|
import { ExtractedWorkspace, TokenExtractor } from './token-extractor'
|
|
10
12
|
|
|
11
13
|
const tempDirs: string[] = []
|
|
14
|
+
const originalAgentBrowserProfile = process.env.AGENT_BROWSER_PROFILE
|
|
12
15
|
|
|
13
16
|
afterEach(() => {
|
|
17
|
+
if (originalAgentBrowserProfile) {
|
|
18
|
+
process.env.AGENT_BROWSER_PROFILE = originalAgentBrowserProfile
|
|
19
|
+
} else {
|
|
20
|
+
delete process.env.AGENT_BROWSER_PROFILE
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
for (const dir of tempDirs) {
|
|
15
24
|
rmSync(dir, { recursive: true, force: true })
|
|
16
25
|
}
|
|
@@ -33,7 +42,7 @@ function createCookiesDb(
|
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
describe('TokenExtractor token deduplication', () => {
|
|
36
|
-
|
|
45
|
+
it('keeps first token per team and upgrades unknown team name', async () => {
|
|
37
46
|
// given — two .log entries for the same team: first has unknown name, second has a name
|
|
38
47
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-dedup-'))
|
|
39
48
|
tempDirs.push(slackDir)
|
|
@@ -59,7 +68,7 @@ describe('TokenExtractor token deduplication', () => {
|
|
|
59
68
|
expect(result[0].workspace_name).toBe('workspace-name')
|
|
60
69
|
})
|
|
61
70
|
|
|
62
|
-
|
|
71
|
+
it('prefers Local Storage token over IndexedDB token for same team', async () => {
|
|
63
72
|
// given — same team in Local Storage (valid) and IndexedDB (stale)
|
|
64
73
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-dedup-tier-'))
|
|
65
74
|
tempDirs.push(slackDir)
|
|
@@ -87,7 +96,7 @@ describe('TokenExtractor token deduplication', () => {
|
|
|
87
96
|
expect(result[0].workspace_name).toBe('valid-workspace')
|
|
88
97
|
})
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
it('prefers IndexedDB token when Local Storage has no token for team', async () => {
|
|
91
100
|
// given — token only in IndexedDB
|
|
92
101
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-dedup-idb-only-'))
|
|
93
102
|
tempDirs.push(slackDir)
|
|
@@ -108,7 +117,7 @@ describe('TokenExtractor token deduplication', () => {
|
|
|
108
117
|
expect(result[0].token).toBe(token)
|
|
109
118
|
})
|
|
110
119
|
|
|
111
|
-
|
|
120
|
+
it('prefers storage dir token over IndexedDB token for same team', async () => {
|
|
112
121
|
// given — structured JSON in storage dir vs raw token in IndexedDB
|
|
113
122
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-dedup-storage-'))
|
|
114
123
|
tempDirs.push(slackDir)
|
|
@@ -136,7 +145,7 @@ describe('TokenExtractor token deduplication', () => {
|
|
|
136
145
|
expect(result[0].workspace_name).toBe('storage-workspace')
|
|
137
146
|
})
|
|
138
147
|
|
|
139
|
-
|
|
148
|
+
it('prefers .log tokens over .ldb tokens for same team', async () => {
|
|
140
149
|
// given — same team ID in both .log (fresh) and .ldb (stale)
|
|
141
150
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-dedup-order-'))
|
|
142
151
|
tempDirs.push(slackDir)
|
|
@@ -160,7 +169,7 @@ describe('TokenExtractor token deduplication', () => {
|
|
|
160
169
|
expect(result[0].token).toBe(freshToken)
|
|
161
170
|
})
|
|
162
171
|
|
|
163
|
-
|
|
172
|
+
it('keeps all tokens with unknown teamId instead of merging them', async () => {
|
|
164
173
|
// given — two different tokens without team ID context (no T[A-Z0-9] nearby)
|
|
165
174
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-dedup-unknown-'))
|
|
166
175
|
tempDirs.push(slackDir)
|
|
@@ -203,7 +212,7 @@ describe('TokenExtractor LevelDB fragmentation markers', () => {
|
|
|
203
212
|
return Buffer.concat([prefix, segments[0] ? Buffer.concat(segments) : Buffer.alloc(0), suffix])
|
|
204
213
|
}
|
|
205
214
|
|
|
206
|
-
|
|
215
|
+
it('extracts token with old fragmentation marker [19 0d f0 NN]', async () => {
|
|
207
216
|
// given
|
|
208
217
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-marker-old-'))
|
|
209
218
|
tempDirs.push(slackDir)
|
|
@@ -225,7 +234,7 @@ describe('TokenExtractor LevelDB fragmentation markers', () => {
|
|
|
225
234
|
expect(result[0].token).toBe(`xoxc-1111111111-2222222222-3333333333-${hex64}`)
|
|
226
235
|
})
|
|
227
236
|
|
|
228
|
-
|
|
237
|
+
it('extracts token with new fragmentation marker [15 0b f0 43]', async () => {
|
|
229
238
|
// given — marker whose 4th byte (0x43 = "C") is a valid hex char
|
|
230
239
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-marker-new-'))
|
|
231
240
|
tempDirs.push(slackDir)
|
|
@@ -248,7 +257,7 @@ describe('TokenExtractor LevelDB fragmentation markers', () => {
|
|
|
248
257
|
expect(result[0].token).not.toContain('C')
|
|
249
258
|
})
|
|
250
259
|
|
|
251
|
-
|
|
260
|
+
it('extracts token with new fragmentation marker [15 0b f0 58]', async () => {
|
|
252
261
|
// given — marker whose 4th byte (0x58 = "X") is not a valid hex char
|
|
253
262
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-marker-58-'))
|
|
254
263
|
tempDirs.push(slackDir)
|
|
@@ -272,7 +281,7 @@ describe('TokenExtractor LevelDB fragmentation markers', () => {
|
|
|
272
281
|
})
|
|
273
282
|
|
|
274
283
|
describe('TokenExtractor Linux cookie decryption', () => {
|
|
275
|
-
|
|
284
|
+
it('decrypts v10 cookie using peanuts password on Linux', async () => {
|
|
276
285
|
// given — LevelDB with valid token + v10-encrypted cookie using Linux key
|
|
277
286
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-linux-'))
|
|
278
287
|
tempDirs.push(slackDir)
|
|
@@ -311,7 +320,7 @@ describe('TokenExtractor Linux cookie decryption', () => {
|
|
|
311
320
|
})
|
|
312
321
|
|
|
313
322
|
describe('TokenExtractor Linux v11 cookie decryption', () => {
|
|
314
|
-
|
|
323
|
+
it('decrypts v11 cookie using gnome-keyring password on Linux', () => {
|
|
315
324
|
// given — v11-prefixed cookie encrypted with a known keyring password
|
|
316
325
|
const { createCipheriv, pbkdf2Sync } = require('node:crypto')
|
|
317
326
|
const testPassword = 'test-gnome-keyring-password'
|
|
@@ -338,7 +347,7 @@ describe('TokenExtractor Linux v11 cookie decryption', () => {
|
|
|
338
347
|
keyringPasswordSpy.mockRestore()
|
|
339
348
|
})
|
|
340
349
|
|
|
341
|
-
|
|
350
|
+
it('falls back to peanuts key when keyring is unavailable for v11 cookie', () => {
|
|
342
351
|
// given — v11-prefixed cookie encrypted with peanuts (tests fallback code path)
|
|
343
352
|
const { createCipheriv, pbkdf2Sync } = require('node:crypto')
|
|
344
353
|
const key = pbkdf2Sync('peanuts', 'saltysalt', 1, 16, 'sha1')
|
|
@@ -363,7 +372,7 @@ describe('TokenExtractor Linux v11 cookie decryption', () => {
|
|
|
363
372
|
})
|
|
364
373
|
|
|
365
374
|
describe('TokenExtractor debug logging', () => {
|
|
366
|
-
|
|
375
|
+
it('calls debugLog callback during extraction', async () => {
|
|
367
376
|
// given
|
|
368
377
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-debug-'))
|
|
369
378
|
tempDirs.push(slackDir)
|
|
@@ -380,7 +389,7 @@ describe('TokenExtractor debug logging', () => {
|
|
|
380
389
|
expect(messages.length).toBeGreaterThan(0)
|
|
381
390
|
})
|
|
382
391
|
|
|
383
|
-
|
|
392
|
+
it('does not throw when debugLog is not provided', async () => {
|
|
384
393
|
// given
|
|
385
394
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-no-debug-'))
|
|
386
395
|
tempDirs.push(slackDir)
|
|
@@ -394,12 +403,12 @@ describe('TokenExtractor debug logging', () => {
|
|
|
394
403
|
})
|
|
395
404
|
|
|
396
405
|
describe('TokenExtractor Windows DPAPI', () => {
|
|
397
|
-
|
|
406
|
+
it('decryptDPAPI returns null on non-win32 platform', () => {
|
|
398
407
|
const extractor = new TokenExtractor('darwin', '/tmp/slack-test')
|
|
399
408
|
expect(extractor.decryptDPAPI(Buffer.from('test'))).toBeNull()
|
|
400
409
|
})
|
|
401
410
|
|
|
402
|
-
|
|
411
|
+
it('decryptV10CookieWindows decrypts AES-256-GCM with master key from Local State', () => {
|
|
403
412
|
// given — known master key and AES-256-GCM encrypted cookie
|
|
404
413
|
const masterKey = randomBytes(32)
|
|
405
414
|
|
|
@@ -423,7 +432,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
423
432
|
expect(extractor.tryDecryptCookie(encrypted)).toBe(plaintext)
|
|
424
433
|
})
|
|
425
434
|
|
|
426
|
-
|
|
435
|
+
it('decryptV10CookieWindows falls back to direct DPAPI when no Local State', () => {
|
|
427
436
|
class TestTokenExtractor extends TokenExtractor {
|
|
428
437
|
override getWindowsMasterKey(): null {
|
|
429
438
|
return null
|
|
@@ -439,7 +448,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
439
448
|
expect(extractor.tryDecryptCookie(encrypted)).toBe('xoxd-dpapiDirectCookie%2B')
|
|
440
449
|
})
|
|
441
450
|
|
|
442
|
-
|
|
451
|
+
it('tryDecryptCookie handles Windows pre-v80 cookies without version prefix', () => {
|
|
443
452
|
class TestTokenExtractor extends TokenExtractor {
|
|
444
453
|
override decryptDPAPI(_encrypted: Buffer): Buffer | null {
|
|
445
454
|
return Buffer.from('xoxd-preV80Cookie%2B')
|
|
@@ -452,7 +461,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
452
461
|
expect(extractor.tryDecryptCookie(encrypted)).toBe('xoxd-preV80Cookie%2B')
|
|
453
462
|
})
|
|
454
463
|
|
|
455
|
-
|
|
464
|
+
it('getWindowsMasterKey reads and decrypts key from Local State file', () => {
|
|
456
465
|
// given
|
|
457
466
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-win-'))
|
|
458
467
|
tempDirs.push(slackDir)
|
|
@@ -477,7 +486,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
477
486
|
expect(extractor.getWindowsMasterKey()).toEqual(fakeDecryptedKey)
|
|
478
487
|
})
|
|
479
488
|
|
|
480
|
-
|
|
489
|
+
it('getWindowsMasterKey returns null when Local State is missing', () => {
|
|
481
490
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-no-ls-'))
|
|
482
491
|
tempDirs.push(slackDir)
|
|
483
492
|
|
|
@@ -485,7 +494,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
485
494
|
expect(extractor.getWindowsMasterKey()).toBeNull()
|
|
486
495
|
})
|
|
487
496
|
|
|
488
|
-
|
|
497
|
+
it('getWindowsMasterKey returns null when encrypted_key has no DPAPI prefix', () => {
|
|
489
498
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-bad-ls-'))
|
|
490
499
|
tempDirs.push(slackDir)
|
|
491
500
|
|
|
@@ -496,7 +505,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
496
505
|
expect(extractor.getWindowsMasterKey()).toBeNull()
|
|
497
506
|
})
|
|
498
507
|
|
|
499
|
-
|
|
508
|
+
it('extract throws descriptive error when cookie file is locked (EBUSY)', async () => {
|
|
500
509
|
// given — LevelDB with a valid token but Cookies file locked by Slack
|
|
501
510
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-ebusy-'))
|
|
502
511
|
tempDirs.push(slackDir)
|
|
@@ -523,7 +532,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
523
532
|
}
|
|
524
533
|
})
|
|
525
534
|
|
|
526
|
-
|
|
535
|
+
it('extract decrypts Windows v10 cookies end-to-end with mocked DPAPI', async () => {
|
|
527
536
|
// given — SQLite DB with v10-encrypted cookie, Local State with master key, LevelDB with token
|
|
528
537
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-win-e2e-'))
|
|
529
538
|
tempDirs.push(slackDir)
|
|
@@ -582,7 +591,7 @@ describe('TokenExtractor Windows DPAPI', () => {
|
|
|
582
591
|
})
|
|
583
592
|
|
|
584
593
|
describe('TokenExtractor IndexedDB blob files', () => {
|
|
585
|
-
|
|
594
|
+
it('extracts token from blob file when not in LevelDB', async () => {
|
|
586
595
|
// given — token only exists in an IndexedDB blob file, not in LevelDB
|
|
587
596
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-blob-'))
|
|
588
597
|
tempDirs.push(slackDir)
|
|
@@ -604,7 +613,7 @@ describe('TokenExtractor IndexedDB blob files', () => {
|
|
|
604
613
|
expect(result[0].workspace_id).toBe('T12345678')
|
|
605
614
|
})
|
|
606
615
|
|
|
607
|
-
|
|
616
|
+
it('extracts tokens from both LevelDB and blob files for different teams', async () => {
|
|
608
617
|
// given — one workspace token in LevelDB, another in blob file
|
|
609
618
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-blob-multi-'))
|
|
610
619
|
tempDirs.push(slackDir)
|
|
@@ -632,7 +641,7 @@ describe('TokenExtractor IndexedDB blob files', () => {
|
|
|
632
641
|
expect(teamIds).toEqual(['TAAAAAAAA', 'TBBBBBBBBB'])
|
|
633
642
|
})
|
|
634
643
|
|
|
635
|
-
|
|
644
|
+
it('LevelDB token wins over blob file token for same team', async () => {
|
|
636
645
|
// given — same team in both LevelDB (.log = highest raw priority) and blob file
|
|
637
646
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-blob-dedup-'))
|
|
638
647
|
tempDirs.push(slackDir)
|
|
@@ -659,7 +668,7 @@ describe('TokenExtractor IndexedDB blob files', () => {
|
|
|
659
668
|
expect(result[0].token).toBe(ldbToken)
|
|
660
669
|
})
|
|
661
670
|
|
|
662
|
-
|
|
671
|
+
it('merges same token from LDB and blob with different teamIds', async () => {
|
|
663
672
|
// given — same token in LevelDB (correct teamId) and blob file (false-positive teamId from binary)
|
|
664
673
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-blob-dup-'))
|
|
665
674
|
tempDirs.push(slackDir)
|
|
@@ -685,7 +694,7 @@ describe('TokenExtractor IndexedDB blob files', () => {
|
|
|
685
694
|
expect(result[0].token).toBe(token)
|
|
686
695
|
})
|
|
687
696
|
|
|
688
|
-
|
|
697
|
+
it('skips blob files larger than 10MB', async () => {
|
|
689
698
|
// given — blob file exceeds size limit
|
|
690
699
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-blob-large-'))
|
|
691
700
|
tempDirs.push(slackDir)
|
|
@@ -710,7 +719,7 @@ describe('TokenExtractor IndexedDB blob files', () => {
|
|
|
710
719
|
})
|
|
711
720
|
|
|
712
721
|
describe('TokenExtractor getWorkspaceDomains', () => {
|
|
713
|
-
|
|
722
|
+
it('reads workspace domains from root-state.json', () => {
|
|
714
723
|
// given
|
|
715
724
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-domains-'))
|
|
716
725
|
tempDirs.push(slackDir)
|
|
@@ -735,7 +744,7 @@ describe('TokenExtractor getWorkspaceDomains', () => {
|
|
|
735
744
|
expect(domains).toEqual({ T111: 'acme-corp', T222: 'devteam' })
|
|
736
745
|
})
|
|
737
746
|
|
|
738
|
-
|
|
747
|
+
it('returns empty when root-state.json is missing', () => {
|
|
739
748
|
// given
|
|
740
749
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-no-rootstate-'))
|
|
741
750
|
tempDirs.push(slackDir)
|
|
@@ -748,7 +757,7 @@ describe('TokenExtractor getWorkspaceDomains', () => {
|
|
|
748
757
|
expect(domains).toEqual({})
|
|
749
758
|
})
|
|
750
759
|
|
|
751
|
-
|
|
760
|
+
it('returns empty when root-state.json has no workspaces', () => {
|
|
752
761
|
// given
|
|
753
762
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-empty-rootstate-'))
|
|
754
763
|
tempDirs.push(slackDir)
|
|
@@ -765,7 +774,7 @@ describe('TokenExtractor getWorkspaceDomains', () => {
|
|
|
765
774
|
expect(domains).toEqual({})
|
|
766
775
|
})
|
|
767
776
|
|
|
768
|
-
|
|
777
|
+
it('skips workspaces without domain field', () => {
|
|
769
778
|
// given
|
|
770
779
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-partial-rootstate-'))
|
|
771
780
|
tempDirs.push(slackDir)
|
|
@@ -792,7 +801,7 @@ describe('TokenExtractor getWorkspaceDomains', () => {
|
|
|
792
801
|
})
|
|
793
802
|
|
|
794
803
|
describe('TokenExtractor browser fallback', () => {
|
|
795
|
-
|
|
804
|
+
it('extractFromBrowsers returns empty array when no browser profiles have tokens', async () => {
|
|
796
805
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-nonexistent-'))
|
|
797
806
|
tempDirs.push(slackDir)
|
|
798
807
|
rmSync(slackDir, { recursive: true, force: true })
|
|
@@ -802,7 +811,53 @@ describe('TokenExtractor browser fallback', () => {
|
|
|
802
811
|
expect(result).toEqual([])
|
|
803
812
|
})
|
|
804
813
|
|
|
805
|
-
|
|
814
|
+
it('resolves Local State from agent-browser profile root for encrypted cookies', async () => {
|
|
815
|
+
// given
|
|
816
|
+
const agentBrowserProfile = mkdtempSync(join(tmpdir(), 'agent-browser-slack-profile-'))
|
|
817
|
+
tempDirs.push(agentBrowserProfile)
|
|
818
|
+
process.env.AGENT_BROWSER_PROFILE = agentBrowserProfile
|
|
819
|
+
|
|
820
|
+
const hex64 = 'c'.repeat(64)
|
|
821
|
+
const token = `xoxc-1111111111-2222222222-3333333333-${hex64}`
|
|
822
|
+
const profileDir = join(agentBrowserProfile, 'Default')
|
|
823
|
+
const leveldbDir = join(profileDir, 'Local Storage', 'leveldb')
|
|
824
|
+
const networkDir = join(profileDir, 'Network')
|
|
825
|
+
mkdirSync(leveldbDir, { recursive: true })
|
|
826
|
+
mkdirSync(networkDir, { recursive: true })
|
|
827
|
+
writeFileSync(join(leveldbDir, '000001.log'), `"${token}"T12345678"name":"agent-browser-workspace"`)
|
|
828
|
+
writeFileSync(join(agentBrowserProfile, 'Local State'), '{}')
|
|
829
|
+
createCookiesDb(join(networkDir, 'Cookies'), [
|
|
830
|
+
{
|
|
831
|
+
name: 'd',
|
|
832
|
+
value: '',
|
|
833
|
+
encrypted_value: new Uint8Array([1, 2, 3]),
|
|
834
|
+
host_key: '.slack.com',
|
|
835
|
+
last_access_utc: 1,
|
|
836
|
+
},
|
|
837
|
+
])
|
|
838
|
+
const decryptSpy = spyOn(ChromiumCookieDecryptor.prototype, 'decryptCookie').mockReturnValue('xoxd-AgentBrowser')
|
|
839
|
+
|
|
840
|
+
try {
|
|
841
|
+
// when
|
|
842
|
+
const extractor = new TokenExtractor('darwin', join(agentBrowserProfile, 'missing-desktop'))
|
|
843
|
+
const result = await extractor.extractFromBrowsers()
|
|
844
|
+
|
|
845
|
+
// then
|
|
846
|
+
expect(result).toEqual([
|
|
847
|
+
{
|
|
848
|
+
workspace_id: 'T12345678',
|
|
849
|
+
workspace_name: 'agent-browser-workspace',
|
|
850
|
+
token,
|
|
851
|
+
cookie: 'xoxd-AgentBrowser',
|
|
852
|
+
},
|
|
853
|
+
])
|
|
854
|
+
expect(decryptSpy).toHaveBeenCalledWith(Buffer.from([1, 2, 3]), join(agentBrowserProfile, 'Local State'))
|
|
855
|
+
} finally {
|
|
856
|
+
decryptSpy.mockRestore()
|
|
857
|
+
}
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
it('extract tries desktop before browser profiles', async () => {
|
|
806
861
|
// given — slackDir with LevelDB token data
|
|
807
862
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-fallback-desktop-'))
|
|
808
863
|
tempDirs.push(slackDir)
|
|
@@ -826,7 +881,48 @@ describe('TokenExtractor browser fallback', () => {
|
|
|
826
881
|
extractFromBrowsersSpy.mockRestore()
|
|
827
882
|
})
|
|
828
883
|
|
|
829
|
-
|
|
884
|
+
it('custom browser profile can upgrade desktop token with a cookie', async () => {
|
|
885
|
+
// given
|
|
886
|
+
const slackDir = mkdtempSync(join(tmpdir(), 'slack-custom-cookie-upgrade-'))
|
|
887
|
+
tempDirs.push(slackDir)
|
|
888
|
+
|
|
889
|
+
const hex64 = 'd'.repeat(64)
|
|
890
|
+
const token = `xoxc-1111111111-2222222222-3333333333-${hex64}`
|
|
891
|
+
const leveldbDir = join(slackDir, 'Local Storage', 'leveldb')
|
|
892
|
+
mkdirSync(leveldbDir, { recursive: true })
|
|
893
|
+
writeFileSync(join(leveldbDir, '000001.log'), `"${token}"T12345678"name":"desktop-workspace"`)
|
|
894
|
+
|
|
895
|
+
const browserWorkspace: ExtractedWorkspace = {
|
|
896
|
+
workspace_id: 'T12345678',
|
|
897
|
+
workspace_name: 'browser-workspace',
|
|
898
|
+
token,
|
|
899
|
+
cookie: 'xoxd-browser-cookie',
|
|
900
|
+
}
|
|
901
|
+
const extractFromBrowsersSpy = spyOn(TokenExtractor.prototype as any, 'extractFromBrowsers').mockResolvedValue([
|
|
902
|
+
browserWorkspace,
|
|
903
|
+
])
|
|
904
|
+
|
|
905
|
+
try {
|
|
906
|
+
// when
|
|
907
|
+
const extractor = new TokenExtractor('darwin', slackDir, undefined, undefined, ['/tmp/custom-profile'])
|
|
908
|
+
const result = await extractor.extract()
|
|
909
|
+
|
|
910
|
+
// then
|
|
911
|
+
expect(extractFromBrowsersSpy).toHaveBeenCalled()
|
|
912
|
+
expect(result).toEqual([
|
|
913
|
+
{
|
|
914
|
+
workspace_id: 'T12345678',
|
|
915
|
+
workspace_name: 'desktop-workspace',
|
|
916
|
+
token,
|
|
917
|
+
cookie: 'xoxd-browser-cookie',
|
|
918
|
+
},
|
|
919
|
+
])
|
|
920
|
+
} finally {
|
|
921
|
+
extractFromBrowsersSpy.mockRestore()
|
|
922
|
+
}
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
it('extract falls back to browser profiles when desktop has no tokens', async () => {
|
|
830
926
|
// given — empty slackDir (no tokens)
|
|
831
927
|
const slackDir = mkdtempSync(join(tmpdir(), 'slack-fallback-browser-'))
|
|
832
928
|
tempDirs.push(slackDir)
|
|
@@ -855,7 +951,7 @@ describe('TokenExtractor browser fallback', () => {
|
|
|
855
951
|
extractFromBrowsersSpy.mockRestore()
|
|
856
952
|
})
|
|
857
953
|
|
|
858
|
-
|
|
954
|
+
it('extract falls back to browser when slackDir does not exist', async () => {
|
|
859
955
|
// given — non-existent slackDir
|
|
860
956
|
const slackDir = '/nonexistent/slack/dir'
|
|
861
957
|
const hex64 = 'c'.repeat(64)
|
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
ChromiumCookieDecryptor,
|
|
13
13
|
ChromiumCookieReader,
|
|
14
14
|
discoverBrowserProfileDirs,
|
|
15
|
+
findLocalStatePath,
|
|
15
16
|
getBrowserBasePath,
|
|
17
|
+
getAgentBrowserProfileDirs,
|
|
16
18
|
} from '@/shared/chromium'
|
|
17
19
|
import { DerivedKeyCache } from '@/shared/utils/derived-key-cache'
|
|
18
20
|
import { lookupLinuxKeyringPassword } from '@/shared/utils/linux-keyring'
|
|
@@ -63,12 +65,14 @@ export class TokenExtractor {
|
|
|
63
65
|
private debugLog: ((message: string) => void) | null
|
|
64
66
|
private browserDecryptor: ChromiumCookieDecryptor
|
|
65
67
|
private browserCookieReader: ChromiumCookieReader
|
|
68
|
+
private customBrowserProfileDirs: string[]
|
|
66
69
|
|
|
67
70
|
constructor(
|
|
68
71
|
platform?: NodeJS.Platform,
|
|
69
72
|
slackDir?: string,
|
|
70
73
|
keyCache?: DerivedKeyCache,
|
|
71
74
|
debugLog?: (message: string) => void,
|
|
75
|
+
customBrowserProfileDirs?: string[],
|
|
72
76
|
) {
|
|
73
77
|
this.platform = platform ?? process.platform
|
|
74
78
|
|
|
@@ -79,6 +83,7 @@ export class TokenExtractor {
|
|
|
79
83
|
this.slackDir = slackDir ?? this.getSlackDir()
|
|
80
84
|
this.keyCache = keyCache ?? new DerivedKeyCache()
|
|
81
85
|
this.debugLog = debugLog ?? null
|
|
86
|
+
this.customBrowserProfileDirs = customBrowserProfileDirs ?? []
|
|
82
87
|
this.browserDecryptor = new ChromiumCookieDecryptor({ platform: this.platform })
|
|
83
88
|
this.browserCookieReader = new ChromiumCookieReader()
|
|
84
89
|
}
|
|
@@ -188,6 +193,9 @@ export class TokenExtractor {
|
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
async extract(): Promise<ExtractedWorkspace[]> {
|
|
196
|
+
const results: ExtractedWorkspace[] = []
|
|
197
|
+
const seenTokens = new Set<string>()
|
|
198
|
+
|
|
191
199
|
if (existsSync(this.slackDir)) {
|
|
192
200
|
await this.getDerivedKeyAsync()
|
|
193
201
|
|
|
@@ -201,27 +209,55 @@ export class TokenExtractor {
|
|
|
201
209
|
if (this.cachedKey) {
|
|
202
210
|
await this.keyCache.set('slack', this.cachedKey)
|
|
203
211
|
const retryCookie = await this.extractCookieFromSQLite()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
workspace_name: t.teamName,
|
|
207
|
-
token: t.token,
|
|
208
|
-
cookie: retryCookie,
|
|
209
|
-
}))
|
|
212
|
+
this.addExtractedWorkspaces(results, seenTokens, tokens, retryCookie)
|
|
213
|
+
return this.customBrowserProfileDirs.length > 0 ? this.mergeBrowserResults(results, seenTokens) : results
|
|
210
214
|
}
|
|
211
215
|
}
|
|
212
216
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
workspace_name: t.teamName,
|
|
216
|
-
token: t.token,
|
|
217
|
-
cookie: cookie,
|
|
218
|
-
}))
|
|
217
|
+
this.addExtractedWorkspaces(results, seenTokens, tokens, cookie)
|
|
218
|
+
return this.customBrowserProfileDirs.length > 0 ? this.mergeBrowserResults(results, seenTokens) : results
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
return this.extractFromBrowsers()
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
private async mergeBrowserResults(
|
|
226
|
+
results: ExtractedWorkspace[],
|
|
227
|
+
seenTokens: Set<string>,
|
|
228
|
+
): Promise<ExtractedWorkspace[]> {
|
|
229
|
+
for (const workspace of await this.extractFromBrowsers()) {
|
|
230
|
+
const existing = results.find((item) => item.token === workspace.token)
|
|
231
|
+
if (existing) {
|
|
232
|
+
if (!existing.cookie && workspace.cookie) {
|
|
233
|
+
existing.cookie = workspace.cookie
|
|
234
|
+
}
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
seenTokens.add(workspace.token)
|
|
238
|
+
results.push(workspace)
|
|
239
|
+
}
|
|
240
|
+
return results
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private addExtractedWorkspaces(
|
|
244
|
+
results: ExtractedWorkspace[],
|
|
245
|
+
seenTokens: Set<string>,
|
|
246
|
+
tokens: TokenInfo[],
|
|
247
|
+
cookie: string,
|
|
248
|
+
): void {
|
|
249
|
+
for (const token of tokens) {
|
|
250
|
+
if (seenTokens.has(token.token)) continue
|
|
251
|
+
seenTokens.add(token.token)
|
|
252
|
+
results.push({
|
|
253
|
+
workspace_id: token.teamId,
|
|
254
|
+
workspace_name: token.teamName,
|
|
255
|
+
token: token.token,
|
|
256
|
+
cookie,
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
225
261
|
async extractFromBrowsers(): Promise<ExtractedWorkspace[]> {
|
|
226
262
|
const results: ExtractedWorkspace[] = []
|
|
227
263
|
const seenTokens = new Set<string>()
|
|
@@ -259,6 +295,33 @@ export class TokenExtractor {
|
|
|
259
295
|
}
|
|
260
296
|
}
|
|
261
297
|
|
|
298
|
+
for (const profileDir of getAgentBrowserProfileDirs({ customProfileDirs: this.customBrowserProfileDirs })) {
|
|
299
|
+
const leveldbDir = join(profileDir, 'Local Storage', 'leveldb')
|
|
300
|
+
if (!existsSync(leveldbDir)) continue
|
|
301
|
+
|
|
302
|
+
let tokenInfos: TokenInfo[]
|
|
303
|
+
try {
|
|
304
|
+
tokenInfos = await this.extractFromLevelDB(leveldbDir, 'local-storage')
|
|
305
|
+
} catch {
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (tokenInfos.length === 0) continue
|
|
310
|
+
|
|
311
|
+
const cookie = await this.extractCookieFromBrowserProfile(profileDir, profileDir)
|
|
312
|
+
|
|
313
|
+
for (const t of tokenInfos) {
|
|
314
|
+
if (seenTokens.has(t.token)) continue
|
|
315
|
+
seenTokens.add(t.token)
|
|
316
|
+
results.push({
|
|
317
|
+
workspace_id: t.teamId,
|
|
318
|
+
workspace_name: t.teamName,
|
|
319
|
+
token: t.token,
|
|
320
|
+
cookie,
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
262
325
|
return results
|
|
263
326
|
}
|
|
264
327
|
|
|
@@ -293,7 +356,7 @@ export class TokenExtractor {
|
|
|
293
356
|
if (row.value?.startsWith('xoxd-')) return row.value
|
|
294
357
|
|
|
295
358
|
if (row.encrypted_value && row.encrypted_value.length > 0) {
|
|
296
|
-
const localStatePath = join(browserBase, 'Local State')
|
|
359
|
+
const localStatePath = findLocalStatePath(cookiesPath) ?? join(browserBase, 'Local State')
|
|
297
360
|
const decrypted = this.browserDecryptor.decryptCookie(
|
|
298
361
|
Buffer.from(row.encrypted_value),
|
|
299
362
|
existsSync(localStatePath) ? localStatePath : undefined,
|