agent-messenger 2.27.2 → 2.29.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/README.md +11 -1
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/AGENTS.md +4 -0
- package/README.md +58 -10
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/discord/client.d.ts +6 -2
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +10 -3
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +1 -1
- package/dist/src/platforms/imessage/cli.d.ts +5 -0
- package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
- package/dist/src/platforms/imessage/cli.js +31 -0
- package/dist/src/platforms/imessage/cli.js.map +1 -0
- package/dist/src/platforms/imessage/client.d.ts +43 -0
- package/dist/src/platforms/imessage/client.d.ts.map +1 -0
- package/dist/src/platforms/imessage/client.js +190 -0
- package/dist/src/platforms/imessage/client.js.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.js +100 -0
- package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.js +42 -0
- package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.js +101 -0
- package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
- package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
- package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/index.js +7 -0
- package/dist/src/platforms/imessage/commands/index.js.map +1 -0
- package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
- package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/message.js +127 -0
- package/dist/src/platforms/imessage/commands/message.js.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.js +57 -0
- package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.js +53 -0
- package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.js +20 -0
- package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.js +111 -0
- package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.js +13 -0
- package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
- package/dist/src/platforms/imessage/errors.d.ts +3 -0
- package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
- package/dist/src/platforms/imessage/errors.js +25 -0
- package/dist/src/platforms/imessage/errors.js.map +1 -0
- package/dist/src/platforms/imessage/index.d.ts +7 -0
- package/dist/src/platforms/imessage/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/index.js +5 -0
- package/dist/src/platforms/imessage/index.js.map +1 -0
- package/dist/src/platforms/imessage/rpc.d.ts +20 -0
- package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
- package/dist/src/platforms/imessage/rpc.js +133 -0
- package/dist/src/platforms/imessage/rpc.js.map +1 -0
- package/dist/src/platforms/imessage/types.d.ts +59 -0
- package/dist/src/platforms/imessage/types.d.ts.map +1 -0
- package/dist/src/platforms/imessage/types.js +29 -0
- package/dist/src/platforms/imessage/types.js.map +1 -0
- package/dist/src/platforms/instagram/cli.js +1 -1
- package/dist/src/platforms/instagram/client.d.ts +29 -2
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +360 -53
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +123 -11
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts +4 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.js +17 -1
- package/dist/src/platforms/instagram/credential-manager.js.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
- package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
- package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
- package/dist/src/platforms/instagram/index.d.ts +2 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +2 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
- package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
- package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
- package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.js +151 -0
- package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
- package/dist/src/platforms/instagram/types.d.ts +10 -8
- package/dist/src/platforms/instagram/types.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +1 -1
- package/dist/src/platforms/line/cli.js +1 -1
- package/dist/src/platforms/teams/app-config.d.ts +36 -0
- package/dist/src/platforms/teams/app-config.d.ts.map +1 -0
- package/dist/src/platforms/teams/app-config.js +69 -0
- package/dist/src/platforms/teams/app-config.js.map +1 -0
- package/dist/src/platforms/teams/client.d.ts +19 -2
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +295 -7
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +10 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +137 -3
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/file.d.ts +3 -0
- package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/file.js +59 -2
- package/dist/src/platforms/teams/commands/file.js.map +1 -1
- package/dist/src/platforms/teams/commands/message.d.ts +11 -0
- package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/message.js +121 -2
- package/dist/src/platforms/teams/commands/message.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts +23 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/credential-manager.js +46 -2
- package/dist/src/platforms/teams/credential-manager.js.map +1 -1
- package/dist/src/platforms/teams/device-code.d.ts +48 -0
- package/dist/src/platforms/teams/device-code.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-code.js +199 -0
- package/dist/src/platforms/teams/device-code.js.map +1 -0
- package/dist/src/platforms/teams/device-login.d.ts +37 -0
- package/dist/src/platforms/teams/device-login.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-login.js +129 -0
- package/dist/src/platforms/teams/device-login.js.map +1 -0
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +12 -0
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/index.d.ts +7 -3
- package/dist/src/platforms/teams/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/index.js +5 -2
- package/dist/src/platforms/teams/index.js.map +1 -1
- package/dist/src/platforms/teams/listener.d.ts +35 -0
- package/dist/src/platforms/teams/listener.d.ts.map +1 -0
- package/dist/src/platforms/teams/listener.js +236 -0
- package/dist/src/platforms/teams/listener.js.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts +3 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.js +33 -0
- package/dist/src/platforms/teams/realm-discovery.js.map +1 -0
- package/dist/src/platforms/teams/token-extractor.d.ts +3 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +60 -0
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/token-provider.d.ts +25 -0
- package/dist/src/platforms/teams/token-provider.d.ts.map +1 -0
- package/dist/src/platforms/teams/token-provider.js +190 -0
- package/dist/src/platforms/teams/token-provider.js.map +1 -0
- package/dist/src/platforms/teams/trouter.d.ts +31 -0
- package/dist/src/platforms/teams/trouter.d.ts.map +1 -0
- package/dist/src/platforms/teams/trouter.js +181 -0
- package/dist/src/platforms/teams/trouter.js.map +1 -0
- package/dist/src/platforms/teams/types.d.ts +94 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +30 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/platforms/webex/cli.js +1 -1
- package/dist/src/platforms/webex/types.d.ts +1 -1
- package/dist/src/platforms/webexbot/cli.js +1 -1
- package/dist/src/platforms/wechatbot/cli.js +1 -1
- package/dist/src/platforms/whatsapp/cli.js +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +1 -1
- package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
- package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/imessage-adapter.js +110 -0
- package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
- package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/instagram-adapter.js +16 -0
- package/dist/src/tui/adapters/instagram-adapter.js.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.d.ts +3 -0
- package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.js +21 -0
- package/dist/src/tui/adapters/teams-adapter.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +12 -3
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +1 -1
- package/docs/content/docs/cli/imessage.mdx +99 -0
- package/docs/content/docs/cli/instagram.mdx +2 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/teams.mdx +22 -0
- package/docs/content/docs/index.mdx +9 -6
- package/docs/content/docs/quick-start.mdx +2 -0
- package/docs/content/docs/sdk/instagram.mdx +90 -5
- package/docs/content/docs/tui.mdx +4 -3
- package/package.json +10 -2
- package/scripts/postbuild.ts +12 -15
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +133 -0
- package/skills/agent-imessage/references/authentication.md +66 -0
- package/skills/agent-imessage/references/permissions.md +31 -0
- package/skills/agent-imessage/references/setup.md +49 -0
- package/skills/agent-instagram/SKILL.md +72 -2
- package/skills/agent-instagram/references/authentication.md +11 -0
- package/skills/agent-instagram/references/common-patterns.md +36 -0
- package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +59 -6
- package/skills/agent-teams/references/authentication.md +31 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/discord/client.test.ts +86 -0
- package/src/platforms/discord/client.ts +10 -3
- package/src/platforms/imessage/cli.ts +39 -0
- package/src/platforms/imessage/client.test.ts +137 -0
- package/src/platforms/imessage/client.ts +253 -0
- package/src/platforms/imessage/commands/auth.ts +142 -0
- package/src/platforms/imessage/commands/chat.ts +51 -0
- package/src/platforms/imessage/commands/doctor.test.ts +80 -0
- package/src/platforms/imessage/commands/doctor.ts +139 -0
- package/src/platforms/imessage/commands/index.ts +6 -0
- package/src/platforms/imessage/commands/message.test.ts +20 -0
- package/src/platforms/imessage/commands/message.ts +151 -0
- package/src/platforms/imessage/commands/setup.test.ts +52 -0
- package/src/platforms/imessage/commands/setup.ts +76 -0
- package/src/platforms/imessage/commands/shared.test.ts +19 -0
- package/src/platforms/imessage/commands/shared.ts +69 -0
- package/src/platforms/imessage/commands/whoami.ts +22 -0
- package/src/platforms/imessage/credential-manager.test.ts +122 -0
- package/src/platforms/imessage/credential-manager.ts +123 -0
- package/src/platforms/imessage/ensure-auth.ts +18 -0
- package/src/platforms/imessage/errors.ts +27 -0
- package/src/platforms/imessage/index.test.ts +13 -0
- package/src/platforms/imessage/index.ts +16 -0
- package/src/platforms/imessage/rpc.ts +157 -0
- package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
- package/src/platforms/imessage/types.test.ts +47 -0
- package/src/platforms/imessage/types.ts +95 -0
- package/src/platforms/instagram/client.test.ts +478 -33
- package/src/platforms/instagram/client.ts +472 -68
- package/src/platforms/instagram/commands/auth.test.ts +177 -1
- package/src/platforms/instagram/commands/auth.ts +182 -11
- package/src/platforms/instagram/credential-manager.test.ts +30 -0
- package/src/platforms/instagram/credential-manager.ts +23 -1
- package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
- package/src/platforms/instagram/hybrid-listener.ts +196 -0
- package/src/platforms/instagram/index.ts +10 -0
- package/src/platforms/instagram/mqtt/connection.ts +72 -0
- package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
- package/src/platforms/instagram/mqtt/thrift.ts +159 -0
- package/src/platforms/instagram/mqtt/transport.ts +285 -0
- package/src/platforms/instagram/realtime-listener.test.ts +34 -0
- package/src/platforms/instagram/realtime-listener.ts +201 -0
- package/src/platforms/instagram/types.ts +11 -8
- package/src/platforms/teams/app-config.ts +92 -0
- package/src/platforms/teams/client.test.ts +335 -1
- package/src/platforms/teams/client.ts +342 -7
- package/src/platforms/teams/commands/auth.test.ts +93 -1
- package/src/platforms/teams/commands/auth.ts +182 -3
- package/src/platforms/teams/commands/file.test.ts +92 -1
- package/src/platforms/teams/commands/file.ts +76 -1
- package/src/platforms/teams/commands/message.test.ts +95 -1
- package/src/platforms/teams/commands/message.ts +145 -3
- package/src/platforms/teams/credential-manager.test.ts +20 -0
- package/src/platforms/teams/credential-manager.ts +68 -3
- package/src/platforms/teams/device-code.test.ts +311 -0
- package/src/platforms/teams/device-code.ts +277 -0
- package/src/platforms/teams/device-login.test.ts +288 -0
- package/src/platforms/teams/device-login.ts +206 -0
- package/src/platforms/teams/ensure-auth.ts +11 -0
- package/src/platforms/teams/index.test.ts +10 -0
- package/src/platforms/teams/index.ts +16 -1
- package/src/platforms/teams/listener.ts +295 -0
- package/src/platforms/teams/realm-discovery.test.ts +73 -0
- package/src/platforms/teams/realm-discovery.ts +42 -0
- package/src/platforms/teams/token-extractor.ts +69 -0
- package/src/platforms/teams/token-provider.test.ts +227 -0
- package/src/platforms/teams/token-provider.ts +284 -0
- package/src/platforms/teams/trouter.test.ts +114 -0
- package/src/platforms/teams/trouter.ts +228 -0
- package/src/platforms/teams/types.test.ts +17 -0
- package/src/platforms/teams/types.ts +86 -0
- package/src/platforms/webexbot/cli.ts +0 -0
- package/src/tui/adapters/imessage-adapter.ts +131 -0
- package/src/tui/adapters/instagram-adapter.ts +13 -0
- package/src/tui/adapters/teams-adapter.ts +23 -0
- package/src/tui/app.ts +16 -3
|
@@ -2,14 +2,178 @@ import { Command } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import { collectBrowserProfileOption } from '@/shared/chromium'
|
|
4
4
|
import { handleError } from '@/shared/utils/error-handler'
|
|
5
|
+
import { isInteractive } from '@/shared/utils/interactive'
|
|
5
6
|
import { formatOutput } from '@/shared/utils/output'
|
|
6
|
-
import { debug } from '@/shared/utils/stderr'
|
|
7
|
+
import { debug, info } from '@/shared/utils/stderr'
|
|
7
8
|
|
|
8
9
|
import { TeamsClient } from '../client'
|
|
9
10
|
import { TeamsCredentialManager } from '../credential-manager'
|
|
11
|
+
import { completeDeviceCode, loginWithDeviceCode, PendingApprovalError, startDeviceCode } from '../device-login'
|
|
12
|
+
import { probeAccountType } from '../realm-discovery'
|
|
10
13
|
import { TeamsTokenExtractor } from '../token-extractor'
|
|
11
14
|
import type { TeamsAccount, TeamsAccountType, TeamsConfig } from '../types'
|
|
12
15
|
|
|
16
|
+
interface LoginOptions {
|
|
17
|
+
pretty?: boolean
|
|
18
|
+
debug?: boolean
|
|
19
|
+
deviceCode?: string
|
|
20
|
+
clientId?: string
|
|
21
|
+
accountType?: string
|
|
22
|
+
email?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function loginAction(options: LoginOptions): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const explicitAccountType = resolveLoginAccountType(options.accountType)
|
|
28
|
+
if (options.deviceCode) {
|
|
29
|
+
await finishDeviceCode(options, explicitAccountType)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
if (!isInteractive()) {
|
|
33
|
+
const accountType = options.email
|
|
34
|
+
? await resolveAccountTypeFromEmail(options, explicitAccountType)
|
|
35
|
+
: explicitAccountType
|
|
36
|
+
await startNonInteractiveLogin(options.clientId, accountType)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
|
|
41
|
+
const accountType = await resolveAccountTypeFromEmail(options, explicitAccountType)
|
|
42
|
+
const result = await loginWithDeviceCode({
|
|
43
|
+
clientId: options.clientId,
|
|
44
|
+
accountType,
|
|
45
|
+
onCode: async ({ verificationUri, userCode }) => {
|
|
46
|
+
info(`\nTo sign in, open ${verificationUri} and enter code ${userCode}\n`)
|
|
47
|
+
info(`Waiting for approval of code ${userCode}...`)
|
|
48
|
+
},
|
|
49
|
+
onPending: () => info('Approved. Finishing sign-in...'),
|
|
50
|
+
debug: debugLog,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
console.log(
|
|
54
|
+
formatOutput(
|
|
55
|
+
{
|
|
56
|
+
authenticated: true,
|
|
57
|
+
account_type: result.accountType,
|
|
58
|
+
user: result.userName,
|
|
59
|
+
teams: result.teams.map((t) => `${t.id}/${t.name}`),
|
|
60
|
+
current: result.current,
|
|
61
|
+
},
|
|
62
|
+
options.pretty,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
handleError(error as Error)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveLoginAccountType(value: string | undefined): TeamsAccountType {
|
|
71
|
+
if (value === undefined) return 'work'
|
|
72
|
+
if (value === 'work' || value === 'personal') return value
|
|
73
|
+
throw new Error('Invalid account type. Use "work" or "personal".')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// When the user did not force --account-type, look the account up by email so a
|
|
77
|
+
// personal account is routed to the personal flow up front instead of failing
|
|
78
|
+
// after approval. Any misstep falls back to the caller's default account type.
|
|
79
|
+
async function resolveAccountTypeFromEmail(
|
|
80
|
+
options: LoginOptions,
|
|
81
|
+
fallback: TeamsAccountType,
|
|
82
|
+
): Promise<TeamsAccountType> {
|
|
83
|
+
if (options.accountType !== undefined) return fallback
|
|
84
|
+
|
|
85
|
+
const email = options.email ?? (isInteractive() ? await promptEmail() : undefined)
|
|
86
|
+
if (!email) return fallback
|
|
87
|
+
|
|
88
|
+
const probed = await probeAccountType(email)
|
|
89
|
+
if (!probed) return fallback
|
|
90
|
+
if (probed === 'personal') {
|
|
91
|
+
info('Detected a personal Microsoft account. Using personal sign-in.')
|
|
92
|
+
}
|
|
93
|
+
return probed
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function promptEmail(): Promise<string | undefined> {
|
|
97
|
+
const { createInterface } = await import('node:readline/promises')
|
|
98
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true })
|
|
99
|
+
try {
|
|
100
|
+
const answer = await rl.question('Microsoft email (press Enter to skip): ')
|
|
101
|
+
return answer.trim() || undefined
|
|
102
|
+
} finally {
|
|
103
|
+
rl.close()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function startNonInteractiveLogin(
|
|
108
|
+
clientIdOverride: string | undefined,
|
|
109
|
+
accountType: TeamsAccountType,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const { info: device, clientId } = await startDeviceCode(clientIdOverride, accountType)
|
|
112
|
+
const clientIdFlag = clientIdOverride ? ` --client-id ${clientIdOverride}` : ''
|
|
113
|
+
const accountTypeFlag = accountType === 'personal' ? ' --account-type personal' : ''
|
|
114
|
+
console.log(
|
|
115
|
+
formatOutput({
|
|
116
|
+
next_action: 'authorize',
|
|
117
|
+
account_type: accountType,
|
|
118
|
+
verification_uri: device.verificationUri,
|
|
119
|
+
verification_uri_complete: device.verificationUriComplete,
|
|
120
|
+
user_code: device.userCode,
|
|
121
|
+
device_code: device.deviceCode,
|
|
122
|
+
client_id: clientId,
|
|
123
|
+
expires_at: Date.now() + device.expiresIn * 1000,
|
|
124
|
+
message: `Show the user \`verification_uri\` and \`user_code\` (or \`verification_uri_complete\`) and ask them to approve in a browser. After they approve, run \`agent-teams auth login --device-code <device_code>${clientIdFlag}${accountTypeFlag}\` to finish.`,
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
process.exit(0)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function finishDeviceCode(options: LoginOptions, accountType: TeamsAccountType): Promise<void> {
|
|
131
|
+
const { getTeamsAppClientId } = await import('../app-config')
|
|
132
|
+
const clientId = options.clientId ?? getTeamsAppClientId(accountType).clientId
|
|
133
|
+
try {
|
|
134
|
+
const result = await completeDeviceCode(options.deviceCode!, clientId, undefined, accountType)
|
|
135
|
+
console.log(
|
|
136
|
+
formatOutput(
|
|
137
|
+
{
|
|
138
|
+
authenticated: true,
|
|
139
|
+
account_type: result.accountType,
|
|
140
|
+
user: result.userName,
|
|
141
|
+
teams: result.teams.map((t) => `${t.id}/${t.name}`),
|
|
142
|
+
current: result.current,
|
|
143
|
+
},
|
|
144
|
+
options.pretty,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error instanceof PendingApprovalError) {
|
|
149
|
+
const clientIdFlag = options.clientId ? ` --client-id ${options.clientId}` : ''
|
|
150
|
+
const accountTypeFlag = options.accountType ? ` --account-type ${accountType}` : ''
|
|
151
|
+
console.log(
|
|
152
|
+
formatOutput(
|
|
153
|
+
{
|
|
154
|
+
next_action: 'still_pending',
|
|
155
|
+
device_code: options.deviceCode,
|
|
156
|
+
message: `User has not approved yet. Confirm they completed authorization in the browser, then run \`agent-teams auth login --device-code <device_code>${clientIdFlag}${accountTypeFlag}\` again.`,
|
|
157
|
+
},
|
|
158
|
+
options.pretty,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
process.exit(0)
|
|
162
|
+
}
|
|
163
|
+
console.log(
|
|
164
|
+
formatOutput(
|
|
165
|
+
{
|
|
166
|
+
next_action: 'restart',
|
|
167
|
+
error: (error as Error).message,
|
|
168
|
+
message: 'This device code is no longer valid. Run `agent-teams auth login` to start over.',
|
|
169
|
+
},
|
|
170
|
+
options.pretty,
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
process.exit(1)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
13
177
|
interface ValidatedTeamsToken {
|
|
14
178
|
client: TeamsClient
|
|
15
179
|
accountType: TeamsAccountType
|
|
@@ -308,7 +472,9 @@ export async function logoutAction(options: { pretty?: boolean }): Promise<void>
|
|
|
308
472
|
const config = await credManager.loadConfig()
|
|
309
473
|
|
|
310
474
|
if (!config || Object.keys(config.accounts).length === 0) {
|
|
311
|
-
console.log(
|
|
475
|
+
console.log(
|
|
476
|
+
formatOutput({ error: 'Not authenticated. Run "auth login" or "auth extract" first.' }, options.pretty),
|
|
477
|
+
)
|
|
312
478
|
process.exit(1)
|
|
313
479
|
}
|
|
314
480
|
|
|
@@ -326,7 +492,9 @@ export async function statusAction(options: { pretty?: boolean }): Promise<void>
|
|
|
326
492
|
const config = await credManager.loadConfig()
|
|
327
493
|
|
|
328
494
|
if (!config || Object.keys(config.accounts).length === 0) {
|
|
329
|
-
console.log(
|
|
495
|
+
console.log(
|
|
496
|
+
formatOutput({ error: 'Not authenticated. Run "auth login" or "auth extract" first.' }, options.pretty),
|
|
497
|
+
)
|
|
330
498
|
process.exit(1)
|
|
331
499
|
}
|
|
332
500
|
|
|
@@ -427,6 +595,17 @@ export async function switchAccountAction(accountType: string, options: { pretty
|
|
|
427
595
|
|
|
428
596
|
export const authCommand = new Command('auth')
|
|
429
597
|
.description('Authentication commands')
|
|
598
|
+
.addCommand(
|
|
599
|
+
new Command('login')
|
|
600
|
+
.description('Sign in via device code (no desktop app needed). Detects the account type from your email.')
|
|
601
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
602
|
+
.option('--debug', 'Show debug output for troubleshooting')
|
|
603
|
+
.option('--device-code <code>', 'Finish a non-interactive login started earlier')
|
|
604
|
+
.option('--client-id <id>', 'Override the AAD client ID')
|
|
605
|
+
.option('--account-type <type>', 'Force account type: work or personal (skips email detection)')
|
|
606
|
+
.option('--email <email>', 'Microsoft email, used to auto-detect work vs personal')
|
|
607
|
+
.action(loginAction),
|
|
608
|
+
)
|
|
430
609
|
.addCommand(
|
|
431
610
|
new Command('extract')
|
|
432
611
|
.description('Extract token from Microsoft Teams desktop app or a supported Chromium browser')
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { afterEach, beforeEach, expect, mock, spyOn, it } from 'bun:test'
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
2
5
|
|
|
3
6
|
import { TeamsClient } from '../client'
|
|
4
7
|
import { TeamsCredentialManager } from '../credential-manager'
|
|
5
|
-
import {
|
|
8
|
+
import { TeamsAuthCapabilityError } from '../types'
|
|
9
|
+
import { downloadAction, infoAction, listAction, uploadAction } from './file'
|
|
6
10
|
|
|
7
11
|
let clientUploadFileSpy: ReturnType<typeof spyOn>
|
|
8
12
|
let clientListFilesSpy: ReturnType<typeof spyOn>
|
|
13
|
+
let clientDownloadFileSpy: ReturnType<typeof spyOn>
|
|
9
14
|
let credManagerLoadConfigSpy: ReturnType<typeof spyOn>
|
|
15
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
16
|
+
const tempDirs: string[] = []
|
|
10
17
|
const originalConsoleLog = console.log
|
|
18
|
+
const originalConsoleError = console.error
|
|
19
|
+
const originalCwd = process.cwd()
|
|
11
20
|
|
|
12
21
|
beforeEach(() => {
|
|
13
22
|
clientUploadFileSpy = spyOn(TeamsClient.prototype, 'uploadFile').mockResolvedValue({
|
|
@@ -35,6 +44,17 @@ beforeEach(() => {
|
|
|
35
44
|
},
|
|
36
45
|
])
|
|
37
46
|
|
|
47
|
+
clientDownloadFileSpy = spyOn(TeamsClient.prototype, 'downloadFile').mockResolvedValue({
|
|
48
|
+
buffer: Buffer.from('downloaded-content'),
|
|
49
|
+
file: {
|
|
50
|
+
id: 'file_123',
|
|
51
|
+
name: 'folder\\test.pdf',
|
|
52
|
+
size: 18,
|
|
53
|
+
url: 'https://teams.microsoft.com/files/123/test.pdf',
|
|
54
|
+
contentType: 'application/pdf',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
38
58
|
credManagerLoadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue({
|
|
39
59
|
current_account: 'work',
|
|
40
60
|
accounts: {
|
|
@@ -46,13 +66,24 @@ beforeEach(() => {
|
|
|
46
66
|
},
|
|
47
67
|
},
|
|
48
68
|
})
|
|
69
|
+
|
|
70
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((code?: string | number | null | undefined): never => {
|
|
71
|
+
throw new Error(`process.exit:${code ?? 0}`)
|
|
72
|
+
})
|
|
49
73
|
})
|
|
50
74
|
|
|
51
75
|
afterEach(() => {
|
|
52
76
|
clientUploadFileSpy?.mockRestore()
|
|
53
77
|
clientListFilesSpy?.mockRestore()
|
|
78
|
+
clientDownloadFileSpy?.mockRestore()
|
|
54
79
|
credManagerLoadConfigSpy?.mockRestore()
|
|
80
|
+
processExitSpy?.mockRestore()
|
|
55
81
|
console.log = originalConsoleLog
|
|
82
|
+
console.error = originalConsoleError
|
|
83
|
+
process.chdir(originalCwd)
|
|
84
|
+
for (const dir of tempDirs.splice(0)) {
|
|
85
|
+
rmSync(dir, { recursive: true, force: true })
|
|
86
|
+
}
|
|
56
87
|
})
|
|
57
88
|
|
|
58
89
|
it('upload: sends multipart request and returns file info', async () => {
|
|
@@ -90,3 +121,63 @@ it('info: returns single file details', async () => {
|
|
|
90
121
|
expect(output).toContain('file_123')
|
|
91
122
|
expect(output).toContain('test.pdf')
|
|
92
123
|
})
|
|
124
|
+
|
|
125
|
+
it('download: writes to a directory using a safe basename', async () => {
|
|
126
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
127
|
+
console.log = consoleSpy
|
|
128
|
+
const dir = mkdtempSync(join(tmpdir(), 'teams-download-dir-'))
|
|
129
|
+
tempDirs.push(dir)
|
|
130
|
+
|
|
131
|
+
await downloadAction('team_123', 'ch_456', 'file_123', dir, { pretty: false })
|
|
132
|
+
|
|
133
|
+
const downloadedPath = join(dir, 'test.pdf')
|
|
134
|
+
expect(readFileSync(downloadedPath, 'utf8')).toBe('downloaded-content')
|
|
135
|
+
expect(clientDownloadFileSpy).toHaveBeenCalledWith('team_123', 'ch_456', 'file_123')
|
|
136
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
137
|
+
expect(output).toContain(downloadedPath)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('download: writes to an explicit output file path', async () => {
|
|
141
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
142
|
+
console.log = consoleSpy
|
|
143
|
+
const dir = mkdtempSync(join(tmpdir(), 'teams-download-file-'))
|
|
144
|
+
tempDirs.push(dir)
|
|
145
|
+
const outputPath = join(dir, 'renamed.pdf')
|
|
146
|
+
|
|
147
|
+
await downloadAction('team_123', 'ch_456', 'file_123', outputPath, { pretty: false })
|
|
148
|
+
|
|
149
|
+
expect(readFileSync(outputPath, 'utf8')).toBe('downloaded-content')
|
|
150
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
151
|
+
expect(output).toContain(outputPath)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('download: writes to cwd when output path is omitted', async () => {
|
|
155
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
156
|
+
console.log = consoleSpy
|
|
157
|
+
const dir = mkdtempSync(join(tmpdir(), 'teams-download-cwd-'))
|
|
158
|
+
tempDirs.push(dir)
|
|
159
|
+
process.chdir(dir)
|
|
160
|
+
|
|
161
|
+
await downloadAction('team_123', 'ch_456', 'file_123', undefined, { pretty: false })
|
|
162
|
+
|
|
163
|
+
const downloadedPath = join(dir, 'test.pdf')
|
|
164
|
+
expect(readFileSync(downloadedPath, 'utf8')).toBe('downloaded-content')
|
|
165
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
166
|
+
expect(output).toContain(downloadedPath)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('download: exits on capability error without writing a partial file', async () => {
|
|
170
|
+
const consoleErrorSpy = mock((_msg: string) => {})
|
|
171
|
+
console.error = consoleErrorSpy
|
|
172
|
+
const dir = mkdtempSync(join(tmpdir(), 'teams-download-capability-'))
|
|
173
|
+
tempDirs.push(dir)
|
|
174
|
+
const outputPath = join(dir, 'sharepoint.docx')
|
|
175
|
+
clientDownloadFileSpy.mockRejectedValue(new TeamsAuthCapabilityError())
|
|
176
|
+
|
|
177
|
+
await expect(downloadAction('team_123', 'ch_456', 'file_123', outputPath, { pretty: false })).rejects.toThrow(
|
|
178
|
+
'process.exit:1',
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
expect(existsSync(outputPath)).toBe(false)
|
|
182
|
+
expect(consoleErrorSpy.mock.calls[0][0]).toContain('Requires `agent-teams auth login`')
|
|
183
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { statSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { basename, join, resolve } from 'node:path'
|
|
2
3
|
|
|
3
4
|
import { Command } from 'commander'
|
|
4
5
|
|
|
@@ -8,6 +9,7 @@ import { formatOutput } from '@/shared/utils/output'
|
|
|
8
9
|
import { TeamsClient } from '../client'
|
|
9
10
|
import { TeamsCredentialManager } from '../credential-manager'
|
|
10
11
|
import type { TeamsFile } from '../types'
|
|
12
|
+
import { TeamsAuthCapabilityError } from '../types'
|
|
11
13
|
|
|
12
14
|
export async function uploadAction(
|
|
13
15
|
teamId: string,
|
|
@@ -122,6 +124,69 @@ export async function infoAction(
|
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
export async function downloadAction(
|
|
128
|
+
teamId: string,
|
|
129
|
+
channelId: string,
|
|
130
|
+
fileId: string,
|
|
131
|
+
outputPath: string | undefined,
|
|
132
|
+
options: { pretty?: boolean },
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
try {
|
|
135
|
+
const credManager = new TeamsCredentialManager()
|
|
136
|
+
const cred = await credManager.getTokenWithExpiry()
|
|
137
|
+
|
|
138
|
+
if (!cred) {
|
|
139
|
+
console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
|
|
140
|
+
process.exit(1)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const client = await new TeamsClient().login({
|
|
144
|
+
token: cred.token,
|
|
145
|
+
tokenExpiresAt: cred.tokenExpiresAt,
|
|
146
|
+
accountType: cred.accountType,
|
|
147
|
+
region: cred.region,
|
|
148
|
+
})
|
|
149
|
+
const { buffer, file } = await client.downloadFile(teamId, channelId, fileId)
|
|
150
|
+
|
|
151
|
+
const safeName = basename(file.name.replace(/\\/g, '/'))
|
|
152
|
+
let destPath = outputPath ? resolve(outputPath) : resolve(safeName)
|
|
153
|
+
let isDirectory = false
|
|
154
|
+
try {
|
|
155
|
+
isDirectory = statSync(destPath).isDirectory()
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const err = error as NodeJS.ErrnoException
|
|
158
|
+
if (err.code !== 'ENOENT') {
|
|
159
|
+
throw error
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (isDirectory) {
|
|
164
|
+
destPath = join(destPath, safeName)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
writeFileSync(destPath, buffer)
|
|
168
|
+
|
|
169
|
+
console.log(
|
|
170
|
+
formatOutput(
|
|
171
|
+
{
|
|
172
|
+
id: file.id,
|
|
173
|
+
name: file.name,
|
|
174
|
+
size: file.size,
|
|
175
|
+
content_type: file.contentType || null,
|
|
176
|
+
path: destPath,
|
|
177
|
+
},
|
|
178
|
+
options.pretty,
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof TeamsAuthCapabilityError) {
|
|
183
|
+
console.error(error.message)
|
|
184
|
+
process.exit(1)
|
|
185
|
+
}
|
|
186
|
+
handleError(error as Error)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
125
190
|
export const fileCommand = new Command('file')
|
|
126
191
|
.description('File commands')
|
|
127
192
|
.addCommand(
|
|
@@ -150,3 +215,13 @@ export const fileCommand = new Command('file')
|
|
|
150
215
|
.option('--pretty', 'Pretty print JSON output')
|
|
151
216
|
.action(infoAction),
|
|
152
217
|
)
|
|
218
|
+
.addCommand(
|
|
219
|
+
new Command('download')
|
|
220
|
+
.description('Download file from channel')
|
|
221
|
+
.argument('<team-id>', 'Team ID')
|
|
222
|
+
.argument('<channel-id>', 'Channel ID')
|
|
223
|
+
.argument('<file-id>', 'File ID')
|
|
224
|
+
.argument('[output-path]', 'Output file or directory path')
|
|
225
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
226
|
+
.action(downloadAction),
|
|
227
|
+
)
|
|
@@ -2,14 +2,17 @@ import { afterEach, beforeEach, expect, mock, spyOn, it } from 'bun:test'
|
|
|
2
2
|
|
|
3
3
|
import { TeamsClient } from '../client'
|
|
4
4
|
import { TeamsCredentialManager } from '../credential-manager'
|
|
5
|
-
import { deleteAction, getAction, listAction, sendAction } from './message'
|
|
5
|
+
import { deleteAction, getAction, listAction, repliesAction, searchAction, sendAction, strictParseInt } from './message'
|
|
6
6
|
|
|
7
7
|
let clientSendMessageSpy: ReturnType<typeof spyOn>
|
|
8
8
|
let clientGetMessagesSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let clientGetThreadRepliesSpy: ReturnType<typeof spyOn>
|
|
9
10
|
let clientGetMessageSpy: ReturnType<typeof spyOn>
|
|
10
11
|
let clientDeleteMessageSpy: ReturnType<typeof spyOn>
|
|
12
|
+
let clientSearchMessagesSpy: ReturnType<typeof spyOn>
|
|
11
13
|
let credManagerLoadSpy: ReturnType<typeof spyOn>
|
|
12
14
|
const originalConsoleLog = console.log
|
|
15
|
+
const originalConsoleError = console.error
|
|
13
16
|
|
|
14
17
|
beforeEach(() => {
|
|
15
18
|
clientSendMessageSpy = spyOn(TeamsClient.prototype, 'sendMessage').mockResolvedValue({
|
|
@@ -37,6 +40,18 @@ beforeEach(() => {
|
|
|
37
40
|
},
|
|
38
41
|
])
|
|
39
42
|
|
|
43
|
+
clientGetThreadRepliesSpy = spyOn(TeamsClient.prototype, 'getThreadReplies').mockResolvedValue([
|
|
44
|
+
{
|
|
45
|
+
id: 'reply_123',
|
|
46
|
+
channel_id: 'ch_456',
|
|
47
|
+
author: { id: 'user_789', displayName: 'Test User' },
|
|
48
|
+
content: 'Thread reply',
|
|
49
|
+
timestamp: '2025-01-29T10:02:00Z',
|
|
50
|
+
root_message_id: 'msg_123',
|
|
51
|
+
is_thread_reply: true,
|
|
52
|
+
},
|
|
53
|
+
])
|
|
54
|
+
|
|
40
55
|
clientGetMessageSpy = spyOn(TeamsClient.prototype, 'getMessage').mockResolvedValue({
|
|
41
56
|
id: 'msg_123',
|
|
42
57
|
channel_id: 'ch_456',
|
|
@@ -47,6 +62,20 @@ beforeEach(() => {
|
|
|
47
62
|
|
|
48
63
|
clientDeleteMessageSpy = spyOn(TeamsClient.prototype, 'deleteMessage').mockResolvedValue(undefined)
|
|
49
64
|
|
|
65
|
+
clientSearchMessagesSpy = spyOn(TeamsClient.prototype, 'searchMessages').mockResolvedValue([
|
|
66
|
+
{
|
|
67
|
+
id: 'search_123',
|
|
68
|
+
content: 'Deploy complete',
|
|
69
|
+
author: { id: 'user_789', displayName: 'Test User' },
|
|
70
|
+
channel_id: 'ch_456',
|
|
71
|
+
thread_id: 'thread_123',
|
|
72
|
+
team_name: 'Test Team',
|
|
73
|
+
channel_name: 'General',
|
|
74
|
+
timestamp: '2025-01-29T10:03:00Z',
|
|
75
|
+
permalink: 'https://teams.microsoft.com/l/message/search_123',
|
|
76
|
+
},
|
|
77
|
+
])
|
|
78
|
+
|
|
50
79
|
credManagerLoadSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue({
|
|
51
80
|
current_account: 'work',
|
|
52
81
|
accounts: {
|
|
@@ -63,10 +92,13 @@ beforeEach(() => {
|
|
|
63
92
|
afterEach(() => {
|
|
64
93
|
clientSendMessageSpy?.mockRestore()
|
|
65
94
|
clientGetMessagesSpy?.mockRestore()
|
|
95
|
+
clientGetThreadRepliesSpy?.mockRestore()
|
|
66
96
|
clientGetMessageSpy?.mockRestore()
|
|
67
97
|
clientDeleteMessageSpy?.mockRestore()
|
|
98
|
+
clientSearchMessagesSpy?.mockRestore()
|
|
68
99
|
credManagerLoadSpy?.mockRestore()
|
|
69
100
|
console.log = originalConsoleLog
|
|
101
|
+
console.error = originalConsoleError
|
|
70
102
|
})
|
|
71
103
|
|
|
72
104
|
it('send: returns message with id', async () => {
|
|
@@ -92,6 +124,68 @@ it('list: returns array of messages', async () => {
|
|
|
92
124
|
expect(output).toContain('msg_124')
|
|
93
125
|
})
|
|
94
126
|
|
|
127
|
+
it('replies: returns array of thread replies', async () => {
|
|
128
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
129
|
+
console.log = consoleSpy
|
|
130
|
+
|
|
131
|
+
await repliesAction('team_123', 'ch_456', 'msg_123', { limit: 10, pretty: false })
|
|
132
|
+
|
|
133
|
+
expect(clientGetThreadRepliesSpy).toHaveBeenCalledWith('team_123', 'ch_456', 'msg_123', 10)
|
|
134
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
135
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
136
|
+
expect(output).toContain('reply_123')
|
|
137
|
+
expect(output).toContain('msg_123')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('search: returns search results and passes pagination options', async () => {
|
|
141
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
142
|
+
console.log = consoleSpy
|
|
143
|
+
|
|
144
|
+
await searchAction('deploy', { limit: 10, from: 5, pretty: false })
|
|
145
|
+
|
|
146
|
+
expect(clientSearchMessagesSpy).toHaveBeenCalledWith('deploy', { limit: 10, from: 5 })
|
|
147
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
148
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
149
|
+
expect(output).toContain('search_123')
|
|
150
|
+
expect(output).toContain('Deploy complete')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it.each([
|
|
154
|
+
['abc', Number.NaN],
|
|
155
|
+
['1.5', Number.NaN],
|
|
156
|
+
['1abc', Number.NaN],
|
|
157
|
+
['', Number.NaN],
|
|
158
|
+
['10', 10],
|
|
159
|
+
[' 10 ', 10],
|
|
160
|
+
['-1', -1],
|
|
161
|
+
])('strictParseInt(%p) -> %p (rejects truncatable strings)', (input, expected) => {
|
|
162
|
+
const result = strictParseInt(input)
|
|
163
|
+
if (Number.isNaN(expected)) {
|
|
164
|
+
expect(Number.isNaN(result)).toBe(true)
|
|
165
|
+
} else {
|
|
166
|
+
expect(result).toBe(expected as number)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it.each([
|
|
171
|
+
['--limit abc', { limit: Number.NaN, from: 0 }],
|
|
172
|
+
['--limit 0', { limit: 0, from: 0 }],
|
|
173
|
+
['--limit -1', { limit: -1, from: 0 }],
|
|
174
|
+
['--from -1', { limit: 20, from: -1 }],
|
|
175
|
+
])('search: rejects invalid pagination %s', async (_label, options) => {
|
|
176
|
+
const consoleErrorSpy = mock((_msg: string) => {})
|
|
177
|
+
console.error = consoleErrorSpy
|
|
178
|
+
const exitSpy = spyOn(process, 'exit').mockImplementation((code?: string | number | null) => {
|
|
179
|
+
throw new Error(`exit:${code}`)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
await expect(searchAction('deploy', { ...options, pretty: false })).rejects.toThrow('exit:1')
|
|
183
|
+
|
|
184
|
+
expect(consoleErrorSpy).toHaveBeenCalled()
|
|
185
|
+
expect(clientSearchMessagesSpy).not.toHaveBeenCalled()
|
|
186
|
+
exitSpy.mockRestore()
|
|
187
|
+
})
|
|
188
|
+
|
|
95
189
|
it('get: returns single message', async () => {
|
|
96
190
|
const consoleSpy = mock((_msg: string) => {})
|
|
97
191
|
console.log = consoleSpy
|