agent-messenger 2.5.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.
Files changed (64) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.github/workflows/ci.yml +35 -0
  3. package/bun.lock +10 -2
  4. package/dist/package.json +3 -1
  5. package/dist/src/platforms/kakaotalk/client.d.ts +4 -1
  6. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  7. package/dist/src/platforms/kakaotalk/client.js +21 -7
  8. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  9. package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
  10. package/dist/src/platforms/kakaotalk/commands/auth.js +24 -55
  11. package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
  12. package/dist/src/platforms/kakaotalk/credential-manager.d.ts.map +1 -1
  13. package/dist/src/platforms/kakaotalk/credential-manager.js +1 -0
  14. package/dist/src/platforms/kakaotalk/credential-manager.js.map +1 -1
  15. package/dist/src/platforms/kakaotalk/index.d.ts +1 -1
  16. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  17. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  18. package/dist/src/platforms/kakaotalk/listener.js +2 -2
  19. package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
  20. package/dist/src/platforms/kakaotalk/protocol/config.d.ts +8 -2
  21. package/dist/src/platforms/kakaotalk/protocol/config.d.ts.map +1 -1
  22. package/dist/src/platforms/kakaotalk/protocol/config.js +15 -2
  23. package/dist/src/platforms/kakaotalk/protocol/config.js.map +1 -1
  24. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +3 -1
  25. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  26. package/dist/src/platforms/kakaotalk/protocol/session.js +13 -10
  27. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  28. package/dist/src/platforms/kakaotalk/types.d.ts +10 -0
  29. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  30. package/dist/src/platforms/kakaotalk/types.js +1 -0
  31. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  32. package/dist/src/tui/adapters/kakaotalk-adapter.d.ts.map +1 -1
  33. package/dist/src/tui/adapters/kakaotalk-adapter.js +5 -2
  34. package/dist/src/tui/adapters/kakaotalk-adapter.js.map +1 -1
  35. package/docs/content/docs/cli/kakaotalk.mdx +3 -39
  36. package/e2e/config.ts +1 -1
  37. package/package.json +3 -1
  38. package/skills/agent-channeltalk/SKILL.md +1 -1
  39. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  40. package/skills/agent-discord/SKILL.md +1 -1
  41. package/skills/agent-discordbot/SKILL.md +1 -1
  42. package/skills/agent-instagram/SKILL.md +1 -1
  43. package/skills/agent-kakaotalk/SKILL.md +11 -63
  44. package/skills/agent-kakaotalk/references/authentication.md +4 -69
  45. package/skills/agent-kakaotalk/references/common-patterns.md +13 -0
  46. package/skills/agent-line/SKILL.md +1 -1
  47. package/skills/agent-slack/SKILL.md +1 -1
  48. package/skills/agent-slackbot/SKILL.md +1 -1
  49. package/skills/agent-teams/SKILL.md +1 -1
  50. package/skills/agent-telegram/SKILL.md +1 -1
  51. package/skills/agent-webex/SKILL.md +1 -1
  52. package/skills/agent-wechatbot/SKILL.md +1 -1
  53. package/skills/agent-whatsapp/SKILL.md +1 -1
  54. package/skills/agent-whatsappbot/SKILL.md +1 -1
  55. package/src/platforms/kakaotalk/client.ts +25 -10
  56. package/src/platforms/kakaotalk/commands/auth.ts +22 -73
  57. package/src/platforms/kakaotalk/credential-manager.ts +1 -0
  58. package/src/platforms/kakaotalk/index.ts +1 -0
  59. package/src/platforms/kakaotalk/listener.test.ts +2 -2
  60. package/src/platforms/kakaotalk/listener.ts +2 -2
  61. package/src/platforms/kakaotalk/protocol/config.ts +26 -2
  62. package/src/platforms/kakaotalk/protocol/session.ts +16 -10
  63. package/src/platforms/kakaotalk/types.ts +4 -0
  64. package/src/tui/adapters/kakaotalk-adapter.ts +5 -2
