agent-messenger 2.23.6 → 2.24.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.
Files changed (56) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +12 -1
  3. package/bun.lock +10 -1
  4. package/dist/package.json +4 -1
  5. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
  6. package/dist/src/platforms/slack/commands/auth.js +56 -0
  7. package/dist/src/platforms/slack/commands/auth.js.map +1 -1
  8. package/dist/src/platforms/slack/ensure-auth.d.ts +1 -1
  9. package/dist/src/platforms/slack/ensure-auth.d.ts.map +1 -1
  10. package/dist/src/platforms/slack/ensure-auth.js +2 -2
  11. package/dist/src/platforms/slack/ensure-auth.js.map +1 -1
  12. package/dist/src/platforms/slack/index.d.ts +4 -0
  13. package/dist/src/platforms/slack/index.d.ts.map +1 -1
  14. package/dist/src/platforms/slack/index.js +2 -0
  15. package/dist/src/platforms/slack/index.js.map +1 -1
  16. package/dist/src/platforms/slack/qr-http-login.d.ts +14 -0
  17. package/dist/src/platforms/slack/qr-http-login.d.ts.map +1 -0
  18. package/dist/src/platforms/slack/qr-http-login.js +90 -0
  19. package/dist/src/platforms/slack/qr-http-login.js.map +1 -0
  20. package/dist/src/platforms/slack/qr-login.d.ts +10 -0
  21. package/dist/src/platforms/slack/qr-login.d.ts.map +1 -0
  22. package/dist/src/platforms/slack/qr-login.js +72 -0
  23. package/dist/src/platforms/slack/qr-login.js.map +1 -0
  24. package/dist/src/vendor/linejs/base/request/mod.js +1 -1
  25. package/dist/src/vendor/linejs/base/request/mod.test.ts +54 -0
  26. package/docs/content/docs/cli/slack.mdx +22 -0
  27. package/docs/content/docs/sdk/slack.mdx +15 -0
  28. package/package.json +4 -1
  29. package/skills/agent-channeltalk/SKILL.md +1 -1
  30. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  31. package/skills/agent-discord/SKILL.md +1 -1
  32. package/skills/agent-discordbot/SKILL.md +1 -1
  33. package/skills/agent-instagram/SKILL.md +1 -1
  34. package/skills/agent-kakaotalk/SKILL.md +1 -1
  35. package/skills/agent-line/SKILL.md +1 -1
  36. package/skills/agent-slack/SKILL.md +45 -1
  37. package/skills/agent-slack/references/authentication.md +29 -0
  38. package/skills/agent-slackbot/SKILL.md +1 -1
  39. package/skills/agent-teams/SKILL.md +1 -1
  40. package/skills/agent-telegram/SKILL.md +1 -1
  41. package/skills/agent-telegrambot/SKILL.md +1 -1
  42. package/skills/agent-webex/SKILL.md +1 -1
  43. package/skills/agent-webexbot/SKILL.md +1 -1
  44. package/skills/agent-wechatbot/SKILL.md +1 -1
  45. package/skills/agent-whatsapp/SKILL.md +1 -1
  46. package/skills/agent-whatsappbot/SKILL.md +1 -1
  47. package/src/platforms/slack/commands/auth.ts +73 -0
  48. package/src/platforms/slack/ensure-auth.ts +6 -2
  49. package/src/platforms/slack/index.test.ts +10 -0
  50. package/src/platforms/slack/index.ts +4 -0
  51. package/src/platforms/slack/qr-http-login.test.ts +157 -0
  52. package/src/platforms/slack/qr-http-login.ts +120 -0
  53. package/src/platforms/slack/qr-login.test.ts +103 -0
  54. package/src/platforms/slack/qr-login.ts +90 -0
  55. package/src/vendor/linejs/base/request/mod.js +1 -1
  56. package/src/vendor/linejs/base/request/mod.test.ts +54 -0
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+
3
+ import { InternalError } from '../core/mod.js'
4
+ import { RequestClient } from './mod.js'
5
+
6
+ // Regression: a thrift error response can set hasError (empty res.data[0]) while
7
+ // omitting the exception struct (res.data[1] absent), leaving res.data.e undefined.
8
+ function createClient(readThriftResult: { data: Record<string, unknown> }) {
9
+ const deviceDetails = {
10
+ device: 'TEST',
11
+ appVersion: '0.0.0',
12
+ systemName: 'TEST',
13
+ systemVersion: '0.0.0',
14
+ }
15
+ const stubClient = {
16
+ deviceDetails,
17
+ endpoint: 'legy.line-apps.test',
18
+ authToken: 'expired-token',
19
+ config: { timeout: 1000 },
20
+ storage: { get: async () => undefined },
21
+ log: () => {},
22
+ emit: () => {},
23
+ fetch: async () => ({
24
+ headers: { get: () => null },
25
+ arrayBuffer: async () => new ArrayBuffer(0),
26
+ }),
27
+ thrift: {
28
+ writeThrift: () => new Uint8Array(),
29
+ readThrift: () => readThriftResult,
30
+ rename_data: () => {},
31
+ },
32
+ }
33
+
34
+ return new RequestClient(stubClient as never)
35
+ }
36
+
37
+ describe('RequestClient.requestCore error handling', () => {
38
+ it('throws a clean RequestError when hasError is set but no exception struct is present', async () => {
39
+ // given: an error response with an empty success slot and no exception struct
40
+ const client = createClient({ data: { 0: undefined, someField: 1 } })
41
+
42
+ // when / then: the error branch must throw InternalError, not a TypeError
43
+ let thrown: unknown
44
+ try {
45
+ await client.requestCore('/S3', [], 'testMethod', 3)
46
+ } catch (error) {
47
+ thrown = error
48
+ }
49
+
50
+ expect(thrown).toBeInstanceOf(InternalError)
51
+ expect((thrown as InternalError).type).toBe('RequestError')
52
+ expect(thrown).not.toBeInstanceOf(TypeError)
53
+ })
54
+ })
@@ -45,6 +45,28 @@ This command:
45
45
  - Stores credentials securely in `~/.config/agent-messenger/`
