agent-messenger 2.27.2 → 2.28.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 (189) hide show
  1. package/.claude-plugin/README.md +11 -1
  2. package/.claude-plugin/marketplace.json +14 -1
  3. package/.claude-plugin/plugin.json +4 -2
  4. package/AGENTS.md +4 -0
  5. package/README.md +40 -9
  6. package/dist/package.json +10 -2
  7. package/dist/src/cli.d.ts.map +1 -1
  8. package/dist/src/cli.js +3 -0
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/platforms/discordbot/cli.js +1 -1
  11. package/dist/src/platforms/imessage/cli.d.ts +5 -0
  12. package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
  13. package/dist/src/platforms/imessage/cli.js +31 -0
  14. package/dist/src/platforms/imessage/cli.js.map +1 -0
  15. package/dist/src/platforms/imessage/client.d.ts +43 -0
  16. package/dist/src/platforms/imessage/client.d.ts.map +1 -0
  17. package/dist/src/platforms/imessage/client.js +190 -0
  18. package/dist/src/platforms/imessage/client.js.map +1 -0
  19. package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
  20. package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
  21. package/dist/src/platforms/imessage/commands/auth.js +100 -0
  22. package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
  23. package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
  24. package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
  25. package/dist/src/platforms/imessage/commands/chat.js +42 -0
  26. package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
  27. package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
  28. package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
  29. package/dist/src/platforms/imessage/commands/doctor.js +101 -0
  30. package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
  31. package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
  32. package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
  33. package/dist/src/platforms/imessage/commands/index.js +7 -0
  34. package/dist/src/platforms/imessage/commands/index.js.map +1 -0
  35. package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
  36. package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
  37. package/dist/src/platforms/imessage/commands/message.js +127 -0
  38. package/dist/src/platforms/imessage/commands/message.js.map +1 -0
  39. package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
  40. package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
  41. package/dist/src/platforms/imessage/commands/setup.js +57 -0
  42. package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
  43. package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
  44. package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
  45. package/dist/src/platforms/imessage/commands/shared.js +53 -0
  46. package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
  47. package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
  48. package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
  49. package/dist/src/platforms/imessage/commands/whoami.js +20 -0
  50. package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
  51. package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
  52. package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
  53. package/dist/src/platforms/imessage/credential-manager.js +111 -0
  54. package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
  55. package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
  56. package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
  57. package/dist/src/platforms/imessage/ensure-auth.js +13 -0
  58. package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
  59. package/dist/src/platforms/imessage/errors.d.ts +3 -0
  60. package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
  61. package/dist/src/platforms/imessage/errors.js +25 -0
  62. package/dist/src/platforms/imessage/errors.js.map +1 -0
  63. package/dist/src/platforms/imessage/index.d.ts +7 -0
  64. package/dist/src/platforms/imessage/index.d.ts.map +1 -0
  65. package/dist/src/platforms/imessage/index.js +5 -0
  66. package/dist/src/platforms/imessage/index.js.map +1 -0
  67. package/dist/src/platforms/imessage/rpc.d.ts +20 -0
  68. package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
  69. package/dist/src/platforms/imessage/rpc.js +133 -0
  70. package/dist/src/platforms/imessage/rpc.js.map +1 -0
  71. package/dist/src/platforms/imessage/types.d.ts +59 -0
  72. package/dist/src/platforms/imessage/types.d.ts.map +1 -0
  73. package/dist/src/platforms/imessage/types.js +29 -0
  74. package/dist/src/platforms/imessage/types.js.map +1 -0
  75. package/dist/src/platforms/instagram/cli.js +1 -1
  76. package/dist/src/platforms/instagram/client.d.ts +6 -1
  77. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  78. package/dist/src/platforms/instagram/client.js +17 -0
  79. package/dist/src/platforms/instagram/client.js.map +1 -1
  80. package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
  81. package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
  82. package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
  83. package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
  84. package/dist/src/platforms/instagram/index.d.ts +2 -0
  85. package/dist/src/platforms/instagram/index.d.ts.map +1 -1
  86. package/dist/src/platforms/instagram/index.js +2 -0
  87. package/dist/src/platforms/instagram/index.js.map +1 -1
  88. package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
  89. package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
  90. package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
  91. package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
  92. package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
  93. package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
  94. package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
  95. package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
  96. package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
  97. package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
  98. package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
  99. package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
  100. package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
  101. package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
  102. package/dist/src/platforms/instagram/realtime-listener.js +151 -0
  103. package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
  104. package/dist/src/platforms/kakaotalk/cli.js +1 -1
  105. package/dist/src/platforms/line/cli.js +1 -1
  106. package/dist/src/platforms/webex/cli.js +1 -1
  107. package/dist/src/platforms/webexbot/cli.js +1 -1
  108. package/dist/src/platforms/wechatbot/cli.js +1 -1
  109. package/dist/src/platforms/whatsapp/cli.js +1 -1
  110. package/dist/src/platforms/whatsappbot/cli.js +1 -1
  111. package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
  112. package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
  113. package/dist/src/tui/adapters/imessage-adapter.js +110 -0
  114. package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
  115. package/dist/src/tui/app.d.ts.map +1 -1
  116. package/dist/src/tui/app.js +9 -0
  117. package/dist/src/tui/app.js.map +1 -1
  118. package/docs/content/docs/agent-skills.mdx +1 -1
  119. package/docs/content/docs/cli/imessage.mdx +99 -0
  120. package/docs/content/docs/cli/instagram.mdx +1 -1
  121. package/docs/content/docs/cli/meta.json +1 -0
  122. package/docs/content/docs/index.mdx +9 -6
  123. package/docs/content/docs/quick-start.mdx +2 -0
  124. package/docs/content/docs/sdk/instagram.mdx +90 -5
  125. package/docs/content/docs/tui.mdx +4 -3
  126. package/package.json +10 -2
  127. package/scripts/postbuild.ts +12 -15
  128. package/skills/agent-channeltalk/SKILL.md +1 -1
  129. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  130. package/skills/agent-discord/SKILL.md +1 -1
  131. package/skills/agent-discordbot/SKILL.md +1 -1
  132. package/skills/agent-imessage/SKILL.md +133 -0
  133. package/skills/agent-imessage/references/authentication.md +66 -0
  134. package/skills/agent-imessage/references/permissions.md +31 -0
  135. package/skills/agent-imessage/references/setup.md +49 -0
  136. package/skills/agent-instagram/SKILL.md +72 -2
  137. package/skills/agent-instagram/references/common-patterns.md +36 -0
  138. package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
  139. package/skills/agent-kakaotalk/SKILL.md +1 -1
  140. package/skills/agent-line/SKILL.md +1 -1
  141. package/skills/agent-slack/SKILL.md +1 -1
  142. package/skills/agent-slackbot/SKILL.md +1 -1
  143. package/skills/agent-teams/SKILL.md +1 -1
  144. package/skills/agent-telegram/SKILL.md +1 -1
  145. package/skills/agent-telegrambot/SKILL.md +1 -1
  146. package/skills/agent-webex/SKILL.md +1 -1
  147. package/skills/agent-webexbot/SKILL.md +1 -1
  148. package/skills/agent-wechatbot/SKILL.md +1 -1
  149. package/skills/agent-whatsapp/SKILL.md +1 -1
  150. package/skills/agent-whatsappbot/SKILL.md +1 -1
  151. package/src/cli.ts +4 -0
  152. package/src/platforms/imessage/cli.ts +39 -0
  153. package/src/platforms/imessage/client.test.ts +137 -0
  154. package/src/platforms/imessage/client.ts +253 -0
  155. package/src/platforms/imessage/commands/auth.ts +142 -0
  156. package/src/platforms/imessage/commands/chat.ts +51 -0
  157. package/src/platforms/imessage/commands/doctor.test.ts +80 -0
  158. package/src/platforms/imessage/commands/doctor.ts +139 -0
  159. package/src/platforms/imessage/commands/index.ts +6 -0
  160. package/src/platforms/imessage/commands/message.test.ts +20 -0
  161. package/src/platforms/imessage/commands/message.ts +151 -0
  162. package/src/platforms/imessage/commands/setup.test.ts +52 -0
  163. package/src/platforms/imessage/commands/setup.ts +76 -0
  164. package/src/platforms/imessage/commands/shared.test.ts +19 -0
  165. package/src/platforms/imessage/commands/shared.ts +69 -0
  166. package/src/platforms/imessage/commands/whoami.ts +22 -0
  167. package/src/platforms/imessage/credential-manager.test.ts +122 -0
  168. package/src/platforms/imessage/credential-manager.ts +123 -0
  169. package/src/platforms/imessage/ensure-auth.ts +18 -0
  170. package/src/platforms/imessage/errors.ts +27 -0
  171. package/src/platforms/imessage/index.test.ts +13 -0
  172. package/src/platforms/imessage/index.ts +16 -0
  173. package/src/platforms/imessage/rpc.ts +157 -0
  174. package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
  175. package/src/platforms/imessage/types.test.ts +47 -0
  176. package/src/platforms/imessage/types.ts +95 -0
  177. package/src/platforms/instagram/client.ts +23 -0
  178. package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
  179. package/src/platforms/instagram/hybrid-listener.ts +196 -0
  180. package/src/platforms/instagram/index.ts +10 -0
  181. package/src/platforms/instagram/mqtt/connection.ts +72 -0
  182. package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
  183. package/src/platforms/instagram/mqtt/thrift.ts +159 -0
  184. package/src/platforms/instagram/mqtt/transport.ts +285 -0
  185. package/src/platforms/instagram/realtime-listener.test.ts +34 -0
  186. package/src/platforms/instagram/realtime-listener.ts +201 -0
  187. package/src/platforms/webexbot/cli.ts +0 -0
  188. package/src/tui/adapters/imessage-adapter.ts +131 -0
  189. package/src/tui/app.ts +13 -0
