agent-messenger 2.4.0 → 2.6.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/.github/workflows/ci.yml +35 -0
- package/.github/workflows/release.yml +0 -12
- package/README.md +3 -3
- package/bun.lock +10 -2
- package/dist/package.json +3 -1
- package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/cli.js +2 -1
- package/dist/src/platforms/channeltalk/cli.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
- package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/index.js +1 -0
- package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
- package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
- package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
- package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.js +2 -1
- package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
- package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
- package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
- package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/discord/cli.d.ts.map +1 -1
- package/dist/src/platforms/discord/cli.js +2 -1
- package/dist/src/platforms/discord/cli.js.map +1 -1
- package/dist/src/platforms/discord/commands/index.d.ts +1 -0
- package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/index.js +1 -0
- package/dist/src/platforms/discord/commands/index.js.map +1 -1
- package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/whoami.js +33 -0
- package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
- package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +2 -1
- package/dist/src/platforms/discordbot/cli.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/index.js +1 -0
- package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
- package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
- package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
- package/dist/src/platforms/instagram/cli.js +2 -1
- package/dist/src/platforms/instagram/cli.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +6 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +12 -0
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/index.js +1 -0
- package/dist/src/platforms/instagram/commands/index.js.map +1 -1
- package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
- package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/instagram/commands/whoami.js +19 -0
- package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
- package/dist/src/platforms/kakaotalk/cli.js +2 -2
- package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +4 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +193 -27
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.js +24 -55
- package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.js +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/commands/{profile.js → whoami.js} +5 -5
- package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
- package/dist/src/platforms/kakaotalk/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/credential-manager.js +1 -0
- package/dist/src/platforms/kakaotalk/credential-manager.js.map +1 -1
- package/dist/src/platforms/kakaotalk/index.d.ts +1 -1
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.js +2 -2
- package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/config.d.ts +8 -2
- package/dist/src/platforms/kakaotalk/protocol/config.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/config.js +15 -2
- package/dist/src/platforms/kakaotalk/protocol/config.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts +6 -2
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +37 -15
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
- package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +22 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +7 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/line/cli.js +2 -2
- package/dist/src/platforms/line/cli.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +9 -59
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/line/commands/index.d.ts +1 -1
- package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/index.js +1 -1
- package/dist/src/platforms/line/commands/index.js.map +1 -1
- package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
- package/dist/src/platforms/line/commands/whoami.js.map +1 -0
- package/dist/src/platforms/slack/cli.d.ts.map +1 -1
- package/dist/src/platforms/slack/cli.js +2 -1
- package/dist/src/platforms/slack/cli.js.map +1 -1
- package/dist/src/platforms/slack/commands/index.d.ts +1 -0
- package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/index.js +1 -0
- package/dist/src/platforms/slack/commands/index.js.map +1 -1
- package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/whoami.js +39 -0
- package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
- package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/cli.js +2 -1
- package/dist/src/platforms/slackbot/cli.js.map +1 -1
- 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/whoami.d.ts +14 -0
- package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
- package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/teams/cli.d.ts.map +1 -1
- package/dist/src/platforms/teams/cli.js +2 -1
- package/dist/src/platforms/teams/cli.js.map +1 -1
- package/dist/src/platforms/teams/commands/index.d.ts +1 -0
- package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/index.js +1 -0
- package/dist/src/platforms/teams/commands/index.js.map +1 -1
- package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/teams/commands/whoami.js +30 -0
- package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
- package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
- package/dist/src/platforms/telegram/cli.js +2 -1
- package/dist/src/platforms/telegram/cli.js.map +1 -1
- package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
- package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/index.js +1 -0
- package/dist/src/platforms/telegram/commands/index.js.map +1 -1
- package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
- package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/telegram/commands/whoami.js +27 -0
- package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
- package/dist/src/platforms/webex/cli.d.ts.map +1 -1
- package/dist/src/platforms/webex/cli.js +2 -1
- package/dist/src/platforms/webex/cli.js.map +1 -1
- package/dist/src/platforms/webex/commands/index.d.ts +1 -0
- package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/index.js +1 -0
- package/dist/src/platforms/webex/commands/index.js.map +1 -1
- package/dist/src/platforms/webex/commands/message.js +1 -1
- package/dist/src/platforms/webex/commands/message.js.map +1 -1
- package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/whoami.js +30 -0
- package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
- package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/cli.js +2 -1
- package/dist/src/platforms/wechatbot/cli.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/index.js +1 -0
- package/dist/src/platforms/wechatbot/commands/index.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
- package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
- package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/cli.js +2 -1
- package/dist/src/platforms/whatsapp/cli.js.map +1 -1
- package/dist/src/platforms/whatsapp/client.d.ts +8 -0
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +116 -8
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/commands/auth.js +115 -45
- package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
- package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/commands/index.js +1 -0
- package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
- package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
- package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
- package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
- package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
- package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +2 -1
- package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
- package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
- package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
- package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
- package/dist/src/shared/utils/qr.d.ts +15 -0
- package/dist/src/shared/utils/qr.d.ts.map +1 -0
- package/dist/src/shared/utils/qr.js +74 -0
- package/dist/src/shared/utils/qr.js.map +1 -0
- package/dist/src/tui/adapters/kakaotalk-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/kakaotalk-adapter.js +5 -2
- package/dist/src/tui/adapters/kakaotalk-adapter.js.map +1 -1
- package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
- package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
- package/docs/content/docs/cli/channeltalk.mdx +11 -0
- package/docs/content/docs/cli/channeltalkbot.mdx +9 -0
- package/docs/content/docs/cli/discord.mdx +10 -0
- package/docs/content/docs/cli/discordbot.mdx +9 -0
- package/docs/content/docs/cli/instagram.mdx +11 -0
- package/docs/content/docs/cli/kakaotalk.mdx +27 -39
- package/docs/content/docs/cli/line.mdx +4 -4
- package/docs/content/docs/cli/slack.mdx +10 -0
- package/docs/content/docs/cli/slackbot.mdx +9 -0
- package/docs/content/docs/cli/teams.mdx +10 -0
- package/docs/content/docs/cli/telegram.mdx +11 -0
- package/docs/content/docs/cli/webex.mdx +10 -0
- package/docs/content/docs/cli/wechatbot.mdx +9 -0
- package/docs/content/docs/cli/whatsapp.mdx +36 -7
- package/docs/content/docs/cli/whatsappbot.mdx +9 -0
- package/e2e/config.ts +1 -1
- package/package.json +3 -1
- package/skills/agent-channeltalk/SKILL.md +12 -1
- package/skills/agent-channeltalkbot/SKILL.md +10 -1
- package/skills/agent-discord/SKILL.md +11 -1
- package/skills/agent-discordbot/SKILL.md +10 -1
- package/skills/agent-instagram/SKILL.md +12 -1
- package/skills/agent-kakaotalk/SKILL.md +24 -70
- package/skills/agent-kakaotalk/references/authentication.md +4 -69
- package/skills/agent-kakaotalk/references/common-patterns.md +14 -1
- package/skills/agent-line/SKILL.md +5 -5
- package/skills/agent-slack/SKILL.md +11 -1
- package/skills/agent-slackbot/SKILL.md +10 -1
- package/skills/agent-teams/SKILL.md +11 -1
- package/skills/agent-telegram/SKILL.md +6 -1
- package/skills/agent-webex/SKILL.md +11 -1
- package/skills/agent-wechatbot/SKILL.md +10 -1
- package/skills/agent-whatsapp/SKILL.md +52 -15
- package/skills/agent-whatsapp/references/authentication.md +36 -6
- package/skills/agent-whatsappbot/SKILL.md +10 -1
- package/src/platforms/channeltalk/cli.ts +2 -0
- package/src/platforms/channeltalk/commands/index.ts +1 -0
- package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
- package/src/platforms/channeltalk/commands/whoami.ts +62 -0
- package/src/platforms/channeltalkbot/cli.ts +2 -0
- package/src/platforms/channeltalkbot/commands/index.ts +1 -0
- package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
- package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
- package/src/platforms/discord/cli.ts +2 -0
- package/src/platforms/discord/commands/index.ts +1 -0
- package/src/platforms/discord/commands/whoami.test.ts +91 -0
- package/src/platforms/discord/commands/whoami.ts +36 -0
- package/src/platforms/discordbot/cli.ts +2 -0
- package/src/platforms/discordbot/commands/index.ts +1 -0
- package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
- package/src/platforms/discordbot/commands/whoami.ts +44 -0
- package/src/platforms/instagram/cli.ts +2 -1
- package/src/platforms/instagram/client.ts +13 -0
- package/src/platforms/instagram/commands/chat.test.ts +1 -5
- package/src/platforms/instagram/commands/index.ts +1 -0
- package/src/platforms/instagram/commands/message.test.ts +1 -5
- package/src/platforms/instagram/commands/whoami.test.ts +60 -0
- package/src/platforms/instagram/commands/whoami.ts +21 -0
- package/src/platforms/kakaotalk/cli.ts +2 -2
- package/src/platforms/kakaotalk/client.test.ts +25 -14
- package/src/platforms/kakaotalk/client.ts +228 -33
- package/src/platforms/kakaotalk/commands/auth.ts +22 -73
- package/src/platforms/kakaotalk/commands/index.ts +1 -1
- package/src/platforms/kakaotalk/commands/{profile.test.ts → whoami.test.ts} +37 -5
- package/src/platforms/kakaotalk/commands/{profile.ts → whoami.ts} +4 -4
- package/src/platforms/kakaotalk/credential-manager.ts +1 -0
- package/src/platforms/kakaotalk/index.ts +1 -0
- package/src/platforms/kakaotalk/listener.test.ts +2 -2
- package/src/platforms/kakaotalk/listener.ts +2 -2
- package/src/platforms/kakaotalk/protocol/config.ts +26 -2
- package/src/platforms/kakaotalk/protocol/session.ts +42 -16
- package/src/platforms/kakaotalk/protocol/types.ts +9 -0
- package/src/platforms/kakaotalk/types.ts +16 -0
- package/src/platforms/line/cli.ts +2 -2
- package/src/platforms/line/commands/auth.ts +37 -70
- package/src/platforms/line/commands/index.ts +1 -1
- package/src/platforms/line/commands/{profile.test.ts → whoami.test.ts} +11 -11
- package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
- package/src/platforms/slack/cli.ts +2 -0
- package/src/platforms/slack/commands/index.ts +1 -0
- package/src/platforms/slack/commands/whoami.test.ts +126 -0
- package/src/platforms/slack/commands/whoami.ts +40 -0
- package/src/platforms/slackbot/cli.ts +2 -1
- package/src/platforms/slackbot/commands/index.ts +1 -0
- package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
- package/src/platforms/slackbot/commands/whoami.ts +44 -0
- package/src/platforms/teams/cli.ts +2 -0
- package/src/platforms/teams/commands/index.ts +1 -0
- package/src/platforms/teams/commands/whoami.test.ts +83 -0
- package/src/platforms/teams/commands/whoami.ts +33 -0
- package/src/platforms/telegram/cli.ts +2 -1
- package/src/platforms/telegram/commands/index.ts +1 -0
- package/src/platforms/telegram/commands/whoami.test.ts +75 -0
- package/src/platforms/telegram/commands/whoami.ts +29 -0
- package/src/platforms/webex/cli.ts +2 -1
- package/src/platforms/webex/commands/auth.test.ts +58 -46
- package/src/platforms/webex/commands/index.ts +1 -0
- package/src/platforms/webex/commands/member.test.ts +1 -5
- package/src/platforms/webex/commands/message.test.ts +1 -5
- package/src/platforms/webex/commands/message.ts +1 -1
- package/src/platforms/webex/commands/snapshot.test.ts +1 -5
- package/src/platforms/webex/commands/space.test.ts +1 -5
- package/src/platforms/webex/commands/whoami.test.ts +113 -0
- package/src/platforms/webex/commands/whoami.ts +31 -0
- package/src/platforms/webex/credential-manager.test.ts +0 -1
- package/src/platforms/wechatbot/cli.ts +2 -1
- package/src/platforms/wechatbot/commands/index.ts +1 -0
- package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
- package/src/platforms/wechatbot/commands/whoami.ts +43 -0
- package/src/platforms/whatsapp/cli.ts +2 -1
- package/src/platforms/whatsapp/client.ts +156 -24
- package/src/platforms/whatsapp/commands/auth.ts +176 -70
- package/src/platforms/whatsapp/commands/index.ts +1 -0
- package/src/platforms/whatsapp/commands/shared.ts +2 -2
- package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
- package/src/platforms/whatsapp/commands/whoami.ts +21 -0
- package/src/platforms/whatsapp/ensure-auth.ts +2 -2
- package/src/platforms/whatsappbot/cli.ts +2 -1
- package/src/platforms/whatsappbot/commands/index.ts +1 -0
- package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
- package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
- package/src/shared/utils/qr.ts +92 -0
- package/src/tui/adapters/kakaotalk-adapter.ts +5 -2
- package/src/tui/adapters/whatsapp-adapter.ts +19 -16
- package/dist/src/platforms/kakaotalk/commands/profile.d.ts +0 -3
- package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +0 -1
- package/dist/src/platforms/kakaotalk/commands/profile.js.map +0 -1
- package/dist/src/platforms/line/commands/profile.d.ts +0 -3
- package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
- package/dist/src/platforms/line/commands/profile.js.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Long } from 'bson'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
2
6
|
|
|
3
7
|
import { warn } from '@/shared/utils/stderr'
|
|
4
8
|
|
|
5
|
-
import {
|
|
9
|
+
import { LANG, PC_OS_NAME, getLocoDeviceConfig } from './protocol/config'
|
|
6
10
|
import { LocoSession } from './protocol/session'
|
|
7
|
-
import type { ChatListResponse, LoginListResponse } from './protocol/types'
|
|
8
|
-
import type { KakaoChat, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
|
|
11
|
+
import type { ChatListResponse, LoginListResponse, SyncState } from './protocol/types'
|
|
12
|
+
import type { KakaoChat, KakaoDeviceType, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
|
|
9
13
|
|
|
10
14
|
export class KakaoTalkError extends Error {
|
|
11
15
|
code: string
|
|
@@ -74,6 +78,14 @@ function matchesSearch(chat: ChatData, term: string): boolean {
|
|
|
74
78
|
return names.some((n) => n.toLowerCase().includes(lower))
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
function findMaxLogId(logs: Array<Record<string, unknown>>, field: string): Long | null {
|
|
82
|
+
return logs.reduce<Long | null>((max, log) => {
|
|
83
|
+
const current = bsonToLong(log[field])
|
|
84
|
+
if (!current) return max
|
|
85
|
+
return !max || current.greaterThan(max) ? current : max
|
|
86
|
+
}, null)
|
|
87
|
+
}
|
|
88
|
+
|
|
77
89
|
function collectChats(chatDatas: ChatData[], into: ChatData[], seen: Set<string>): void {
|
|
78
90
|
for (const chat of chatDatas) {
|
|
79
91
|
const id = String(chat.c)
|
|
@@ -92,16 +104,140 @@ function wrapError(error: unknown, code: string): KakaoTalkError {
|
|
|
92
104
|
|
|
93
105
|
const MAX_PAGES = 50
|
|
94
106
|
|
|
107
|
+
const CONFIG_DIR = join(homedir(), '.config', 'agent-messenger')
|
|
108
|
+
|
|
109
|
+
function syncStatePath(deviceUuid: string): string {
|
|
110
|
+
return join(CONFIG_DIR, `kakaotalk-sync-state-${deviceUuid}.json`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function loadSyncState(deviceUuid: string): Promise<SyncState | undefined> {
|
|
114
|
+
const path = syncStatePath(deviceUuid)
|
|
115
|
+
if (!existsSync(path)) return undefined
|
|
116
|
+
const content = await readFile(path, 'utf-8')
|
|
117
|
+
const parsed = JSON.parse(content) as Partial<SyncState>
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
parsed.version !== 2 ||
|
|
121
|
+
typeof parsed.revision !== 'number' ||
|
|
122
|
+
!Array.isArray(parsed.chatIds) ||
|
|
123
|
+
!Array.isArray(parsed.maxIds) ||
|
|
124
|
+
parsed.chatIds.length !== parsed.maxIds.length ||
|
|
125
|
+
!parsed.lastTokenId ||
|
|
126
|
+
typeof parsed.lbk !== 'number'
|
|
127
|
+
) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return parsed as SyncState
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function saveSyncState(deviceUuid: string, state: SyncState): Promise<void> {
|
|
135
|
+
await mkdir(CONFIG_DIR, { recursive: true })
|
|
136
|
+
const path = syncStatePath(deviceUuid)
|
|
137
|
+
await writeFile(path, JSON.stringify(state, null, 2))
|
|
138
|
+
await chmod(path, 0o600)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function toLongLike(v: unknown): { low: number; high: number } {
|
|
142
|
+
if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
|
|
143
|
+
const { low, high } = v as { low: number; high: number }
|
|
144
|
+
return { low, high }
|
|
145
|
+
}
|
|
146
|
+
if (typeof v === 'number') {
|
|
147
|
+
const big = BigInt(v)
|
|
148
|
+
return { low: Number(big & 0xffffffffn), high: Number((big >> 32n) & 0xffffffffn) }
|
|
149
|
+
}
|
|
150
|
+
return { low: 0, high: 0 }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildSyncState(loginResult: LoginListResponse, previousRevision: number): SyncState {
|
|
154
|
+
const chatDatas = (loginResult.chatDatas ?? []) as Array<Record<string, unknown>>
|
|
155
|
+
return {
|
|
156
|
+
version: 2,
|
|
157
|
+
revision: typeof loginResult.revision === 'number' ? loginResult.revision : previousRevision,
|
|
158
|
+
chatIds: chatDatas.map((chat) => toLongLike(chat.c)),
|
|
159
|
+
maxIds: chatDatas.map((chat) => toLongLike(chat.ll)),
|
|
160
|
+
lastTokenId: toLongLike(loginResult.lastTokenId),
|
|
161
|
+
lbk: typeof loginResult.lbk === 'number' ? loginResult.lbk : 0,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function deleteFromSyncState(state: SyncState, chatId: string): void {
|
|
166
|
+
const index = state.chatIds.findIndex((entry) => longToString(entry) === chatId)
|
|
167
|
+
if (index === -1) return
|
|
168
|
+
|
|
169
|
+
state.chatIds.splice(index, 1)
|
|
170
|
+
state.maxIds.splice(index, 1)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function upsertSyncState(state: SyncState, chatId: unknown, maxId: unknown): void {
|
|
174
|
+
const chatIdString = longToString(chatId)
|
|
175
|
+
const nextChatId = toLongLike(chatId)
|
|
176
|
+
const nextMaxId = toLongLike(maxId)
|
|
177
|
+
const index = state.chatIds.findIndex((entry) => longToString(entry) === chatIdString)
|
|
178
|
+
|
|
179
|
+
if (index === -1) {
|
|
180
|
+
state.chatIds.push(nextChatId)
|
|
181
|
+
state.maxIds.push(nextMaxId)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
state.chatIds[index] = nextChatId
|
|
186
|
+
state.maxIds[index] = nextMaxId
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mergeSyncState(previous: SyncState | undefined, loginResult: LoginListResponse): SyncState {
|
|
190
|
+
const next = previous
|
|
191
|
+
? {
|
|
192
|
+
version: 2 as const,
|
|
193
|
+
revision: previous.revision,
|
|
194
|
+
chatIds: [...previous.chatIds],
|
|
195
|
+
maxIds: [...previous.maxIds],
|
|
196
|
+
lastTokenId: previous.lastTokenId,
|
|
197
|
+
lbk: previous.lbk,
|
|
198
|
+
}
|
|
199
|
+
: buildSyncState(loginResult, 0)
|
|
200
|
+
|
|
201
|
+
next.revision = typeof loginResult.revision === 'number' ? loginResult.revision : next.revision
|
|
202
|
+
next.lastTokenId = toLongLike(loginResult.lastTokenId)
|
|
203
|
+
next.lbk = typeof loginResult.lbk === 'number' ? loginResult.lbk : next.lbk
|
|
204
|
+
|
|
205
|
+
const delChatIds = Array.isArray(loginResult.delChatIds) ? loginResult.delChatIds : []
|
|
206
|
+
for (const chatId of delChatIds) {
|
|
207
|
+
deleteFromSyncState(next, longToString(chatId))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const chatDatas = Array.isArray(loginResult.chatDatas) ? loginResult.chatDatas : []
|
|
211
|
+
for (const chat of chatDatas) {
|
|
212
|
+
upsertSyncState(next, chat.c, chat.ll)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return next
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatMessages(logs: Array<Record<string, unknown>>, count: number): KakaoMessage[] {
|
|
219
|
+
logs.sort((a, b) => (a.sendAt as number) - (b.sendAt as number))
|
|
220
|
+
|
|
221
|
+
return logs.slice(-count).map((log) => ({
|
|
222
|
+
log_id: longToString(log.logId),
|
|
223
|
+
type: log.type as number,
|
|
224
|
+
author_id: log.authorId as number,
|
|
225
|
+
message: log.message as string,
|
|
226
|
+
sent_at: log.sendAt as number,
|
|
227
|
+
}))
|
|
228
|
+
}
|
|
229
|
+
|
|
95
230
|
export class KakaoTalkClient {
|
|
96
231
|
private oauthToken: string | null = null
|
|
97
232
|
private userId: string | null = null
|
|
98
233
|
private deviceUuid: string | null = null
|
|
234
|
+
private deviceType: KakaoDeviceType = 'tablet'
|
|
99
235
|
private state: SessionState | null = null
|
|
100
236
|
private initPromise: Promise<SessionState> | null = null
|
|
101
237
|
private closed = false
|
|
102
238
|
|
|
103
239
|
async login(
|
|
104
|
-
credentials?: { oauthToken: string; userId: string; deviceUuid?: string },
|
|
240
|
+
credentials?: { oauthToken: string; userId: string; deviceUuid?: string; deviceType?: KakaoDeviceType },
|
|
105
241
|
accountId?: string,
|
|
106
242
|
): Promise<this> {
|
|
107
243
|
if (credentials) {
|
|
@@ -110,19 +246,26 @@ export class KakaoTalkClient {
|
|
|
110
246
|
this.oauthToken = credentials.oauthToken
|
|
111
247
|
this.userId = credentials.userId
|
|
112
248
|
this.deviceUuid = credentials.deviceUuid ?? `agent-messenger-${credentials.userId}`
|
|
249
|
+
this.deviceType = credentials.deviceType ?? 'tablet'
|
|
113
250
|
return this
|
|
114
251
|
}
|
|
115
252
|
const { ensureKakaoAuth } = await import('./ensure-auth')
|
|
116
253
|
const account = await ensureKakaoAuth(accountId)
|
|
117
|
-
return this.login({
|
|
254
|
+
return this.login({
|
|
255
|
+
oauthToken: account.oauth_token,
|
|
256
|
+
userId: account.user_id,
|
|
257
|
+
deviceUuid: account.device_uuid,
|
|
258
|
+
deviceType: account.device_type,
|
|
259
|
+
})
|
|
118
260
|
}
|
|
119
261
|
|
|
120
|
-
getCredentials(): { oauthToken: string; userId: string; deviceUuid: string } {
|
|
262
|
+
getCredentials(): { oauthToken: string; userId: string; deviceUuid: string; deviceType: KakaoDeviceType } {
|
|
121
263
|
this.ensureAuth()
|
|
122
264
|
return {
|
|
123
265
|
oauthToken: this.oauthToken!,
|
|
124
266
|
userId: this.userId!,
|
|
125
267
|
deviceUuid: this.deviceUuid!,
|
|
268
|
+
deviceType: this.deviceType,
|
|
126
269
|
}
|
|
127
270
|
}
|
|
128
271
|
|
|
@@ -179,7 +322,11 @@ export class KakaoTalkClient {
|
|
|
179
322
|
private async connect(): Promise<SessionState> {
|
|
180
323
|
const session = new LocoSession()
|
|
181
324
|
try {
|
|
182
|
-
const
|
|
325
|
+
const syncState = await loadSyncState(this.deviceUuid!)
|
|
326
|
+
const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!, syncState, this.deviceType)
|
|
327
|
+
|
|
328
|
+
const newSyncState = mergeSyncState(syncState, loginResult)
|
|
329
|
+
await saveSyncState(this.deviceUuid!, newSyncState)
|
|
183
330
|
|
|
184
331
|
session.onClose(() => {
|
|
185
332
|
if (this.state?.session === session) {
|
|
@@ -253,21 +400,62 @@ export class KakaoTalkClient {
|
|
|
253
400
|
}
|
|
254
401
|
|
|
255
402
|
async getMessages(chatId: string, options?: { count?: number; from?: string }): Promise<KakaoMessage[]> {
|
|
256
|
-
return this.executeWithReconnect(async ({ session
|
|
403
|
+
return this.executeWithReconnect(async ({ session }) => {
|
|
257
404
|
try {
|
|
258
|
-
const rawChats = (loginResult.chatDatas ?? []) as ChatData[]
|
|
259
|
-
const chat = rawChats.find((c) => String(c.c) === chatId)
|
|
260
|
-
const lastLogId = chat?.ll as { high: number; low: number } | undefined
|
|
261
|
-
const maxLogId = lastLogId ? new Long(lastLogId.low, lastLogId.high) : undefined
|
|
262
|
-
|
|
263
405
|
const count = options?.count ?? 20
|
|
264
406
|
const cursor = options?.from ? parseLong(options.from) : undefined
|
|
265
407
|
|
|
266
408
|
const cid = parseLong(chatId)
|
|
267
|
-
|
|
409
|
+
|
|
268
410
|
const allMessages: Array<Record<string, unknown>> = []
|
|
269
411
|
const seenLogIds = new Set<string>()
|
|
270
|
-
let cur =
|
|
412
|
+
let cur = cursor ?? Long.fromNumber(0)
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
416
|
+
const response = await session.getChatLogs([cid], [cur])
|
|
417
|
+
const responseStatus = response.body.status
|
|
418
|
+
if (typeof responseStatus === 'number' && responseStatus !== 0) {
|
|
419
|
+
throw new Error(`MCHATLOGS failed: ${responseStatus}`)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const batch = ((response.body.chatLogs ?? []) as Array<Record<string, unknown>>).filter(
|
|
423
|
+
(log) => longToString(log.chatId) === chatId,
|
|
424
|
+
)
|
|
425
|
+
if (batch.length === 0) {
|
|
426
|
+
return formatMessages(allMessages, count)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
for (const log of batch) {
|
|
430
|
+
const lid = longToString(log.logId)
|
|
431
|
+
if (!seenLogIds.has(lid)) {
|
|
432
|
+
seenLogIds.add(lid)
|
|
433
|
+
allMessages.push(log)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const maxLog = findMaxLogId(batch, 'logId')
|
|
438
|
+
if (!maxLog || maxLog.equals(cur) || response.body.eof) {
|
|
439
|
+
return formatMessages(allMessages, count)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
cur = maxLog
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
allMessages.length = 0
|
|
446
|
+
seenLogIds.clear()
|
|
447
|
+
cur = cursor ?? Long.fromNumber(0)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (allMessages.length > 0) {
|
|
451
|
+
warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
|
|
452
|
+
return formatMessages(allMessages, count)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Fetch fresh lastLogId via CHATONROOM (not the stale login-time snapshot)
|
|
456
|
+
const chatInfo = await session.getChatInfo(cid)
|
|
457
|
+
const chatBody = chatInfo.body as Record<string, unknown>
|
|
458
|
+
const maxLogId = bsonToLong(chatBody.l)
|
|
271
459
|
|
|
272
460
|
let reachedEnd = false
|
|
273
461
|
for (let page = 0; page < MAX_PAGES; page++) {
|
|
@@ -283,11 +471,7 @@ export class KakaoTalkClient {
|
|
|
283
471
|
}
|
|
284
472
|
}
|
|
285
473
|
|
|
286
|
-
const maxLog = batch
|
|
287
|
-
const lid = l.logId as { high: number; low: number }
|
|
288
|
-
const long = new Long(lid.low, lid.high)
|
|
289
|
-
return !max || long.greaterThan(max) ? long : max
|
|
290
|
-
}, null)
|
|
474
|
+
const maxLog = findMaxLogId(batch, 'logId')
|
|
291
475
|
|
|
292
476
|
if (!maxLog || maxLog.equals(cur) || response.body.isOK) { reachedEnd = true; break }
|
|
293
477
|
cur = maxLog
|
|
@@ -296,15 +480,7 @@ export class KakaoTalkClient {
|
|
|
296
480
|
warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
|
|
297
481
|
}
|
|
298
482
|
|
|
299
|
-
allMessages
|
|
300
|
-
|
|
301
|
-
return allMessages.slice(-count).map((log) => ({
|
|
302
|
-
log_id: longToString(log.logId),
|
|
303
|
-
type: log.type as number,
|
|
304
|
-
author_id: log.authorId as number,
|
|
305
|
-
message: log.message as string,
|
|
306
|
-
sent_at: log.sendAt as number,
|
|
307
|
-
}))
|
|
483
|
+
return formatMessages(allMessages, count)
|
|
308
484
|
} catch (error) {
|
|
309
485
|
throw wrapError(error, 'get_messages_failed')
|
|
310
486
|
}
|
|
@@ -332,17 +508,24 @@ export class KakaoTalkClient {
|
|
|
332
508
|
async getProfile(): Promise<KakaoProfile> {
|
|
333
509
|
this.ensureAuth()
|
|
334
510
|
try {
|
|
511
|
+
const deviceConfig = getLocoDeviceConfig(this.deviceType)
|
|
512
|
+
const isPC = deviceConfig.os !== 'android'
|
|
513
|
+
const apiPrefix = isPC ? 'mac' : 'android'
|
|
514
|
+
const userAgent = isPC
|
|
515
|
+
? `KT/${deviceConfig.appVersion} Md/${PC_OS_NAME} ${LANG}`
|
|
516
|
+
: `KT/${deviceConfig.appVersion} An/13 ${LANG}`
|
|
517
|
+
|
|
335
518
|
const headers = {
|
|
336
519
|
Authorization: `${this.oauthToken}-${this.deviceUuid}`,
|
|
337
|
-
A: `${
|
|
338
|
-
'User-Agent':
|
|
520
|
+
A: `${deviceConfig.os}/${deviceConfig.appVersion}/${LANG}`,
|
|
521
|
+
'User-Agent': userAgent,
|
|
339
522
|
Accept: '*/*',
|
|
340
523
|
'Accept-Language': LANG,
|
|
341
524
|
}
|
|
342
525
|
|
|
343
526
|
const [profileRes, settingsRes] = await Promise.all([
|
|
344
|
-
fetch(
|
|
345
|
-
fetch(
|
|
527
|
+
fetch(`https://katalk.kakao.com/${apiPrefix}/profile3/me.json`, { headers }),
|
|
528
|
+
fetch(`https://katalk.kakao.com/${apiPrefix}/account/more_settings.json?since=0&lang=ko`, { headers }),
|
|
346
529
|
])
|
|
347
530
|
|
|
348
531
|
if (!profileRes.ok) {
|
|
@@ -353,9 +536,15 @@ export class KakaoTalkClient {
|
|
|
353
536
|
const profile = profileData.profile as Record<string, unknown> | undefined
|
|
354
537
|
|
|
355
538
|
let accountDisplayId: string | null = null
|
|
539
|
+
let accountEmail: string | null = null
|
|
540
|
+
let pstnNumber: string | null = null
|
|
541
|
+
let emailVerified: boolean | null = null
|
|
356
542
|
if (settingsRes.ok) {
|
|
357
543
|
const settingsData = await settingsRes.json() as Record<string, unknown>
|
|
358
544
|
accountDisplayId = (settingsData.accountDisplayId as string) || null
|
|
545
|
+
accountEmail = (settingsData.accountEmail as string) || null
|
|
546
|
+
pstnNumber = (settingsData.pstnNumber as string) || null
|
|
547
|
+
emailVerified = typeof settingsData.emailVerified === 'boolean' ? settingsData.emailVerified : null
|
|
359
548
|
}
|
|
360
549
|
|
|
361
550
|
return {
|
|
@@ -363,8 +552,14 @@ export class KakaoTalkClient {
|
|
|
363
552
|
nickname: (profile?.nickName as string) || '',
|
|
364
553
|
profile_image_url: (profile?.profileImageUrl as string) || null,
|
|
365
554
|
original_profile_image_url: (profile?.originalProfileImageUrl as string) || null,
|
|
555
|
+
background_image_url: (profile?.backgroundImageUrl as string) || null,
|
|
556
|
+
original_background_image_url: (profile?.originalBackgroundImageUrl as string) || null,
|
|
557
|
+
fullname: (profile?.fullname as string) || null,
|
|
366
558
|
status_message: (profile?.statusMessage as string) || null,
|
|
367
559
|
account_display_id: accountDisplayId,
|
|
560
|
+
account_email: accountEmail,
|
|
561
|
+
pstn_number: pstnNumber,
|
|
562
|
+
email_verified: emailVerified,
|
|
368
563
|
}
|
|
369
564
|
} catch (error) {
|
|
370
565
|
throw wrapError(error, 'get_profile_failed')
|
|
@@ -6,7 +6,7 @@ import { handleError } from '@/shared/utils/error-handler'
|
|
|
6
6
|
import { formatOutput } from '@/shared/utils/output'
|
|
7
7
|
import { info, error, debug } from '@/shared/utils/stderr'
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { loginFlow } from '../auth/kakao-login'
|
|
10
10
|
import { CredentialManager } from '../credential-manager'
|
|
11
11
|
import { KakaoTokenExtractor } from '../token-extractor'
|
|
12
12
|
import {
|
|
@@ -23,7 +23,9 @@ function isInteractiveSession(): boolean {
|
|
|
23
23
|
function hasTTY(): boolean {
|
|
24
24
|
try {
|
|
25
25
|
const { openSync, closeSync } = require('node:fs') as typeof import('node:fs')
|
|
26
|
-
|
|
26
|
+
// CONIN$ is the Windows console input device; /dev/tty is the Unix equivalent
|
|
27
|
+
const ttyDevice = process.platform === 'win32' ? 'CONIN$' : '/dev/tty'
|
|
28
|
+
const fd = openSync(ttyDevice, 'r')
|
|
27
29
|
closeSync(fd)
|
|
28
30
|
return true
|
|
29
31
|
} catch { return false }
|
|
@@ -178,6 +180,20 @@ async function promptHidden(message: string): Promise<string | undefined> {
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
async function promptHiddenTTY(message: string): Promise<string | undefined> {
|
|
183
|
+
if (process.platform === 'win32') {
|
|
184
|
+
const { execSync } = require('node:child_process') as typeof import('node:child_process')
|
|
185
|
+
try {
|
|
186
|
+
const escapedMessage = message.replace(/'/g, "''")
|
|
187
|
+
const ps = `$p = Read-Host '${escapedMessage}' -AsSecureString; [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($p))`
|
|
188
|
+
const result = execSync(`powershell -NoProfile -Command "${ps}"`, {
|
|
189
|
+
encoding: 'utf-8',
|
|
190
|
+
timeout: 120_000,
|
|
191
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
192
|
+
})
|
|
193
|
+
return result.trim() || undefined
|
|
194
|
+
} catch { return undefined }
|
|
195
|
+
}
|
|
196
|
+
|
|
181
197
|
const { createReadStream } = await import('node:fs')
|
|
182
198
|
const { createInterface } = await import('node:readline/promises')
|
|
183
199
|
const ttyIn = createReadStream('/dev/tty')
|
|
@@ -277,7 +293,8 @@ async function loginAction(options: KakaoAuthOptions): Promise<void> {
|
|
|
277
293
|
|
|
278
294
|
const existing = await credManager.getAccount()
|
|
279
295
|
const pendingState = await credManager.loadPendingLogin()
|
|
280
|
-
const
|
|
296
|
+
const existingUuid = existing?.auth_method === 'login' ? existing?.device_uuid : undefined
|
|
297
|
+
const savedDeviceUuid = pendingState?.device_uuid ?? existingUuid
|
|
281
298
|
|
|
282
299
|
const onPasscodeDisplay = (code: string) => {
|
|
283
300
|
if (interactive) {
|
|
@@ -355,6 +372,7 @@ async function handleLoginResult(
|
|
|
355
372
|
refresh_token: result.credentials.refresh_token,
|
|
356
373
|
device_uuid: result.credentials.device_uuid,
|
|
357
374
|
device_type: result.credentials.device_type,
|
|
375
|
+
auth_method: 'login',
|
|
358
376
|
created_at: now,
|
|
359
377
|
updated_at: now,
|
|
360
378
|
})
|
|
@@ -372,67 +390,6 @@ async function handleLoginResult(
|
|
|
372
390
|
}
|
|
373
391
|
}
|
|
374
392
|
|
|
375
|
-
async function extractAction(options: {
|
|
376
|
-
pretty?: boolean
|
|
377
|
-
debug?: boolean
|
|
378
|
-
unsafelyShowSecrets?: boolean
|
|
379
|
-
}): Promise<void> {
|
|
380
|
-
try {
|
|
381
|
-
if (options.unsafelyShowSecrets) {
|
|
382
|
-
options.debug = true
|
|
383
|
-
}
|
|
384
|
-
const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
|
|
385
|
-
const extractor = new KakaoTokenExtractor(undefined, debugLog)
|
|
386
|
-
|
|
387
|
-
const token = await extractor.extract()
|
|
388
|
-
|
|
389
|
-
if (!token) {
|
|
390
|
-
console.log(
|
|
391
|
-
formatOutput(
|
|
392
|
-
{
|
|
393
|
-
error: 'No credentials found. Make sure KakaoTalk desktop app is installed and logged in.',
|
|
394
|
-
hint: options.debug ? undefined : 'Run with --debug for more info.',
|
|
395
|
-
},
|
|
396
|
-
options.pretty,
|
|
397
|
-
),
|
|
398
|
-
)
|
|
399
|
-
process.exit(1)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (options.debug) {
|
|
403
|
-
const display = options.unsafelyShowSecrets
|
|
404
|
-
? token.oauth_token
|
|
405
|
-
: `${token.oauth_token.substring(0, 12)}...`
|
|
406
|
-
debug(`[debug] oauth_token: ${display}`)
|
|
407
|
-
debug(`[debug] user_id: ${token.user_id}`)
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const credManager = new CredentialManager()
|
|
411
|
-
const accountId = token.user_id || 'default'
|
|
412
|
-
const now = new Date().toISOString()
|
|
413
|
-
|
|
414
|
-
await credManager.setAccount({
|
|
415
|
-
account_id: accountId,
|
|
416
|
-
oauth_token: token.oauth_token,
|
|
417
|
-
user_id: token.user_id,
|
|
418
|
-
refresh_token: token.refresh_token,
|
|
419
|
-
device_uuid: token.device_uuid ?? generateDeviceUuid(),
|
|
420
|
-
device_type: 'tablet',
|
|
421
|
-
created_at: now,
|
|
422
|
-
updated_at: now,
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
const config = await credManager.load()
|
|
426
|
-
if (!config.current_account) {
|
|
427
|
-
await credManager.setCurrentAccount(accountId)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
console.log(formatOutput({ account_id: accountId, user_id: token.user_id, extracted: true }, options.pretty))
|
|
431
|
-
} catch (error) {
|
|
432
|
-
handleError(error as Error)
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
393
|
async function listAction(options: { pretty?: boolean }): Promise<void> {
|
|
437
394
|
try {
|
|
438
395
|
const credManager = new CredentialManager()
|
|
@@ -483,7 +440,7 @@ async function statusAction(options: { account?: string; pretty?: boolean }): Pr
|
|
|
483
440
|
const account = await credManager.getAccount(options.account)
|
|
484
441
|
|
|
485
442
|
if (!account) {
|
|
486
|
-
console.log(formatOutput({ error: 'No account configured. Run "auth login"
|
|
443
|
+
console.log(formatOutput({ error: 'No account configured. Run "auth login" first.' }, options.pretty))
|
|
487
444
|
process.exit(1)
|
|
488
445
|
}
|
|
489
446
|
|
|
@@ -533,14 +490,6 @@ export const authCommand = new Command('auth')
|
|
|
533
490
|
.option('--debug', 'Show debug output')
|
|
534
491
|
.action(loginAction),
|
|
535
492
|
)
|
|
536
|
-
.addCommand(
|
|
537
|
-
new Command('extract')
|
|
538
|
-
.description('Extract credentials from KakaoTalk desktop app (kicks desktop session)')
|
|
539
|
-
.option('--pretty', 'Pretty print JSON output')
|
|
540
|
-
.option('--debug', 'Show debug output for troubleshooting')
|
|
541
|
-
.option('--unsafely-show-secrets', 'Show full token values in debug output')
|
|
542
|
-
.action(extractAction),
|
|
543
|
-
)
|
|
544
493
|
.addCommand(
|
|
545
494
|
new Command('list')
|
|
546
495
|
.description('List all authenticated KakaoTalk accounts')
|
|
@@ -13,6 +13,15 @@ const mockGetProfile = mock(() =>
|
|
|
13
13
|
user_id: 'user-1',
|
|
14
14
|
nickname: 'Test User',
|
|
15
15
|
profile_image_url: 'https://example.com/avatar.jpg',
|
|
16
|
+
original_profile_image_url: 'https://example.com/avatar_orig.jpg',
|
|
17
|
+
background_image_url: 'https://example.com/bg.jpg',
|
|
18
|
+
original_background_image_url: 'https://example.com/bg_orig.jpg',
|
|
19
|
+
fullname: 'Real Name',
|
|
20
|
+
status_message: 'Hello!',
|
|
21
|
+
account_display_id: 'testuser',
|
|
22
|
+
account_email: 'test@example.com',
|
|
23
|
+
pstn_number: '+821012345678',
|
|
24
|
+
email_verified: true,
|
|
16
25
|
}),
|
|
17
26
|
)
|
|
18
27
|
|
|
@@ -24,9 +33,9 @@ mock.module('./shared', () => ({
|
|
|
24
33
|
withKakaoClient: mockWithKakaoClient,
|
|
25
34
|
}))
|
|
26
35
|
|
|
27
|
-
import {
|
|
36
|
+
import { whoamiCommand } from './whoami'
|
|
28
37
|
|
|
29
|
-
describe('
|
|
38
|
+
describe('whoami command', () => {
|
|
30
39
|
let consoleLogSpy: ReturnType<typeof mock>
|
|
31
40
|
|
|
32
41
|
beforeEach(() => {
|
|
@@ -43,6 +52,15 @@ describe('profile command', () => {
|
|
|
43
52
|
user_id: 'user-1',
|
|
44
53
|
nickname: 'Test User',
|
|
45
54
|
profile_image_url: 'https://example.com/avatar.jpg',
|
|
55
|
+
original_profile_image_url: 'https://example.com/avatar_orig.jpg',
|
|
56
|
+
background_image_url: 'https://example.com/bg.jpg',
|
|
57
|
+
original_background_image_url: 'https://example.com/bg_orig.jpg',
|
|
58
|
+
fullname: 'Real Name',
|
|
59
|
+
status_message: 'Hello!',
|
|
60
|
+
account_display_id: 'testuser',
|
|
61
|
+
account_email: 'test@example.com',
|
|
62
|
+
pstn_number: '+821012345678',
|
|
63
|
+
email_verified: true,
|
|
46
64
|
}),
|
|
47
65
|
)
|
|
48
66
|
|
|
@@ -55,7 +73,7 @@ describe('profile command', () => {
|
|
|
55
73
|
})
|
|
56
74
|
|
|
57
75
|
test('outputs profile information', async () => {
|
|
58
|
-
await
|
|
76
|
+
await whoamiCommand.parseAsync([], { from: 'user' })
|
|
59
77
|
|
|
60
78
|
expect(mockGetProfile).toHaveBeenCalled()
|
|
61
79
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
@@ -64,8 +82,22 @@ describe('profile command', () => {
|
|
|
64
82
|
expect(output.profile_image_url).toBe('https://example.com/avatar.jpg')
|
|
65
83
|
})
|
|
66
84
|
|
|
85
|
+
test('outputs enriched profile fields', async () => {
|
|
86
|
+
await whoamiCommand.parseAsync([], { from: 'user' })
|
|
87
|
+
|
|
88
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
89
|
+
expect(output.background_image_url).toBe('https://example.com/bg.jpg')
|
|
90
|
+
expect(output.original_background_image_url).toBe('https://example.com/bg_orig.jpg')
|
|
91
|
+
expect(output.fullname).toBe('Real Name')
|
|
92
|
+
expect(output.status_message).toBe('Hello!')
|
|
93
|
+
expect(output.account_display_id).toBe('testuser')
|
|
94
|
+
expect(output.account_email).toBe('test@example.com')
|
|
95
|
+
expect(output.pstn_number).toBe('+821012345678')
|
|
96
|
+
expect(output.email_verified).toBe(true)
|
|
97
|
+
})
|
|
98
|
+
|
|
67
99
|
test('passes account option to withKakaoClient', async () => {
|
|
68
|
-
await
|
|
100
|
+
await whoamiCommand.parseAsync(['--account', 'my-account'], { from: 'user' })
|
|
69
101
|
|
|
70
102
|
expect(mockWithKakaoClient).toHaveBeenCalledWith(
|
|
71
103
|
expect.objectContaining({ account: 'my-account' }),
|
|
@@ -74,7 +106,7 @@ describe('profile command', () => {
|
|
|
74
106
|
})
|
|
75
107
|
|
|
76
108
|
test('outputs profile with pretty flag', async () => {
|
|
77
|
-
await
|
|
109
|
+
await whoamiCommand.parseAsync(['--pretty'], { from: 'user' })
|
|
78
110
|
|
|
79
111
|
expect(mockGetProfile).toHaveBeenCalled()
|
|
80
112
|
const rawOutput = consoleLogSpy.mock.calls[0][0]
|
|
@@ -5,7 +5,7 @@ import { formatOutput } from '@/shared/utils/output'
|
|
|
5
5
|
|
|
6
6
|
import { withKakaoClient } from './shared'
|
|
7
7
|
|
|
8
|
-
async function
|
|
8
|
+
async function whoamiAction(options: { account?: string; pretty?: boolean }): Promise<void> {
|
|
9
9
|
try {
|
|
10
10
|
const profile = await withKakaoClient(options, (client) => client.getProfile())
|
|
11
11
|
console.log(formatOutput(profile, options.pretty))
|
|
@@ -14,8 +14,8 @@ async function profileAction(options: { account?: string; pretty?: boolean }): P
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export const
|
|
18
|
-
.description('Show
|
|
17
|
+
export const whoamiCommand = new Command('whoami')
|
|
18
|
+
.description('Show current authenticated user')
|
|
19
19
|
.option('--account <id>', 'Use a specific KakaoTalk account')
|
|
20
20
|
.option('--pretty', 'Pretty print JSON output')
|
|
21
|
-
.action(
|
|
21
|
+
.action(whoamiAction)
|
|
@@ -34,6 +34,7 @@ export class KakaoCredentialManager {
|
|
|
34
34
|
|
|
35
35
|
async save(config: KakaoConfig): Promise<void> {
|
|
36
36
|
await mkdir(this.configDir, { recursive: true })
|
|
37
|
+
// TODO: Windows does not honor mode 0o600 — consider platform-specific credential storage
|
|
37
38
|
await writeFile(this.credentialsPath, JSON.stringify(config, null, 2))
|
|
38
39
|
await chmod(this.credentialsPath, 0o600)
|
|
39
40
|
}
|
|
@@ -45,7 +45,7 @@ mock.module('./protocol/session', () => ({ LocoSession: MockLocoSession }))
|
|
|
45
45
|
|
|
46
46
|
function createMockClient(overrides: Record<string, unknown> = {}) {
|
|
47
47
|
return {
|
|
48
|
-
getCredentials: mock(() => ({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })),
|
|
48
|
+
getCredentials: mock(() => ({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1', deviceType: 'tablet' as const })),
|
|
49
49
|
...overrides,
|
|
50
50
|
} as any
|
|
51
51
|
}
|
|
@@ -68,7 +68,7 @@ describe('KakaoTalkListener', () => {
|
|
|
68
68
|
await listener.start()
|
|
69
69
|
|
|
70
70
|
expect(mockLogin).toHaveBeenCalledTimes(1)
|
|
71
|
-
expect(mockLogin).toHaveBeenCalledWith('token', 'user1', 'device1')
|
|
71
|
+
expect(mockLogin).toHaveBeenCalledWith('token', 'user1', 'device1', undefined, 'tablet')
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
test('is idempotent', async () => {
|