@@ -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.5.0
4
+ version: 2.6.0
5
5
  allowed-tools: Bash(agent-kakaotalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -16,7 +16,7 @@ metadata:
16
16
 
17
17
  # Agent KakaoTalk
18
18
 
19
- A TypeScript CLI tool that enables AI agents and humans to interact with KakaoTalk through a simple command interface. Features credential extraction from the KakaoTalk desktop app and sub-device login that keeps your desktop app running.
19
+ A TypeScript CLI tool that enables AI agents and humans to interact with KakaoTalk through a simple command interface. Features sub-device login that keeps your desktop app running.
20
20
 
21
21
  ## Key Concepts
22
22
 
@@ -32,12 +32,9 @@ Before diving in, a few things about KakaoTalk's architecture:
32
32
  ## Quick Start
33
33
 
34
34
  ```bash
35
- # Login (recommended — registers as sub-device, desktop app stays running)
35
+ # Login (registers as sub-device, desktop app stays running)
36
36
  agent-kakaotalk auth login
37
37
 
38
- # Or extract credentials from desktop app (kicks desktop session)
39
- agent-kakaotalk auth extract
40
-
41
38
  # List chat rooms
42
39
  agent-kakaotalk chat list
43
40
 
@@ -53,10 +50,6 @@ agent-kakaotalk whoami
53
50
 
54
51
  ## Authentication
55
52
 
56
- KakaoTalk offers two authentication methods:
57
-
58
- ### Method 1: Login (Recommended)
59
-
60
53
  Registers the CLI as a sub-device (tablet slot by default). Your desktop app keeps running.
61
54
 
62
55
  ```bash
@@ -77,18 +70,7 @@ agent-kakaotalk auth login --email user@example.com --password mypass
77
70
  3. The CLI polls until you confirm on your phone
78
71
  4. Login completes automatically after confirmation
79
72
 
80
- **IMPORTANT**: NEVER guide the user to open a web browser, use DevTools, or manually copy tokens. Always use `agent-kakaotalk auth login` or `agent-kakaotalk auth extract`.
81
-
82
- ### Method 2: Extract from Desktop App
83
-
84
- Extracts OAuth tokens directly from the KakaoTalk desktop app's cache. This **kicks the desktop session** because it reuses the desktop's credentials.
85
-
86
- ```bash
87
- agent-kakaotalk auth extract
88
- agent-kakaotalk auth extract --debug
89
- ```
90
-
91
- On macOS, reads from `~/Library/Containers/com.kakao.KakaoTalkMac/Data/Library/Caches/Cache.db`. On Windows, reads from the registry and `%LocalAppData%\Kakao\login_list.dat`.
73
+ **IMPORTANT**: NEVER guide the user to open a web browser, use DevTools, or manually copy tokens. Always use `agent-kakaotalk auth login`.
92
74
 
93
75
  ### Agent Behavior (MANDATORY)
94
76
 
@@ -102,21 +84,13 @@ agent-kakaotalk auth status
102
84
 
103
85
  If authenticated → retry the original command.
104
86
 
105
- **Step 2: Try credential extraction first**
106
-
107
- ```bash
108
- agent-kakaotalk auth extract
109
- ```
110
-
111
- If extraction succeeds → retry the original command. Extraction is silent and requires no user input.
112
-
113
- **Step 3: If extraction fails, use login flow**
87
+ **Step 2: Login (registers as sub-device — desktop app stays running)**
114
88
 
115
89
  ```bash
116
90
  agent-kakaotalk auth login
117
91
  ```
118
92
 
119
- The CLI will auto-extract the email from the desktop app. On fresh installs, the CLI may prompt for the KakaoTalk password once (one-time device registration). After registration, the password is never needed again.
93
+ The CLI auto-extracts the email (and password if available) from the desktop app. On fresh installs, the CLI may prompt for the KakaoTalk password once (one-time device registration). After registration, the password is never needed again.
120
94
 
121
95
  Possible responses:
122
96
 
@@ -134,7 +108,7 @@ agent-kakaotalk auth login --password-file /tmp/.kakao-pw
134
108
 
135
109
  The `--password-file` flag reads the password from the file and **deletes the file immediately after reading**. The password never appears in chat, shell history, or process list.
136
110
 
137
- **Step 4: Retry the original command**
111
+ **Step 3: Retry the original command**
138
112
  After successful auth, immediately execute whatever the user originally asked for.
139
113
 
140
114
  ### Device Slots
@@ -157,7 +131,7 @@ agent-kakaotalk auth login --device-type tablet --force
157
131
 
158
132
  ## Multi-Account
159
133
 
160
- KakaoTalk supports multiple accounts. Each login or extraction stores credentials separately, keyed by user ID.
134
+ KakaoTalk supports multiple accounts. Each login stores credentials separately, keyed by user ID.
161
135
 
162
136
  ### Listing Accounts
163
137
 
@@ -262,11 +236,6 @@ agent-kakaotalk auth login --email <email> --password <password>
262
236
  agent-kakaotalk auth login --device-type pc --force
263
237
  agent-kakaotalk auth login --debug
264
238
 
265
- # Extract credentials from KakaoTalk desktop app
266
- agent-kakaotalk auth extract
267
- agent-kakaotalk auth extract --debug
268
- agent-kakaotalk auth extract --unsafely-show-secrets
269
-
270
239
  # Check auth status
271
240
  agent-kakaotalk auth status
272
241
 
@@ -434,7 +403,7 @@ All commands return consistent error format:
434
403
 
435
404
  Common errors:
436
405
 
437
- - `No KakaoTalk credentials found` — not authenticated. Run `auth login` or `auth extract`.
406
+ - `No KakaoTalk credentials found` — not authenticated. Run `auth login`.
438
407
  - `bad_credentials` — wrong email or password. Cached credentials from the desktop app may be stale. Ask the user to provide credentials manually with `--email` and `--password`.
439
408
  - `login_failed` — device slot conflict or unknown login error. Run with `--debug` for the full server response.
440
409
  - `passcode_request_failed` — failed to request device verification code.
@@ -519,7 +488,7 @@ See the [KakaoTalk SDK documentation](https://agent-messenger.dev/docs/sdk/kakao
519
488
 
520
489
  ## Limitations
521
490
 
522
- - macOS and Windows only (desktop app required for credential extraction)
491
+ - macOS and Windows only (desktop app needed for auto-extracting email/password during login)
523
492
  - No Linux support (KakaoTalk desktop not available on Linux)
524
493
  - No file upload or download
525
494
  - No channel/chat room creation or management
@@ -555,17 +524,9 @@ pnpm dlx --package agent-messenger agent-kakaotalk chat list --pretty
555
524
 
556
525
  **NEVER run `npx agent-kakaotalk`, `bunx agent-kakaotalk`, or `pnpm dlx agent-kakaotalk`** without `--package agent-messenger`. It will fail or install a wrong package since `agent-kakaotalk` is not the npm package name.
557
526
 
558
- ### No credentials found
559
-
560
- If `auth extract` fails:
561
-
562
- 1. Make sure the KakaoTalk desktop app is installed and you're logged in
563
- 2. Run `agent-kakaotalk auth extract --debug` for detailed diagnostics
564
- 3. If extraction still fails, use `agent-kakaotalk auth login` instead (recommended)
565
-
566
527
  ### Password prompt on fresh install
567
528
 
568
- On fresh installs, the macOS desktop app hashes the password before caching, so the CLI cannot extract it automatically. The CLI will prompt for the password once to register the device. After registration, the password is never needed again.
529
+ On fresh installs, the desktop app (macOS or Windows) may hash or omit the password from its cache, so the CLI cannot extract it automatically. The CLI will prompt for the password once to register the device — via a native dialog on macOS (AppKit) and Windows (PowerShell WinForms), or via a TTY prompt if a terminal is available. After registration, the password is never needed again.
569
530
 
570
531
  When the CLI returns `{"next_action": "run_interactive", ...}`, use a tmux session to let the user type their password securely. See "Handling `run_interactive`" above for the exact steps.
571
532
 
@@ -597,19 +558,6 @@ If the passcode expires before you confirm on your phone:
597
558
  2. Confirm the code on your phone within the time limit
598
559
  3. The CLI automatically completes login after confirmation
599
560
 
600
- ### Cache.db not found (macOS)
601
-
602
- The CLI looks for KakaoTalk's cache at:
603
-
604
- ```
605
- ~/Library/Containers/com.kakao.KakaoTalkMac/Data/Library/Caches/Cache.db
606
- ```
607
-
608
- If this file doesn't exist:
609
- 1. Install KakaoTalk from the Mac App Store
610
- 2. Log in and send at least one message (to populate the cache)
611
- 3. Run `agent-kakaotalk auth extract` again
612
-
613
561
  ## References
614
562
 
615
563
  - [Authentication Guide](references/authentication.md)
@@ -2,12 +2,9 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- agent-kakaotalk supports two authentication methods:
5
+ agent-kakaotalk authenticates by registering the CLI as a sub-device (tablet or PC slot) using KakaoTalk's Android sub-device API. Your desktop app and phone keep running.
6
6
 
7
- 1. **Login** (recommended) — registers the CLI as a sub-device (tablet or PC slot). Your desktop app and phone keep running.
8
- 2. **Extract** — reads OAuth tokens from the KakaoTalk desktop app's local cache. This reuses the desktop's credentials, which kicks the desktop session.
9
-
10
- ## Method 1: Login Flow
7
+ ## Login Flow
11
8
 
12
9
  ### How It Works
13
10
 
@@ -107,65 +104,6 @@ Each device is identified by a UUID. The CLI generates one on first login and re
107
104
 
108
105
  The UUID is stored in the credentials file alongside the OAuth token.
109
106
 
110
- ## Method 2: Extract from Desktop App
111
-
112
- ### How It Works
113
-
114
- The KakaoTalk desktop app (macOS) caches HTTP requests in a SQLite database (`Cache.db`). These cached requests contain:
115
-
116
- - `Authorization` header with the OAuth token
117
- - Login form body with email, password, and device UUID
118
- - Refresh token from token renewal requests
119
-
120
- The CLI reads this cache and stores the extracted credentials.
121
-
122
- ### macOS
123
-
124
- ```bash
125
- agent-kakaotalk auth extract
126
- ```
127
-
128
- Cache location:
129
- ```
130
- ~/Library/Containers/com.kakao.KakaoTalkMac/Data/Library/Caches/Cache.db
131
- ```
132
-
133
- The CLI:
134
- 1. Copies `Cache.db` (and WAL/SHM journals) to a temp directory
135
- 2. Queries for requests to `talk-pilsner.kakao.com` (messaging API)
136
- 3. Parses binary plist blobs for the OAuth token
137
- 4. Extracts device UUID and refresh token from `login.json` / `renew_token.json` cached requests
138
- 5. Stores extracted credentials
139
-
140
- ### Windows
141
-
142
- ```bash
143
- agent-kakaotalk auth extract
144
- ```
145
-
146
- The CLI reads from:
147
- - Registry: `HKCU\Software\Kakao\KakaoTalk\DeviceInfo` (device UUID)
148
- - File: `%LocalAppData%\Kakao\login_list.dat` (login credentials)
149
-
150
- ### Troubleshooting Extraction
151
-
152
- Use `--debug` for detailed logs:
153
-
154
- ```bash
155
- agent-kakaotalk auth extract --debug
156
- ```
157
-
158
- This shows:
159
- - Which cache path was found
160
- - How many cached requests were discovered
161
- - Which tokens were successfully extracted
162
-
163
- Use `--unsafely-show-secrets` to see full token values (debug mode only):
164
-
165
- ```bash
166
- agent-kakaotalk auth extract --unsafely-show-secrets
167
- ```
168
-
169
107
  ## Credential Storage
170
108
 
171
109
  ### Location
@@ -237,13 +175,13 @@ Output when not authenticated:
237
175
 
238
176
  ```json
239
177
  {
240
- "error": "No account configured. Run \"auth login\" or \"auth extract\" first."
178
+ "error": "No account configured. Run \"auth login\" first."
241
179
  }
242
180
  ```
243
181
 
244
182
  ## Multi-Account
245
183
 
246
- KakaoTalk supports multiple accounts. Each login or extraction stores credentials separately, keyed by user ID.
184
+ KakaoTalk supports multiple accounts. Each login stores credentials separately, keyed by user ID.
247
185
 
248
186
  ### Listing Accounts
249
187
 
@@ -316,9 +254,6 @@ If commands start failing with auth errors:
316
254
  # Try login again (reuses saved device UUID to skip passcode)
317
255
  agent-kakaotalk auth login
318
256
 
319
- # Or re-extract from desktop app
320
- agent-kakaotalk auth extract
321
-
322
257
  # Verify it worked
323
258
  agent-kakaotalk auth status
324
259
  ```
@@ -391,6 +391,19 @@ try {
391
391
  }
392
392
  ```
393
393
 
394
+ When passing credentials manually, include `deviceType` to control which session slot the LOCO connection uses:
395
+
396
+ ```typescript
397
+ const client = await new KakaoTalkClient().login({
398
+ oauthToken: 'token',
399
+ userId: '1234567890',
400
+ deviceUuid: 'uuid',
401
+ deviceType: 'tablet', // 'tablet' (default, safe) or 'pc' (kicks desktop app)
402
+ })
403
+ ```
404
+
405
+ The `deviceType` determines the LOCO protocol identity: `'tablet'` sends `os: 'android'` (tablet sub-device slot), while `'pc'` sends `os: 'mac'` on macOS or `os: 'win'` on Windows (PC slot, conflicts with the desktop app). When using auto-login (`.login()` with no arguments), `deviceType` is read from stored credentials automatically.
406
+
394
407
  ### Auto-Reconnect
395
408
 
396
409
  `getChats`, `getMessages`, and `sendMessage` automatically reconnect once when the LOCO session dies (e.g. the KakaoTalk desktop app reclaims the session or the network drops). The reconnect is transparent — callers don't need to handle session-drop errors.
@@ -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.5.0
4
+ version: 2.6.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.5.0
4
+ version: 2.6.0
5
5
  allowed-tools: Bash(agent-slack:*)
6
6
  metadata:
7
7
  openclaw:
@@ -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.5.0
4
+ version: 2.6.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.5.0
4
+ version: 2.6.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.5.0
4
+ version: 2.6.0
5
5
  allowed-tools: Bash(agent-telegram:*)
6
6
  ---
7
7
 
@@ -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.5.0
4
+ version: 2.6.0
5
5
  allowed-tools: Bash(agent-webex:*)
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.5.0
4
+ version: 2.6.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.5.0
4
+ version: 2.6.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.5.0
4
+ version: 2.6.0
5
5
  allowed-tools: Bash(agent-whatsappbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -6,10 +6,10 @@ import { join } from 'node:path'
6
6
 
7
7
  import { warn } from '@/shared/utils/stderr'
8
8
 
9
- import { APP_VERSION, LANG, OS } from './protocol/config'
9
+ import { LANG, PC_OS_NAME, getLocoDeviceConfig } from './protocol/config'
10
10
  import { LocoSession } from './protocol/session'
11
11
  import type { ChatListResponse, LoginListResponse, SyncState } from './protocol/types'
12
- import type { KakaoChat, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
12
+ import type { KakaoChat, KakaoDeviceType, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
13
13
 
14
14
  export class KakaoTalkError extends Error {
15
15
  code: string
@@ -231,12 +231,13 @@ export class KakaoTalkClient {
231
231
  private oauthToken: string | null = null
232
232
  private userId: string | null = null
233
233
  private deviceUuid: string | null = null
234
+ private deviceType: KakaoDeviceType = 'tablet'
234
235
  private state: SessionState | null = null
235
236
  private initPromise: Promise<SessionState> | null = null
236
237
  private closed = false
237
238
 
238
239
  async login(
239
- credentials?: { oauthToken: string; userId: string; deviceUuid?: string },
240
+ credentials?: { oauthToken: string; userId: string; deviceUuid?: string; deviceType?: KakaoDeviceType },
240
241
  accountId?: string,
241
242
  ): Promise<this> {
242
243
  if (credentials) {
@@ -245,19 +246,26 @@ export class KakaoTalkClient {
245
246
  this.oauthToken = credentials.oauthToken
246
247
  this.userId = credentials.userId
247
248
  this.deviceUuid = credentials.deviceUuid ?? `agent-messenger-${credentials.userId}`
249
+ this.deviceType = credentials.deviceType ?? 'tablet'
248
250
  return this
249
251
  }
250
252
  const { ensureKakaoAuth } = await import('./ensure-auth')
251
253
  const account = await ensureKakaoAuth(accountId)
252
- return this.login({ oauthToken: account.oauth_token, userId: account.user_id, deviceUuid: account.device_uuid })
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
+ })
253
260
  }
254
261
 
255
- getCredentials(): { oauthToken: string; userId: string; deviceUuid: string } {
262
+ getCredentials(): { oauthToken: string; userId: string; deviceUuid: string; deviceType: KakaoDeviceType } {
256
263
  this.ensureAuth()
257
264
  return {
258
265
  oauthToken: this.oauthToken!,
259
266
  userId: this.userId!,
260
267
  deviceUuid: this.deviceUuid!,
268
+ deviceType: this.deviceType,
261
269
  }
262
270
  }
263
271
 
@@ -315,7 +323,7 @@ export class KakaoTalkClient {
315
323
  const session = new LocoSession()
316
324
  try {
317
325
  const syncState = await loadSyncState(this.deviceUuid!)
318
- const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!, syncState)
326
+ const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!, syncState, this.deviceType)
319
327
 
320
328
  const newSyncState = mergeSyncState(syncState, loginResult)
321
329
  await saveSyncState(this.deviceUuid!, newSyncState)
@@ -500,17 +508,24 @@ export class KakaoTalkClient {
500
508
  async getProfile(): Promise<KakaoProfile> {
501
509
  this.ensureAuth()
502
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
+
503
518
  const headers = {
504
519
  Authorization: `${this.oauthToken}-${this.deviceUuid}`,
505
- A: `${OS}/${APP_VERSION}/${LANG}`,
506
- 'User-Agent': `KT/${APP_VERSION} Md/macOS ${LANG}`,
520
+ A: `${deviceConfig.os}/${deviceConfig.appVersion}/${LANG}`,
521
+ 'User-Agent': userAgent,
507
522
  Accept: '*/*',
508
523
  'Accept-Language': LANG,
509
524
  }
510
525
 
511
526
  const [profileRes, settingsRes] = await Promise.all([
512
- fetch('https://katalk.kakao.com/mac/profile3/me.json', { headers }),
513
- fetch('https://katalk.kakao.com/mac/account/more_settings.json?since=0&lang=ko', { headers }),
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 }),
514
529
  ])
515
530
 
516
531
  if (!profileRes.ok) {
@@ -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 { generateDeviceUuid, loginFlow } from '../auth/kakao-login'
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
- const fd = openSync('/dev/tty', 'r')
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 savedDeviceUuid = pendingState?.device_uuid ?? existing?.device_uuid
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" or "auth extract" first.' }, options.pretty))
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')
@@ -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
  }
@@ -4,6 +4,7 @@ export { KakaoTalkListener } from './listener'
4
4
  export type { PendingLoginState } from './credential-manager'
5
5
  export type {
6
6
  KakaoAccountCredentials,
7
+ KakaoAuthMethod,
7
8
  KakaoChat,
8
9
  KakaoConfig,
9
10
  KakaoDeviceType,
@@ -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 () => {