@@ -0,0 +1,201 @@
1
+ import { EventEmitter } from 'node:events'
2
+ import { deflateSync, inflateSync } from 'node:zlib'
3
+
4
+ import type { InstagramClient } from './client'
5
+ import { buildConnectPayload } from './mqtt/connection'
6
+ import { MqttTransport } from './mqtt/transport'
7
+ import { extractMediaUrl, extractMessageText, getMessageType, type InstagramMessageSummary } from './types'
8
+
9
+ export interface InstagramRealtimeListenerEventMap {
10
+ message: [InstagramMessageSummary]
11
+ error: [Error]
12
+ connected: [{ userId: string }]
13
+ disconnected: []
14
+ }
15
+
16
+ export interface InstagramRealtimeListenerOptions {
17
+ connackTimeoutMs?: number
18
+ }
19
+
20
+ type EventKey = keyof InstagramRealtimeListenerEventMap
21
+
22
+ const TOPIC_PUBSUB = '88'
23
+ const TOPIC_REALTIME_SUB = '149'
24
+ const TOPIC_IRIS_SUB = '134'
25
+ const TOPIC_MESSAGE_SYNC = '146'
26
+
27
+ const ZLIB_MAGIC_BYTE = 0x78
28
+
29
+ const DM_THREAD_PATH_PATTERN = /^\/direct_v2\/(?:inbox\/)?threads\//
30
+ const THREAD_ID_PATTERN = /^\/direct_v2\/(?:inbox\/)?threads\/(\d+)/
31
+
32
+ interface IrisEnvelope {
33
+ data?: IrisPatch[]
34
+ }
35
+
36
+ interface IrisPatch {
37
+ op?: string
38
+ path?: string
39
+ value?: string
40
+ }
41
+
42
+ export class InstagramRealtimeListener {
43
+ private client: InstagramClient
44
+ private transport: MqttTransport | null = null
45
+ private emitter = new EventEmitter()
46
+ private running = false
47
+ private userId: string
48
+ private connackTimeoutMs: number | undefined
49
+
50
+ constructor(client: InstagramClient, options: InstagramRealtimeListenerOptions = {}) {
51
+ this.client = client
52
+ this.userId = client.getUserId() ?? ''
53
+ this.connackTimeoutMs = options.connackTimeoutMs
54
+ }
55
+
56
+ async start(): Promise<void> {
57
+ if (this.running) return
58
+ this.running = true
59
+
60
+ try {
61
+ const session = this.client.getSessionState()
62
+ const iris = await this.client.fetchIrisBootstrap()
63
+
64
+ if (!this.running) return
65
+
66
+ const connectPayload = buildConnectPayload(session)
67
+
68
+ const transport = new MqttTransport({ connackTimeoutMs: this.connackTimeoutMs })
69
+ this.transport = transport
70
+
71
+ transport.on('connect', () => {
72
+ this.sendSubscriptions(iris)
73
+ this.emitter.emit('connected', { userId: this.userId })
74
+ })
75
+ transport.on('publish', ({ topic, payload }) => this.handlePublish(topic, payload))
76
+ transport.on('error', (err) => this.emitter.emit('error', err))
77
+ transport.on('close', () => {
78
+ if (this.running) this.emitter.emit('disconnected')
79
+ })
80
+
81
+ transport.connect(connectPayload)
82
+ } catch (error) {
83
+ // Any setup failure must leave the listener cleanly restartable: reset running
84
+ // and discard the partial transport so a later start() does not no-op at the guard.
85
+ this.running = false
86
+ if (this.transport) {
87
+ this.transport.disconnect()
88
+ this.transport = null
89
+ }
90
+ throw error
91
+ }
92
+ }
93
+
94
+ stop(): void {
95
+ this.running = false
96
+ if (this.transport) {
97
+ this.transport.disconnect()
98
+ this.transport = null
99
+ }
100
+ this.emitter.emit('disconnected')
101
+ }
102
+
103
+ on<K extends EventKey>(event: K, listener: (...args: InstagramRealtimeListenerEventMap[K]) => void): this {
104
+ this.emitter.on(event, listener as (...args: unknown[]) => void)
105
+ return this
106
+ }
107
+
108
+ off<K extends EventKey>(event: K, listener: (...args: InstagramRealtimeListenerEventMap[K]) => void): this {
109
+ this.emitter.off(event, listener as (...args: unknown[]) => void)
110
+ return this
111
+ }
112
+
113
+ once<K extends EventKey>(event: K, listener: (...args: InstagramRealtimeListenerEventMap[K]) => void): this {
114
+ this.emitter.once(event, listener as (...args: unknown[]) => void)
115
+ return this
116
+ }
117
+
118
+ private sendSubscriptions(iris: { seqId: number; snapshotAtMs: number }): void {
119
+ const transport = this.transport
120
+ if (!transport) return
121
+
122
+ transport.publish(
123
+ TOPIC_IRIS_SUB,
124
+ deflateJson({
125
+ seq_id: iris.seqId,
126
+ snapshot_at_ms: iris.snapshotAtMs,
127
+ snapshot_app_version: APP_VERSION,
128
+ }),
129
+ )
130
+
131
+ transport.publish(TOPIC_PUBSUB, deflateJson({ sub: [`ig/u/v1/${this.userId}`] }))
132
+
133
+ transport.publish(
134
+ TOPIC_REALTIME_SUB,
135
+ deflateJson({
136
+ sub: [`1/graphqlsubscriptions/17867973967082385/{"input_data":{"user_id":"${this.userId}"}}`],
137
+ }),
138
+ )
139
+ }
140
+
141
+ private handlePublish(topic: string, payload: Buffer): void {
142
+ if (topic !== TOPIC_MESSAGE_SYNC) return
143
+
144
+ let envelopes: IrisEnvelope[]
145
+ try {
146
+ envelopes = JSON.parse(inflateIfNeeded(payload).toString('utf8')) as IrisEnvelope[]
147
+ } catch (error) {
148
+ this.emitter.emit('error', error instanceof Error ? error : new Error(String(error)))
149
+ return
150
+ }
151
+
152
+ for (const envelope of envelopes) {
153
+ for (const patch of envelope.data ?? []) {
154
+ const message = this.decodeDmPatch(patch)
155
+ if (message) this.emitter.emit('message', message)
156
+ }
157
+ }
158
+ }
159
+
160
+ private decodeDmPatch(patch: IrisPatch): InstagramMessageSummary | null {
161
+ if (patch.op !== 'add') return null
162
+ if (!patch.path || !patch.value || !DM_THREAD_PATH_PATTERN.test(patch.path)) return null
163
+
164
+ const threadId = patch.path.match(THREAD_ID_PATTERN)?.[1] ?? ''
165
+
166
+ let item: Record<string, unknown>
167
+ try {
168
+ item = JSON.parse(patch.value) as Record<string, unknown>
169
+ } catch {
170
+ return null
171
+ }
172
+
173
+ const itemId = String(item['item_id'] ?? '')
174
+ const fromUserId = String(item['user_id'] ?? '')
175
+ const timestampUs = item['timestamp'] as number | string | undefined
176
+
177
+ return {
178
+ id: itemId,
179
+ thread_id: threadId,
180
+ from: fromUserId,
181
+ timestamp: timestampUs ? new Date(Number(timestampUs) / 1000).toISOString() : new Date().toISOString(),
182
+ is_outgoing: fromUserId === this.userId,
183
+ type: getMessageType(item),
184
+ text: extractMessageText(item),
185
+ media_url: extractMediaUrl(item),
186
+ }
187
+ }
188
+ }
189
+
190
+ const APP_VERSION = '312.1.0.34.111'
191
+
192
+ function deflateJson(value: unknown): Buffer {
193
+ return deflateSync(JSON.stringify(value), { level: 9 })
194
+ }
195
+
196
+ function inflateIfNeeded(payload: Buffer): Buffer {
197
+ if (payload.length > 0 && payload[0] === ZLIB_MAGIC_BYTE) {
198
+ return inflateSync(payload)
199
+ }
200
+ return payload
201
+ }
File without changes
@@ -0,0 +1,131 @@
1
+ import { ImsgClient } from '@/platforms/imessage/client'
2
+ import { IMessageCredentialManager } from '@/platforms/imessage/credential-manager'
3
+
4
+ import type { AuthHint, AuthIO, PlatformAdapter, UnifiedChannel, UnifiedMessage, Workspace } from './types'
5
+
6
+ export class IMessageAdapter implements PlatformAdapter {
7
+ readonly name = 'iMessage'
8
+
9
+ private client: ImsgClient | null = null
10
+ private credManager = new IMessageCredentialManager()
11
+ private currentAccount: Workspace | null = null
12
+ private stopWatch: (() => Promise<void>) | null = null
13
+
14
+ async login(): Promise<void> {
15
+ const resolved = await this.credManager.resolveAccount()
16
+ if (!resolved) {
17
+ throw new Error('iMessage is not configured. Run "agent-imessage setup" or set AGENT_IMESSAGE_BIN.')
18
+ }
19
+ const client = new ImsgClient()
20
+ await client.login({ binaryPath: resolved.binary_path, region: resolved.region })
21
+ await client.connect()
22
+ this.client = client
23
+
24
+ const config = await this.credManager.loadConfig()
25
+ if (config.current && config.accounts[config.current]) {
26
+ const acct = config.accounts[config.current]
27
+ this.currentAccount = { id: acct.account_id, name: acct.label ?? acct.account_id }
28
+ }
29
+ }
30
+
31
+ async getChannels(): Promise<UnifiedChannel[]> {
32
+ const client = this.ensureClient()
33
+ const chats = await client.listChats(50)
34
+ return chats.map((chat) => ({ id: String(chat.id), name: chat.name }))
35
+ }
36
+
37
+ async getMessages(channelId: string, limit = 50): Promise<UnifiedMessage[]> {
38
+ const client = this.ensureClient()
39
+ const messages = await client.getMessages(Number.parseInt(channelId, 10), limit)
40
+ return messages.map((msg) => ({
41
+ id: msg.guid,
42
+ channelId,
43
+ author: msg.is_outgoing ? 'you' : (msg.from_name ?? msg.from ?? 'unknown'),
44
+ content: msg.text ?? '',
45
+ timestamp: msg.timestamp,
46
+ }))
47
+ }
48
+
49
+ async sendMessage(channelId: string, text: string): Promise<void> {
50
+ const client = this.ensureClient()
51
+ await client.sendMessage({ chatId: Number.parseInt(channelId, 10) }, text)
52
+ }
53
+
54
+ async startListening(onMessage: (msg: UnifiedMessage) => void): Promise<void> {
55
+ const client = this.ensureClient()
56
+ this.stopWatch = await client.watch(
57
+ (msg) =>
58
+ onMessage({
59
+ id: msg.guid,
60
+ channelId: String(msg.chat_id),
61
+ author: msg.is_outgoing ? 'you' : (msg.from_name ?? msg.from ?? 'unknown'),
62
+ content: msg.text ?? '',
63
+ timestamp: msg.timestamp,
64
+ }),
65
+ { onError: () => undefined },
66
+ )
67
+ }
68
+
69
+ stopListening(): void {
70
+ void this.stopWatch?.()
71
+ this.stopWatch = null
72
+ }
73
+
74
+ async getWorkspaces(): Promise<Workspace[]> {
75
+ const accounts = await this.credManager.listAccounts()
76
+ return accounts.map((acct) => ({ id: acct.account_id, name: acct.label ?? acct.account_id }))
77
+ }
78
+
79
+ async switchWorkspace(accountId: string): Promise<void> {
80
+ if (this.client) {
81
+ await this.client.close().catch(() => {})
82
+ this.client = null
83
+ }
84
+ const account = await this.credManager.getAccount(accountId)
85
+ if (!account) throw new Error(`Account ${accountId} not found`)
86
+
87
+ const client = new ImsgClient()
88
+ await client.login({ binaryPath: account.binary_path, region: account.region })
89
+ await client.connect()
90
+ this.client = client
91
+ this.currentAccount = { id: account.account_id, name: account.label ?? account.account_id }
92
+ }
93
+
94
+ getCurrentWorkspace(): Workspace | null {
95
+ return this.currentAccount
96
+ }
97
+
98
+ getAuthHint(): AuthHint {
99
+ return {
100
+ command: 'agent-imessage setup',
101
+ description: 'iMessage runs on this Mac via the imsg tool. Run the guided setup below.',
102
+ }
103
+ }
104
+
105
+ async authenticate(io: AuthIO): Promise<void> {
106
+ const bin = (await io.prompt('Path to imsg binary (blank for "imsg" on PATH)')).trim() || undefined
107
+ const client = new ImsgClient()
108
+ await client.login({ binaryPath: bin })
109
+ await client.connect()
110
+
111
+ const { createAccountId } = await import('@/platforms/imessage/types')
112
+ const now = new Date().toISOString()
113
+ const accountId = createAccountId(bin ?? 'default')
114
+ await this.credManager.setAccount({
115
+ account_id: accountId,
116
+ provider: 'imsg',
117
+ binary_path: bin,
118
+ created_at: now,
119
+ updated_at: now,
120
+ })
121
+ await this.credManager.setCurrent(accountId)
122
+
123
+ this.client = client
124
+ this.currentAccount = { id: accountId, name: accountId }
125
+ }
126
+
127
+ private ensureClient(): ImsgClient {
128
+ if (!this.client) throw new Error('Not logged in. Call login() first.')
129
+ return this.client
130
+ }
131
+ }
package/src/tui/app.ts CHANGED
@@ -182,6 +182,19 @@ export async function createApp(): Promise<void> {
182
182
  listening: false,
183
183
  lastChannelId: null,
184
184
  },
185
+ {
186
+ adapter: lazyAdapter(
187
+ 'iMessage',
188
+ 'agent-imessage setup',
189
+ async () => new (await import('./adapters/imessage-adapter')).IMessageAdapter(),
190
+ ),
191
+ label: 'iMessage',
192
+ enabled: false,
193
+ channels: null,
194
+ workspaces: null,
195
+ listening: false,
196
+ lastChannelId: null,
197
+ },
185
198
  {
186
199
  adapter: lazyAdapter(
187
200
  'LINE',