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.
- package/.claude-plugin/README.md +11 -1
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/AGENTS.md +4 -0
- package/README.md +40 -9
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +1 -1
- package/dist/src/platforms/imessage/cli.d.ts +5 -0
- package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
- package/dist/src/platforms/imessage/cli.js +31 -0
- package/dist/src/platforms/imessage/cli.js.map +1 -0
- package/dist/src/platforms/imessage/client.d.ts +43 -0
- package/dist/src/platforms/imessage/client.d.ts.map +1 -0
- package/dist/src/platforms/imessage/client.js +190 -0
- package/dist/src/platforms/imessage/client.js.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.js +100 -0
- package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.js +42 -0
- package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.js +101 -0
- package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
- package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
- package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/index.js +7 -0
- package/dist/src/platforms/imessage/commands/index.js.map +1 -0
- package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
- package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/message.js +127 -0
- package/dist/src/platforms/imessage/commands/message.js.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.js +57 -0
- package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.js +53 -0
- package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.js +20 -0
- package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.js +111 -0
- package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.js +13 -0
- package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
- package/dist/src/platforms/imessage/errors.d.ts +3 -0
- package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
- package/dist/src/platforms/imessage/errors.js +25 -0
- package/dist/src/platforms/imessage/errors.js.map +1 -0
- package/dist/src/platforms/imessage/index.d.ts +7 -0
- package/dist/src/platforms/imessage/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/index.js +5 -0
- package/dist/src/platforms/imessage/index.js.map +1 -0
- package/dist/src/platforms/imessage/rpc.d.ts +20 -0
- package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
- package/dist/src/platforms/imessage/rpc.js +133 -0
- package/dist/src/platforms/imessage/rpc.js.map +1 -0
- package/dist/src/platforms/imessage/types.d.ts +59 -0
- package/dist/src/platforms/imessage/types.d.ts.map +1 -0
- package/dist/src/platforms/imessage/types.js +29 -0
- package/dist/src/platforms/imessage/types.js.map +1 -0
- package/dist/src/platforms/instagram/cli.js +1 -1
- package/dist/src/platforms/instagram/client.d.ts +6 -1
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +17 -0
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
- package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
- package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
- package/dist/src/platforms/instagram/index.d.ts +2 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +2 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
- package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
- package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
- package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.js +151 -0
- package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
- package/dist/src/platforms/kakaotalk/cli.js +1 -1
- package/dist/src/platforms/line/cli.js +1 -1
- package/dist/src/platforms/webex/cli.js +1 -1
- package/dist/src/platforms/webexbot/cli.js +1 -1
- package/dist/src/platforms/wechatbot/cli.js +1 -1
- package/dist/src/platforms/whatsapp/cli.js +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +1 -1
- package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
- package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/imessage-adapter.js +110 -0
- package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +9 -0
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +1 -1
- package/docs/content/docs/cli/imessage.mdx +99 -0
- package/docs/content/docs/cli/instagram.mdx +1 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/index.mdx +9 -6
- package/docs/content/docs/quick-start.mdx +2 -0
- package/docs/content/docs/sdk/instagram.mdx +90 -5
- package/docs/content/docs/tui.mdx +4 -3
- package/package.json +10 -2
- package/scripts/postbuild.ts +12 -15
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +133 -0
- package/skills/agent-imessage/references/authentication.md +66 -0
- package/skills/agent-imessage/references/permissions.md +31 -0
- package/skills/agent-imessage/references/setup.md +49 -0
- package/skills/agent-instagram/SKILL.md +72 -2
- package/skills/agent-instagram/references/common-patterns.md +36 -0
- package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +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-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/imessage/cli.ts +39 -0
- package/src/platforms/imessage/client.test.ts +137 -0
- package/src/platforms/imessage/client.ts +253 -0
- package/src/platforms/imessage/commands/auth.ts +142 -0
- package/src/platforms/imessage/commands/chat.ts +51 -0
- package/src/platforms/imessage/commands/doctor.test.ts +80 -0
- package/src/platforms/imessage/commands/doctor.ts +139 -0
- package/src/platforms/imessage/commands/index.ts +6 -0
- package/src/platforms/imessage/commands/message.test.ts +20 -0
- package/src/platforms/imessage/commands/message.ts +151 -0
- package/src/platforms/imessage/commands/setup.test.ts +52 -0
- package/src/platforms/imessage/commands/setup.ts +76 -0
- package/src/platforms/imessage/commands/shared.test.ts +19 -0
- package/src/platforms/imessage/commands/shared.ts +69 -0
- package/src/platforms/imessage/commands/whoami.ts +22 -0
- package/src/platforms/imessage/credential-manager.test.ts +122 -0
- package/src/platforms/imessage/credential-manager.ts +123 -0
- package/src/platforms/imessage/ensure-auth.ts +18 -0
- package/src/platforms/imessage/errors.ts +27 -0
- package/src/platforms/imessage/index.test.ts +13 -0
- package/src/platforms/imessage/index.ts +16 -0
- package/src/platforms/imessage/rpc.ts +157 -0
- package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
- package/src/platforms/imessage/types.test.ts +47 -0
- package/src/platforms/imessage/types.ts +95 -0
- package/src/platforms/instagram/client.ts +23 -0
- package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
- package/src/platforms/instagram/hybrid-listener.ts +196 -0
- package/src/platforms/instagram/index.ts +10 -0
- package/src/platforms/instagram/mqtt/connection.ts +72 -0
- package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
- package/src/platforms/instagram/mqtt/thrift.ts +159 -0
- package/src/platforms/instagram/mqtt/transport.ts +285 -0
- package/src/platforms/instagram/realtime-listener.test.ts +34 -0
- package/src/platforms/instagram/realtime-listener.ts +201 -0
- package/src/platforms/webexbot/cli.ts +0 -0
- package/src/tui/adapters/imessage-adapter.ts +131 -0
- 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',
|