46
46
  - Supports `--browser-profile <path>` for agent-browser profiles, custom Chrome user data dirs, or portable browser profiles
47
47
 
48
+ ### QR Code Sign-In
49
+
50
+ When the desktop app isn't installed (or extraction isn't an option), sign in with a QR code from a device where you're already logged into Slack. This runs entirely over HTTP — no browser automation required:
51
+
52
+ ```bash
53
+ # In Slack (desktop or web): your name (top-left) → "Sign in on mobile".
54
+ # Right-click the QR code → "Copy Image Address", then pipe it in:
55
+ pbpaste | agent-slack auth qr
56
+
57
+ # Or pass the data URL directly:
58
+ agent-slack auth qr "data:image/png;base64,iVBORw0KGgo..."
59
+
60
+ # Show each redirect hop while debugging:
61
+ agent-slack auth qr --debug
62
+ ```
63
+
64
+ This decodes the QR's one-time `z-app-` login URL, follows Slack's server-side redirect chain to capture the session cookie, retrieves the matching client token, and stores credentials like `auth extract`.
65
+
66
+ - The QR link is **single-use** — generate a fresh one if it expires.
67
+ - No Chrome/Chromium or desktop app is required.
68
+ - Works with workspaces that allow password/email sign-in. SSO-only / Enterprise Grid workspaces that disable the mobile QR flow are not supported.
69
+
48
70
  ### Multi-Workspace Management
49
71
 
50
72
  ```bash
@@ -39,6 +39,21 @@ const auth = await client.testAuth()
39
39
  // → { user_id: string, team_id: string, user?: string, team?: string }
40
40
  ```
41
41
 
