agent-messenger 2.15.1 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/attachment-router.d.ts +25 -0
- package/dist/src/platforms/kakaotalk/attachment-router.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/attachment-router.js +29 -0
- package/dist/src/platforms/kakaotalk/attachment-router.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts +14 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +216 -0
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/message.js +49 -0
- package/dist/src/platforms/kakaotalk/commands/message.js.map +1 -1
- package/dist/src/platforms/kakaotalk/image-meta.d.ts +7 -0
- package/dist/src/platforms/kakaotalk/image-meta.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/image-meta.js +153 -0
- package/dist/src/platforms/kakaotalk/image-meta.js.map +1 -0
- package/dist/src/platforms/kakaotalk/index.d.ts +8 -2
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +5 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/media-upload.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/media-upload.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/media-upload.js +44 -0
- package/dist/src/platforms/kakaotalk/media-upload.js.map +1 -0
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts +1 -0
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +11 -0
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.d.ts +25 -0
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.js +99 -0
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.js.map +1 -0
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts +6 -0
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +61 -0
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +44 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +9 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/slackbot/types.d.ts +4 -0
- package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +47 -2
- package/docs/content/docs/sdk/kakaotalk.mdx +32 -0
- package/package.json +1 -1
- 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-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +62 -4
- package/skills/agent-kakaotalk/references/common-patterns.md +50 -11
- 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 +1 -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-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/kakaotalk/attachment-router.test.ts +102 -0
- package/src/platforms/kakaotalk/attachment-router.ts +50 -0
- package/src/platforms/kakaotalk/client.ts +315 -8
- package/src/platforms/kakaotalk/commands/message.ts +66 -0
- package/src/platforms/kakaotalk/image-meta.test.ts +90 -0
- package/src/platforms/kakaotalk/image-meta.ts +176 -0
- package/src/platforms/kakaotalk/index.ts +13 -0
- package/src/platforms/kakaotalk/media-upload.ts +44 -0
- package/src/platforms/kakaotalk/protocol/connection.ts +11 -0
- package/src/platforms/kakaotalk/protocol/media-uploader.ts +129 -0
- package/src/platforms/kakaotalk/protocol/session.ts +67 -0
- package/src/platforms/kakaotalk/types.ts +57 -0
- package/src/platforms/slackbot/types.ts +16 -0
- package/src/platforms/telegrambot/cli.ts +0 -0
|
@@ -55,9 +55,48 @@ agent-kakaotalk message send "$TARGET_CHAT" "Hey Alice!"
|
|
|
55
55
|
|
|
56
56
|
**When to use**: First time interacting with a chat, or when the user references a chat by name.
|
|
57
57
|
|
|
58
|
-
> Note: `display_name` joins the chat's member nicknames. For the user-set room title (matching the KakaoTalk app), see [Pattern
|
|
58
|
+
> Note: `display_name` joins the chat's member nicknames. For the user-set room title (matching the KakaoTalk app), see [Pattern 10](#pattern-10-resolve-canonical-room-titles).
|
|
59
59
|
|
|
60
|
-
## Pattern 3:
|
|
60
|
+
## Pattern 3: Send Files, Photos, Videos, and Audio
|
|
61
|
+
|
|
62
|
+
**Use case**: Upload an attachment to a chat (photo, video, voice, generic file, or multi-photo gallery)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
#!/bin/bash
|
|
66
|
+
|
|
67
|
+
CHAT_ID="9876543210"
|
|
68
|
+
|
|
69
|
+
# Single file — MIME is sniffed from the filename and routed to the right kind
|
|
70
|
+
agent-kakaotalk message upload "$CHAT_ID" ./photo.jpg # → photo (inline preview)
|
|
71
|
+
agent-kakaotalk message upload "$CHAT_ID" ./clip.mp4 # → video (inline player)
|
|
72
|
+
agent-kakaotalk message upload "$CHAT_ID" ./voice.m4a # → audio (voice bubble)
|
|
73
|
+
agent-kakaotalk message upload "$CHAT_ID" ./report.pdf # → file (download icon)
|
|
74
|
+
|
|
75
|
+
# Multi-photo gallery — pass 2+ files and the CLI uses the gallery flow
|
|
76
|
+
agent-kakaotalk message upload "$CHAT_ID" ./img1.jpg ./img2.jpg ./img3.jpg
|
|
77
|
+
|
|
78
|
+
# Force a specific kind to override auto-routing
|
|
79
|
+
agent-kakaotalk message upload "$CHAT_ID" ./clip.mp4 --as file # send as generic file, not inline video
|
|
80
|
+
|
|
81
|
+
# Override MIME detection for extension-less files
|
|
82
|
+
agent-kakaotalk message upload "$CHAT_ID" ./data.bin --mime application/octet-stream
|
|
83
|
+
|
|
84
|
+
# With error handling
|
|
85
|
+
RESULT=$(agent-kakaotalk message upload "$CHAT_ID" ./photo.jpg)
|
|
86
|
+
SUCCESS=$(echo "$RESULT" | jq -r '.success')
|
|
87
|
+
if [ "$SUCCESS" = "true" ]; then
|
|
88
|
+
echo "Uploaded as log_id $(echo "$RESULT" | jq -r '.log_id')"
|
|
89
|
+
else
|
|
90
|
+
echo "Upload failed: $(echo "$RESULT" | jq -r '.status_code')"
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**When to use**: Sending screenshots, generated reports, deployment artifacts, voice notes, or photo bundles to a chat.
|
|
96
|
+
|
|
97
|
+
**Routing rules**: `image/*` → photo, `video/*` → video, `audio/*` → audio (voice memo), everything else → generic file. The full SHIP / POST / COMPLETE LOCO pipeline is handled internally — no separate "send text after upload" step is needed. KakaoTalk caps single-message attachment sizes server-side; the CLI surfaces the server's status code on rejection.
|
|
98
|
+
|
|
99
|
+
## Pattern 4: Monitor Chat for New Messages
|
|
61
100
|
|
|
62
101
|
**Use case**: Watch a chat room and respond to new messages
|
|
63
102
|
|
|
@@ -91,7 +130,7 @@ done
|
|
|
91
130
|
|
|
92
131
|
**Limitations**: Polling-based, not real-time. Each poll establishes a LOCO connection, so use reasonable intervals (10s+).
|
|
93
132
|
|
|
94
|
-
## Pattern
|
|
133
|
+
## Pattern 5: Read Recent Chat History
|
|
95
134
|
|
|
96
135
|
**Use case**: Catch up on what happened in a chat
|
|
97
136
|
|
|
@@ -113,7 +152,7 @@ echo "$MESSAGES" | jq -r '.[] | "\(.author_id): \(.message // "[non-text]")"'
|
|
|
113
152
|
|
|
114
153
|
**When to use**: Context gathering, summarizing conversations, catching up on missed messages.
|
|
115
154
|
|
|
116
|
-
## Pattern
|
|
155
|
+
## Pattern 6: Fetch More Messages
|
|
117
156
|
|
|
118
157
|
**Use case**: Read more messages than the default 20
|
|
119
158
|
|
|
@@ -148,7 +187,7 @@ NEW_MESSAGES=$(agent-kakaotalk message list "$CHAT_ID" --from "$LAST_SEEN")
|
|
|
148
187
|
|
|
149
188
|
**Pagination details**: The CLI now prefers KakaoTalk's `MCHATLOGS` flow for history reads, fetching message batches from the requested `--from` point and returning the last N messages after deduplication and ascending sort. If that path cannot provide results, it falls back to `CHATONROOM` + `SYNCMSG` for compatibility. As a safety net, both paths are capped at 50 internal pages. A warning is printed to stderr only when that cap is actually hit and the returned history may be incomplete.
|
|
150
189
|
|
|
151
|
-
## Pattern
|
|
190
|
+
## Pattern 7: Multi-Chat Broadcast
|
|
152
191
|
|
|
153
192
|
**Use case**: Send the same message to multiple chats
|
|
154
193
|
|
|
@@ -176,7 +215,7 @@ done
|
|
|
176
215
|
|
|
177
216
|
**When to use**: Announcements, notifications across multiple chats.
|
|
178
217
|
|
|
179
|
-
## Pattern
|
|
218
|
+
## Pattern 8: Multi-Account Operations
|
|
180
219
|
|
|
181
220
|
**Use case**: Manage and operate across multiple KakaoTalk accounts
|
|
182
221
|
|
|
@@ -201,7 +240,7 @@ agent-kakaotalk auth status --account 1111111111
|
|
|
201
240
|
|
|
202
241
|
**When to use**: Managing multiple KakaoTalk identities, sending messages as different accounts, or checking status across accounts.
|
|
203
242
|
|
|
204
|
-
## Pattern
|
|
243
|
+
## Pattern 9: Unread Message Summary
|
|
205
244
|
|
|
206
245
|
**Use case**: Check which chats have unread messages
|
|
207
246
|
|
|
@@ -223,7 +262,7 @@ echo "$UNREAD" | jq -r '.[] | " \(.display_name // "Unknown") — \(.unread_cou
|
|
|
223
262
|
|
|
224
263
|
**When to use**: Morning catch-up, checking for urgent messages, triage.
|
|
225
264
|
|
|
226
|
-
## Pattern
|
|
265
|
+
## Pattern 10: Resolve Canonical Room Titles
|
|
227
266
|
|
|
228
267
|
**Use case**: Show user-set room names (matching the official KakaoTalk app) instead of comma-joined member nicknames
|
|
229
268
|
|
|
@@ -269,7 +308,7 @@ const chats = await client.getChats({ resolveTitles: true })
|
|
|
269
308
|
const title = await client.getChatTitle('9876543210')
|
|
270
309
|
```
|
|
271
310
|
|
|
272
|
-
## Pattern
|
|
311
|
+
## Pattern 11: Error Handling and Retry
|
|
273
312
|
|
|
274
313
|
**Use case**: Robust message sending with retries
|
|
275
314
|
|
|
@@ -454,9 +493,9 @@ The `deviceType` determines the LOCO protocol identity: `'tablet'` sends `os: 'a
|
|
|
454
493
|
|
|
455
494
|
### Auto-Reconnect
|
|
456
495
|
|
|
457
|
-
`getChats`, `getMessages`, and `
|
|
496
|
+
`getChats`, `getMessages`, `sendMessage`, and `sendAttachment` (plus the underlying typed helpers `sendPhoto` / `sendVideo` / `sendAudio` / `sendFile` / `sendMultiPhoto`) 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.
|
|
458
497
|
|
|
459
|
-
Reconnect only triggers on actual session death. Operation-level errors (invalid chat ID, server rejection, etc.) are thrown immediately without retry, so side effects like `sendMessage` are never duplicated.
|
|
498
|
+
Reconnect only triggers on actual session death. Operation-level errors (invalid chat ID, server rejection, etc.) are thrown immediately without retry, so side effects like `sendMessage` or `sendAttachment` are never duplicated.
|
|
460
499
|
|
|
461
500
|
## See Also
|
|
462
501
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { planAttachments, resolveAttachment } from './attachment-router'
|
|
4
|
+
|
|
5
|
+
const bytes = new Uint8Array([0])
|
|
6
|
+
|
|
7
|
+
describe('resolveAttachment', () => {
|
|
8
|
+
it('classifies image MIME as photo', () => {
|
|
9
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.jpg' }).kind).toBe('photo')
|
|
10
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.png', mime: 'image/png' }).kind).toBe('photo')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('classifies video MIME as video', () => {
|
|
14
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.mp4' }).kind).toBe('video')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('classifies audio MIME as audio', () => {
|
|
18
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.m4a' }).kind).toBe('audio')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('classifies everything else as file', () => {
|
|
22
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.pdf' }).kind).toBe('file')
|
|
23
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.zip' }).kind).toBe('file')
|
|
24
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.unknown-ext' }).kind).toBe('file')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('uses explicit mime override over filename inference', () => {
|
|
28
|
+
const r = resolveAttachment({ data: bytes, filename: 'x.pdf', mime: 'image/png' })
|
|
29
|
+
expect(r.kind).toBe('photo')
|
|
30
|
+
expect(r.mime).toBe('image/png')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('routes upper-case and mixed-case MIME overrides the same as lower-case', () => {
|
|
34
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.bin', mime: 'IMAGE/JPEG' }).kind).toBe('photo')
|
|
35
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.bin', mime: 'Video/MP4' }).kind).toBe('video')
|
|
36
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.bin', mime: 'Audio/MPEG' }).kind).toBe('audio')
|
|
37
|
+
expect(resolveAttachment({ data: bytes, filename: 'x.bin', mime: 'IMAGE/PNG' }).mime).toBe('image/png')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('preserves data and filename verbatim', () => {
|
|
41
|
+
const r = resolveAttachment({ data: bytes, filename: 'cat picture.jpg' })
|
|
42
|
+
expect(r.data).toBe(bytes)
|
|
43
|
+
expect(r.filename).toBe('cat picture.jpg')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('planAttachments', () => {
|
|
48
|
+
it('throws on empty array', () => {
|
|
49
|
+
expect(() => planAttachments([])).toThrow(/empty/i)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('returns single for a one-element array', () => {
|
|
53
|
+
const plan = planAttachments([{ data: bytes, filename: 'x.jpg' }])
|
|
54
|
+
expect(plan.kind).toBe('single')
|
|
55
|
+
if (plan.kind !== 'single') return
|
|
56
|
+
expect(plan.resolved.kind).toBe('photo')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('returns single (not multiphoto) for a one-photo array', () => {
|
|
60
|
+
const plan = planAttachments([{ data: bytes, filename: 'a.png' }])
|
|
61
|
+
expect(plan.kind).toBe('single')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('returns multiphoto when every item resolves to photo', () => {
|
|
65
|
+
const plan = planAttachments([
|
|
66
|
+
{ data: bytes, filename: 'a.jpg' },
|
|
67
|
+
{ data: bytes, filename: 'b.png' },
|
|
68
|
+
{ data: bytes, filename: 'c.webp' },
|
|
69
|
+
])
|
|
70
|
+
expect(plan.kind).toBe('multiphoto')
|
|
71
|
+
if (plan.kind !== 'multiphoto') return
|
|
72
|
+
expect(plan.items.length).toBe(3)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('returns sequential for mixed kinds (image + file)', () => {
|
|
76
|
+
const plan = planAttachments([
|
|
77
|
+
{ data: bytes, filename: 'photo.jpg' },
|
|
78
|
+
{ data: bytes, filename: 'spec.pdf' },
|
|
79
|
+
])
|
|
80
|
+
expect(plan.kind).toBe('sequential')
|
|
81
|
+
if (plan.kind !== 'sequential') return
|
|
82
|
+
expect(plan.resolved.map((r) => r.kind)).toEqual(['photo', 'file'])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('returns sequential for all-video (multiphoto is image-only)', () => {
|
|
86
|
+
const plan = planAttachments([
|
|
87
|
+
{ data: bytes, filename: 'a.mp4' },
|
|
88
|
+
{ data: bytes, filename: 'b.mp4' },
|
|
89
|
+
])
|
|
90
|
+
expect(plan.kind).toBe('sequential')
|
|
91
|
+
if (plan.kind !== 'sequential') return
|
|
92
|
+
expect(plan.resolved.every((r) => r.kind === 'video')).toBe(true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('honors explicit mime overrides when classifying for the photo gate', () => {
|
|
96
|
+
const plan = planAttachments([
|
|
97
|
+
{ data: bytes, filename: 'a.pdf', mime: 'image/jpeg' },
|
|
98
|
+
{ data: bytes, filename: 'b.bin', mime: 'image/png' },
|
|
99
|
+
])
|
|
100
|
+
expect(plan.kind).toBe('multiphoto')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { guessMimeFromFilename } from './media-upload'
|
|
2
|
+
|
|
3
|
+
export type AttachmentInput = {
|
|
4
|
+
data: Uint8Array | Buffer
|
|
5
|
+
filename: string
|
|
6
|
+
mime?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type SingleAttachmentKind = 'photo' | 'video' | 'audio' | 'file'
|
|
10
|
+
|
|
11
|
+
export type ResolvedAttachment = {
|
|
12
|
+
kind: SingleAttachmentKind
|
|
13
|
+
mime: string
|
|
14
|
+
data: Uint8Array | Buffer
|
|
15
|
+
filename: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type AttachmentPlan =
|
|
19
|
+
| { kind: 'single'; resolved: ResolvedAttachment }
|
|
20
|
+
| { kind: 'multiphoto'; items: readonly AttachmentInput[] }
|
|
21
|
+
| { kind: 'sequential'; resolved: readonly ResolvedAttachment[] }
|
|
22
|
+
|
|
23
|
+
export function resolveAttachment(input: AttachmentInput): ResolvedAttachment {
|
|
24
|
+
// MIME types are case-insensitive per RFC 2045 §5.1; normalize so an `Image/JPEG`
|
|
25
|
+
// override still routes to `photo` instead of falling through to `file`.
|
|
26
|
+
const mime = (input.mime ?? guessMimeFromFilename(input.filename)).toLowerCase()
|
|
27
|
+
const kind: SingleAttachmentKind = mime.startsWith('image/')
|
|
28
|
+
? 'photo'
|
|
29
|
+
: mime.startsWith('video/')
|
|
30
|
+
? 'video'
|
|
31
|
+
: mime.startsWith('audio/')
|
|
32
|
+
? 'audio'
|
|
33
|
+
: 'file'
|
|
34
|
+
return { kind, mime, data: input.data, filename: input.filename }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function planAttachments(items: readonly AttachmentInput[]): AttachmentPlan {
|
|
38
|
+
if (items.length === 0) {
|
|
39
|
+
throw new Error('sendAttachment received an empty attachments array')
|
|
40
|
+
}
|
|
41
|
+
if (items.length === 1) {
|
|
42
|
+
return { kind: 'single', resolved: resolveAttachment(items[0]!) }
|
|
43
|
+
}
|
|
44
|
+
const resolved = items.map(resolveAttachment)
|
|
45
|
+
// MULTIPHOTO (message_type 27) is image-only by KakaoTalk's wire protocol.
|
|
46
|
+
if (resolved.every((r) => r.kind === 'photo')) {
|
|
47
|
+
return { kind: 'multiphoto', items: items.slice() }
|
|
48
|
+
}
|
|
49
|
+
return { kind: 'sequential', resolved }
|
|
50
|
+
}
|