42
+ ### QR Code Login
43
+
44
+ `loginWithQr` turns a Slack "Sign in on mobile" QR image into a usable session (token + cookie) over plain HTTP — no browser or desktop app. `dataUrl` is the QR image as a `data:image/png;base64,...` string.
45
+
46
+ ```typescript
47
+ import { loginWithQr, SlackClient } from 'agent-messenger/slack'
48
+
49
+ const session = await loginWithQr(dataUrl)
50
+ // → { token: string, cookie: string, workspace: string }
51
+
52
+ const client = await new SlackClient().login({ token: session.token, cookie: session.cookie })
53
+ ```
54
+
55
+ `loginWithQr(dataUrl, options)` accepts an optional `{ debug, fetchImpl, maxRedirects }` and throws `SlackError` with codes `qr_session_failed` (link expired / no session) or `qr_token_failed` (token unavailable). Use `decodeSlackQr(dataUrl)` to decode the QR without logging in.
56
+
42
57
  ### Messages
43
58
 
44
59
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.23.6",
3
+ "version": "2.24.0",
4
4
  "description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, Channel Talk)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -178,10 +178,12 @@
178
178
  "commander": "^11.1.0",
179
179
  "crypto-js": "^4.2.0",
180
180
  "curve25519-js": "^0.0.4",
181
+ "jsqr": "^1.4.0",
181
182
  "node-bignumber": "^1.2.2",
182
183
  "node-int64": "^0.4.0",
183
184
  "node-jose": "^2.2.0",
184
185
  "pino": "^10.3.1",
186
+ "pngjs": "^7.0.0",
185
187
  "qrcode": "^1.5.4",
186
188
  "thrift": "^0.20.0",
187
189
  "tweetnacl": "^1.0.3",
@@ -196,6 +198,7 @@
196
198
  "@types/hapi__boom": "^9.0.1",
197
199
  "@types/node": "^20.10.6",
198
200
  "@types/node-jose": "^1.1.13",
201
+ "@types/pngjs": "^6.0.5",
199
202
  "@types/qrcode": "^1.5.6",
200
203
  "@types/ws": "^8.18.1",
201
204
  "oxfmt": "^0.36.0",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-channeltalk
3
3
  description: Interact with Channel Talk using extracted desktop app or browser credentials - read chats, send messages, search messages, manage groups
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-channeltalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-channeltalkbot
3
3
  description: Interact with Channel Talk workspaces using API credentials - send messages, read chats, manage groups and bots
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-channeltalkbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-discord
3
3
  description: Interact with Discord servers - send messages, read channels, manage reactions
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-discord:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-discordbot
3
3
  description: Interact with Discord servers using bot tokens - send messages, read channels, manage reactions
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-discordbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-instagram
3
3
  description: Interact with Instagram DMs - send messages, read conversations, manage accounts
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-instagram:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-kakaotalk
3
3
  description: Interact with KakaoTalk - send messages, read chats, manage conversations
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-kakaotalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-line
3
3
  description: Interact with LINE - send messages, read chats, manage conversations
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-line:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-slack
3
3
  description: Interact with Slack workspaces - send messages, read channels, manage reactions
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-slack:*)
6
6
  metadata:
7
7
  openclaw:
@@ -39,6 +39,21 @@ On macOS, the system may prompt for your Keychain password the first time (requi
39
39
 
40
40
  **IMPORTANT**: Always use `agent-slack auth extract` to obtain tokens. The CLI extracts from the desktop app first, falling back to Chromium browsers if the app isn't installed.
41
41
 
42
+ ### QR Code Sign-In (No Desktop App Required)
43
+
44
+ If the Slack desktop app isn't installed (or extraction fails), you can sign in with a QR code from any device where you're already logged into Slack — no browser automation, fully over HTTP:
45
+
46
+ ```bash
47
+ # In Slack (desktop or web): your name (top-left) → "Sign in on mobile".
48
+ # Right-click the QR code → "Copy Image Address", then:
49
+ pbpaste | agent-slack auth qr
50
+
51
+ # Or pass the data URL directly:
52
+ agent-slack auth qr "data:image/png;base64,iVBORw0KGgo..."
53
+ ```
54
+
55
+ This decodes the QR's one-time login link, establishes a session, and stores the resulting credentials like `auth extract` does. The QR link is single-use — generate a fresh one if it expires.
56
+
42
57
  ### Multi-Workspace Support
43
58
 
44
59
  ```bash
@@ -145,6 +160,11 @@ agent-slack auth extract --browser-profile ~/work-profile --browser-profile ~/pe
145
160
 
146
161
  # --browser-profile accepts repeatable or comma-separated Chromium profile/user-data dirs
147
162
 
163
+ # Sign in with a QR code from Slack's "Sign in on mobile" screen (no desktop app needed)
164
+ agent-slack auth qr "data:image/png;base64,..."
165
+ pbpaste | agent-slack auth qr
166
+ agent-slack auth qr --debug # show each redirect hop for troubleshooting
167
+
148
168
  # Check auth status
149
169
  agent-slack auth status
150
170
 
@@ -562,6 +582,30 @@ Common errors:
562
582
 
563
583
  Credentials stored in `~/.config/agent-messenger/slack-credentials.json` (0600 permissions). See [references/authentication.md](references/authentication.md) for format and security details.
564
584
 
585
+ ## SDK: QR Code Login
586
+
587
+ `loginWithQr` turns a Slack "Sign in on mobile" QR image into a usable session (token + cookie) over plain HTTP — no browser, no desktop app.
588
+
589
+ ```typescript
590
+ import { loginWithQr, SlackClient, SlackCredentialManager } from 'agent-messenger/slack'
591
+
592
+ // dataUrl is the QR image as a "data:image/png;base64,..." string
593
+ const session = await loginWithQr(dataUrl)
594
+
595
+ const client = await new SlackClient().login({ token: session.token, cookie: session.cookie })
596
+ const { team_id, team } = await client.testAuth()
597
+
598
+ // Persist for later, like the CLI does
599
+ await new SlackCredentialManager().setWorkspace({
600
+ workspace_id: team_id,
601
+ workspace_name: team ?? session.workspace,
602
+ token: session.token,
603
+ cookie: session.cookie,
604
+ })
605
+ ```
606
+
607
+ `loginWithQr(dataUrl, options)` accepts an optional `{ debug, fetchImpl, maxRedirects }`. It throws `SlackError` with codes `qr_session_failed` (link expired / no session) or `qr_token_failed` (token could not be retrieved). To decode the QR without logging in, use `decodeSlackQr(dataUrl)` which returns `{ url, workspace, teamId, userId }`.
608
+
565
609
  ## SDK: Real-Time Events
566
610
 
567
611
  `SlackListener` connects to Slack's RTM WebSocket for instant event streaming. No polling — events arrive in real time.
@@ -34,6 +34,35 @@ This command:
34
34
 
35
35
  Use `--browser-profile <path>` for agent-browser profiles, custom Chrome user data dirs, or portable browser profiles. The option can be repeated or given comma-separated paths, and explicit paths are included even when desktop credentials are also present.
36
36
 
37
+ ### QR Code Sign-In
38
+
39
+ When the desktop app isn't installed and browser extraction isn't an option, you can sign in with a QR code from a device where you're already logged into Slack. This runs entirely over HTTP — no browser automation:
40
+
41
+ ```bash
42
+ # In Slack (desktop or web): your name (top-left) → "Sign in on mobile".
43
+ # Right-click the QR code → "Copy Image Address", then pipe it in:
44
+ pbpaste | agent-slack auth qr
45
+
46
+ # Or pass the data URL directly:
47
+ agent-slack auth qr "data:image/png;base64,iVBORw0KGgo..."
48
+
49
+ # Show each redirect hop while debugging:
50
+ agent-slack auth qr --debug
51
+ ```
52
+
53
+ How it works:
54
+
55
+ 1. Decodes the QR image (a `data:image/png;base64,...` string) to its one-time `z-app-` login URL
56
+ 2. Follows Slack's server-side redirect chain, capturing the session `d` cookie (`xoxd-...`)
57
+ 3. Retrieves the matching `xoxc-` client token from the established session
58
+ 4. Validates against the Slack API and stores credentials like `auth extract`
59
+
60
+ Notes:
61
+
62
+ - The QR link is **single-use** — if it expires, generate a fresh one from Slack's "Sign in on mobile" screen.
63
+ - No Chrome/Chromium or desktop app is required; the flow uses plain HTTP requests.
64
+ - Works with workspaces that allow password/email sign-in. SSO-only / Enterprise Grid workspaces that disable the mobile QR flow are not supported.
65
+
37
66
  ### Platform-Specific Paths
38
67
 
39
68
  **macOS (Direct Download):**
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-slackbot
3
3
  description: Interact with Slack workspaces using bot tokens - send messages, read channels, manage reactions
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-slackbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-teams
3
3
  description: Interact with Microsoft Teams - send messages, read channels, manage reactions
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-teams:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-telegram
3
3
  description: Interact with Telegram through TDLib - authenticate, inspect chats, and send messages
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-telegram:*)
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-telegrambot
3
3
  description: Interact with Telegram using bot tokens - send messages, read chats, manage reactions
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-telegrambot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-webex
3
3
  description: Interact with Cisco Webex - send messages, read spaces, manage memberships
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-webex:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-webexbot
3
3
  description: Interact with Cisco Webex using bot tokens - send messages, reply in threads, upload and download files, look up people, read spaces, manage memberships, stream real-time events
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-webexbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-wechatbot
3
3
  description: Interact with WeChat Official Account using API credentials - send messages, manage templates, list followers
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-wechatbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsapp
3
3
  description: Interact with WhatsApp - send messages, read chats, manage conversations
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-whatsapp:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsappbot
3
3
  description: Interact with WhatsApp using Cloud API credentials - send messages, manage templates
4
- version: 2.23.6
4
+ version: 2.24.0
5
5
  allowed-tools: Bash(agent-whatsappbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -9,6 +9,7 @@ import { debug } from '@/shared/utils/stderr'
9
9
  import { SlackClient, SlackError } from '../client'
10
10
  import { CredentialManager } from '../credential-manager'
11
11
  import { refreshCookie, tryWebTokenRefresh } from '../ensure-auth'
12
+ import { loginWithQr } from '../qr-http-login'
12
13
  import { type ExtractedWorkspace, TokenExtractor } from '../token-extractor'
13
14
 
14
15
  export function formatCredentialDebug(ws: ExtractedWorkspace, showSecrets?: boolean): string {
@@ -230,6 +231,70 @@ async function statusAction(options: { pretty?: boolean }): Promise<void> {
230
231
  }
231
232
  }
232
233
 
234
+ async function qrAction(imageArg: string | undefined, options: { pretty?: boolean; debug?: boolean }): Promise<void> {
235
+ try {
236
+ const dataUrl = imageArg ?? (await readStdin())
237
+ if (!dataUrl) {
238
+ console.log(
239
+ formatOutput(
240
+ {
241
+ error: 'No QR image provided. Pass the copied QR image data URL as an argument, or pipe it via stdin.',
242
+ hint: 'In Slack: your name (top-left) → "Sign in on mobile" → right-click the QR → Copy Image Address.',
243
+ },
244
+ options.pretty,
245
+ ),
246
+ )
247
+ process.exit(1)
248
+ }
249
+
250
+ const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
251
+ const session = await loginWithQr(dataUrl, { debug: debugLog })
252
+
253
+ const client = await new SlackClient().login({ token: session.token, cookie: session.cookie })
254
+ const authInfo = await client.testAuth()
255
+
256
+ const credManager = new CredentialManager()
257
+ const workspace: ExtractedWorkspace = {
258
+ workspace_id: authInfo.team_id,
259
+ workspace_name: authInfo.team || session.workspace,
260
+ token: session.token,
261
+ cookie: session.cookie,
262
+ }
263
+ await credManager.setWorkspace(workspace)
264
+
265
+ const config = await credManager.load()
266
+ if (!config.current_workspace) {
267
+ await credManager.setCurrentWorkspace(workspace.workspace_id)
268
+ }
269
+
270
+ console.log(
271
+ formatOutput(
272
+ {
273
+ workspace: `${workspace.workspace_id}/${workspace.workspace_name}`,
274
+ user: authInfo.user,
275
+ current: (await credManager.load()).current_workspace,
276
+ },
277
+ options.pretty,
278
+ ),
279
+ )
280
+ } catch (error) {
281
+ handleError(error as Error)
282
+ }
283
+ }
284
+
285
+ function readStdin(): Promise<string> {
286
+ if (process.stdin.isTTY) return Promise.resolve('')
287
+ return new Promise((resolve) => {
288
+ let data = ''
289
+ process.stdin.setEncoding('utf8')
290
+ process.stdin.on('data', (chunk) => {
291
+ data += chunk
292
+ })
293
+ process.stdin.on('end', () => resolve(data))
294
+ process.stdin.on('error', () => resolve(data))
295
+ })
296
+ }
297
+
233
298
  export function getExtractionErrorMessage(failureReasons: string[]): string {
234
299
  if (failureReasons.includes('missing_cookie')) {
235
300
  return 'Cookie extraction failed. Grant Keychain access when prompted, and make sure you are signed into Slack in the desktop app or a supported Chromium browser.'
@@ -260,6 +325,14 @@ export const authCommand = new Command('auth')
260
325
  .option('--unsafely-show-secrets', 'Show full token and cookie values in debug output')
261
326
  .action((options: BrowserProfileOption & Parameters<typeof extractAction>[0]) => extractAction(options)),
262
327
  )
328
+ .addCommand(
329
+ new Command('qr')
330
+ .description('Sign in by pasting a QR code from Slack\u2019s "Sign in on mobile" screen')
331
+ .argument('[image]', 'QR image data URL (data:image/png;base64,...); read from stdin if omitted')
332
+ .option('--pretty', 'Pretty print JSON output')
333
+ .option('--debug', 'Show debug output for troubleshooting')
334
+ .action(qrAction),
335
+ )
263
336
  .addCommand(
264
337
  new Command('logout')
265
338
  .description('Logout from workspace')
@@ -145,9 +145,13 @@ async function refreshAndVerify(cookie: string, domain: string, fallbackName: st
145
145
 
146
146
  const TOKEN_REGEX = /"api_token":"(xoxc-[a-zA-Z0-9-]+)"/
147
147
 
148
- export async function refreshTokenFromWeb(domain: string, cookie: string): Promise<string | null> {
148
+ export async function refreshTokenFromWeb(
149
+ domain: string,
150
+ cookie: string,
151
+ fetchImpl: typeof fetch = fetch,
152
+ ): Promise<string | null> {
149
153
  try {
150
- const response = await fetch(`https://${domain}.slack.com/ssb/redirect`, {
154
+ const response = await fetchImpl(`https://${domain}.slack.com/ssb/redirect`, {
151
155
  headers: { Cookie: `d=${cookie}` },
152
156
  redirect: 'follow',
153
157
  })
@@ -12,6 +12,8 @@ import {
12
12
  SlackUserSchema,
13
13
  WorkspaceCredentialsSchema,
14
14
  ConfigSchema,
15
+ decodeSlackQr,
16
+ loginWithQr,
15
17
  } from '@/platforms/slack/index'
16
18
 
17
19
  it('SlackClient is exported from barrel', () => {
@@ -57,3 +59,11 @@ it('WorkspaceCredentialsSchema is exported from barrel', () => {
57
59
  it('ConfigSchema is exported from barrel', () => {
58
60
  expect(typeof ConfigSchema.parse).toBe('function')
59
61
  })
62
+
63
+ it('loginWithQr is exported from barrel', () => {
64
+ expect(typeof loginWithQr).toBe('function')
65
+ })
66
+
67
+ it('decodeSlackQr is exported from barrel', () => {
68
+ expect(typeof decodeSlackQr).toBe('function')
69
+ })
@@ -1,6 +1,10 @@
1
1
  export { SlackClient, SlackError } from './client'
2
2
  export { SlackCredentialManager, CredentialManager } from './credential-manager'
3
3
  export { SlackListener } from './listener'
4
+ export { loginWithQr } from './qr-http-login'
5
+ export type { QrLoginOptions, QrSession } from './qr-http-login'
6
+ export { decodeSlackQr } from './qr-login'
7
+ export type { SlackQrLogin } from './qr-login'
4
8
  export type {
5
9
  SlackBookmark,
6
10
  SlackChannel,