@plosson/agentio 0.4.2 → 0.4.3
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/README.md +4 -4
- package/package.json +3 -1
- package/src/auth/oauth.ts +14 -2
- package/src/commands/gateway.ts +259 -0
- package/src/commands/gcal.ts +383 -0
- package/src/commands/gtasks.ts +326 -0
- package/src/commands/status.ts +85 -0
- package/src/commands/telegram.ts +209 -1
- package/src/commands/update.ts +2 -2
- package/src/commands/whatsapp.ts +853 -0
- package/src/config/config-manager.ts +1 -1
- package/src/gateway/adapters/telegram.ts +357 -0
- package/src/gateway/adapters/types.ts +147 -0
- package/src/gateway/adapters/whatsapp-auth.ts +172 -0
- package/src/gateway/adapters/whatsapp.ts +723 -0
- package/src/gateway/api.ts +791 -0
- package/src/gateway/client.ts +402 -0
- package/src/gateway/daemon.ts +461 -0
- package/src/gateway/store.ts +637 -0
- package/src/gateway/types.ts +325 -0
- package/src/gateway/webhook.ts +109 -0
- package/src/index.ts +32 -16
- package/src/polyfills.ts +10 -0
- package/src/services/gcal/client.ts +380 -0
- package/src/services/gtasks/client.ts +301 -0
- package/src/types/config.ts +36 -1
- package/src/types/gcal.ts +135 -0
- package/src/types/gtasks.ts +58 -0
- package/src/types/qrcode-terminal.d.ts +8 -0
- package/src/types/whatsapp.ts +116 -0
- package/src/utils/output.ts +505 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { CliError } from '../utils/errors';
|
|
2
|
+
import { loadConfig } from '../config/config-manager';
|
|
3
|
+
import { getEnv } from '../config/config-manager';
|
|
4
|
+
import type { ServiceName } from '../types/config';
|
|
5
|
+
import type {
|
|
6
|
+
GatewayConfig,
|
|
7
|
+
InboxPullRequest,
|
|
8
|
+
InboxPullResponse,
|
|
9
|
+
InboxGetRequest,
|
|
10
|
+
InboxGetResponse,
|
|
11
|
+
InboxAckRequest,
|
|
12
|
+
InboxAckResponse,
|
|
13
|
+
InboxReplyRequest,
|
|
14
|
+
InboxReplyResponse,
|
|
15
|
+
InboxStatsRequest,
|
|
16
|
+
InboxStatsResponse,
|
|
17
|
+
OutboxSendRequest,
|
|
18
|
+
OutboxSendResponse,
|
|
19
|
+
OutboxStatusRequest,
|
|
20
|
+
OutboxStatusResponse,
|
|
21
|
+
OutboxListRequest,
|
|
22
|
+
OutboxListResponse,
|
|
23
|
+
GatewayStatusResponse,
|
|
24
|
+
HealthResponse,
|
|
25
|
+
WhatsAppPairResponse,
|
|
26
|
+
InboundMessage,
|
|
27
|
+
OutboundMessage,
|
|
28
|
+
MediaType,
|
|
29
|
+
InboxStatus,
|
|
30
|
+
OutboxStatus,
|
|
31
|
+
WhatsAppGroupListRequest,
|
|
32
|
+
WhatsAppGroupListResponse,
|
|
33
|
+
WhatsAppGroupGetRequest,
|
|
34
|
+
WhatsAppGroupGetResponse,
|
|
35
|
+
WhatsAppGroupCreateRequest,
|
|
36
|
+
WhatsAppGroupCreateResponse,
|
|
37
|
+
WhatsAppGroupUpdateRequest,
|
|
38
|
+
WhatsAppGroupUpdateResponse,
|
|
39
|
+
WhatsAppGroupParticipantsRequest,
|
|
40
|
+
WhatsAppGroupParticipantsResponse,
|
|
41
|
+
WhatsAppGroupLeaveRequest,
|
|
42
|
+
WhatsAppGroupLeaveResponse,
|
|
43
|
+
WhatsAppGroupInviteRequest,
|
|
44
|
+
WhatsAppGroupInviteResponse,
|
|
45
|
+
WhatsAppGroupJoinRequest,
|
|
46
|
+
WhatsAppGroupJoinResponse,
|
|
47
|
+
WhatsAppGroupResolveRequest,
|
|
48
|
+
WhatsAppGroupResolveResponse,
|
|
49
|
+
} from './types';
|
|
50
|
+
import type { WhatsAppGroup, WhatsAppParticipantAction } from '../types/whatsapp';
|
|
51
|
+
|
|
52
|
+
let cachedConfig: { url: string; secret: string } | null = null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get gateway URL and secret from config or environment
|
|
56
|
+
*/
|
|
57
|
+
async function getGatewayConnection(): Promise<{ url: string; secret: string }> {
|
|
58
|
+
if (cachedConfig) return cachedConfig;
|
|
59
|
+
|
|
60
|
+
// Check environment variables first
|
|
61
|
+
const envUrl = process.env.AGENTIO_GATEWAY_URL || await getEnv('AGENTIO_GATEWAY_URL');
|
|
62
|
+
const envSecret = process.env.AGENTIO_GATEWAY_SECRET || await getEnv('AGENTIO_GATEWAY_SECRET');
|
|
63
|
+
|
|
64
|
+
if (envUrl) {
|
|
65
|
+
cachedConfig = { url: envUrl, secret: envSecret || '' };
|
|
66
|
+
return cachedConfig;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Load from config
|
|
70
|
+
const config = await loadConfig() as unknown as { gateway?: GatewayConfig };
|
|
71
|
+
const gatewayConfig = config.gateway;
|
|
72
|
+
|
|
73
|
+
if (!gatewayConfig?.api) {
|
|
74
|
+
throw new CliError('CONFIG_ERROR', 'Gateway not configured', 'Set AGENTIO_GATEWAY_URL or configure gateway in config');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const host = gatewayConfig.api.host ?? '127.0.0.1';
|
|
78
|
+
const port = gatewayConfig.api.port ?? 7890;
|
|
79
|
+
const url = `http://${host}:${port}`;
|
|
80
|
+
const secret = gatewayConfig.api.secret ?? '';
|
|
81
|
+
|
|
82
|
+
cachedConfig = { url, secret };
|
|
83
|
+
return cachedConfig;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Make a request to the gateway API
|
|
88
|
+
*/
|
|
89
|
+
async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
|
|
90
|
+
const { url, secret } = await getGatewayConnection();
|
|
91
|
+
|
|
92
|
+
const headers: Record<string, string> = {
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (secret) {
|
|
97
|
+
headers['Authorization'] = `Bearer ${secret}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(`${url}${endpoint}`, {
|
|
102
|
+
method,
|
|
103
|
+
headers,
|
|
104
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
if (response.status === 401) {
|
|
109
|
+
throw new CliError('AUTH_FAILED', 'Gateway authentication failed', 'Check AGENTIO_GATEWAY_SECRET');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const errorData = await response.json().catch(() => ({})) as { error?: string };
|
|
113
|
+
throw new CliError('API_ERROR', errorData.error || `Gateway error: ${response.status}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return await response.json() as T;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error instanceof CliError) throw error;
|
|
119
|
+
|
|
120
|
+
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
121
|
+
throw new CliError('NETWORK_ERROR', 'Cannot connect to gateway', 'Is the gateway running? Try: agentio gateway status');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new CliError('NETWORK_ERROR', error instanceof Error ? error.message : 'Unknown error');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Gateway client for CLI commands
|
|
130
|
+
*/
|
|
131
|
+
export class GatewayClient {
|
|
132
|
+
/**
|
|
133
|
+
* Check gateway health
|
|
134
|
+
*/
|
|
135
|
+
async health(): Promise<HealthResponse> {
|
|
136
|
+
return request<HealthResponse>('GET', '/health');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get gateway status
|
|
141
|
+
*/
|
|
142
|
+
async status(): Promise<GatewayStatusResponse> {
|
|
143
|
+
return request<GatewayStatusResponse>('GET', '/status');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Pull messages from inbox
|
|
148
|
+
*/
|
|
149
|
+
async inboxPull(options: {
|
|
150
|
+
service?: ServiceName;
|
|
151
|
+
profile?: string;
|
|
152
|
+
conversationId?: string;
|
|
153
|
+
limit?: number;
|
|
154
|
+
status?: InboxStatus;
|
|
155
|
+
}): Promise<InboundMessage[]> {
|
|
156
|
+
const body: InboxPullRequest = {
|
|
157
|
+
service: options.service,
|
|
158
|
+
profile: options.profile,
|
|
159
|
+
conversationId: options.conversationId,
|
|
160
|
+
limit: options.limit,
|
|
161
|
+
status: options.status,
|
|
162
|
+
};
|
|
163
|
+
const response = await request<InboxPullResponse>('POST', '/inbox/pull', body);
|
|
164
|
+
return response.messages;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get a specific inbox message
|
|
169
|
+
*/
|
|
170
|
+
async inboxGet(id: string): Promise<InboundMessage | null> {
|
|
171
|
+
const body: InboxGetRequest = { id };
|
|
172
|
+
const response = await request<InboxGetResponse>('POST', '/inbox/get', body);
|
|
173
|
+
return response.message;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Acknowledge (mark as done) an inbox message
|
|
178
|
+
*/
|
|
179
|
+
async inboxAck(id: string): Promise<boolean> {
|
|
180
|
+
const body: InboxAckRequest = { id };
|
|
181
|
+
const response = await request<InboxAckResponse>('POST', '/inbox/ack', body);
|
|
182
|
+
return response.success;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Reply to an inbox message
|
|
187
|
+
*/
|
|
188
|
+
async inboxReply(id: string, content: string, options?: {
|
|
189
|
+
mediaPath?: string;
|
|
190
|
+
mediaType?: MediaType;
|
|
191
|
+
}): Promise<{ outboxId: string; status: OutboxStatus }> {
|
|
192
|
+
const body: InboxReplyRequest = {
|
|
193
|
+
id,
|
|
194
|
+
content,
|
|
195
|
+
mediaPath: options?.mediaPath,
|
|
196
|
+
mediaType: options?.mediaType,
|
|
197
|
+
};
|
|
198
|
+
const response = await request<InboxReplyResponse>('POST', '/inbox/reply', body);
|
|
199
|
+
return { outboxId: response.outboxId, status: response.status };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get inbox statistics
|
|
204
|
+
*/
|
|
205
|
+
async inboxStats(options?: {
|
|
206
|
+
service?: ServiceName;
|
|
207
|
+
profile?: string;
|
|
208
|
+
}): Promise<{ pending: number; done: number; total: number }> {
|
|
209
|
+
const body: InboxStatsRequest = {
|
|
210
|
+
service: options?.service,
|
|
211
|
+
profile: options?.profile,
|
|
212
|
+
};
|
|
213
|
+
return request<InboxStatsResponse>('POST', '/inbox/stats', body);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Send a message via outbox
|
|
218
|
+
*/
|
|
219
|
+
async outboxSend(options: {
|
|
220
|
+
service: ServiceName;
|
|
221
|
+
profile: string;
|
|
222
|
+
conversationId: string;
|
|
223
|
+
content?: string;
|
|
224
|
+
mediaPath?: string;
|
|
225
|
+
mediaType?: MediaType;
|
|
226
|
+
replyToPlatformId?: string;
|
|
227
|
+
metadata?: Record<string, unknown>;
|
|
228
|
+
}): Promise<{ id: string; status: OutboxStatus }> {
|
|
229
|
+
const body: OutboxSendRequest = {
|
|
230
|
+
service: options.service,
|
|
231
|
+
profile: options.profile,
|
|
232
|
+
conversationId: options.conversationId,
|
|
233
|
+
content: options.content,
|
|
234
|
+
mediaPath: options.mediaPath,
|
|
235
|
+
mediaType: options.mediaType,
|
|
236
|
+
replyToPlatformId: options.replyToPlatformId,
|
|
237
|
+
metadata: options.metadata,
|
|
238
|
+
};
|
|
239
|
+
const response = await request<OutboxSendResponse>('POST', '/outbox/send', body);
|
|
240
|
+
return { id: response.id, status: response.status };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get outbox message status
|
|
245
|
+
*/
|
|
246
|
+
async outboxStatus(id: string): Promise<OutboundMessage | null> {
|
|
247
|
+
const body: OutboxStatusRequest = { id };
|
|
248
|
+
const response = await request<OutboxStatusResponse>('POST', '/outbox/status', body);
|
|
249
|
+
return response.message;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* List outbox messages
|
|
254
|
+
*/
|
|
255
|
+
async outboxList(options?: {
|
|
256
|
+
service?: ServiceName;
|
|
257
|
+
profile?: string;
|
|
258
|
+
status?: OutboxStatus;
|
|
259
|
+
limit?: number;
|
|
260
|
+
}): Promise<OutboundMessage[]> {
|
|
261
|
+
const body: OutboxListRequest = {
|
|
262
|
+
service: options?.service,
|
|
263
|
+
profile: options?.profile,
|
|
264
|
+
status: options?.status,
|
|
265
|
+
limit: options?.limit,
|
|
266
|
+
};
|
|
267
|
+
const response = await request<OutboxListResponse>('POST', '/outbox/list', body);
|
|
268
|
+
return response.messages;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get media URL for an inbox message
|
|
273
|
+
*/
|
|
274
|
+
getMediaUrl(messageId: string): string {
|
|
275
|
+
// This needs the URL synchronously, so we use cached config
|
|
276
|
+
if (!cachedConfig) {
|
|
277
|
+
throw new CliError('CONFIG_ERROR', 'Gateway not configured');
|
|
278
|
+
}
|
|
279
|
+
return `${cachedConfig.url}/media/${messageId}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get WhatsApp pairing status and QR code
|
|
284
|
+
*/
|
|
285
|
+
async whatsappPair(profile: string): Promise<WhatsAppPairResponse> {
|
|
286
|
+
return request<WhatsAppPairResponse>('GET', `/whatsapp/pair/${encodeURIComponent(profile)}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============ WHATSAPP GROUP METHODS ============
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* List all WhatsApp groups
|
|
293
|
+
*/
|
|
294
|
+
async whatsappGroupList(profile: string): Promise<WhatsAppGroup[]> {
|
|
295
|
+
const body: WhatsAppGroupListRequest = { profile };
|
|
296
|
+
const response = await request<WhatsAppGroupListResponse>('POST', '/whatsapp/groups/list', body);
|
|
297
|
+
return response.groups;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get WhatsApp group details
|
|
302
|
+
*/
|
|
303
|
+
async whatsappGroupGet(profile: string, groupId: string): Promise<WhatsAppGroup> {
|
|
304
|
+
const body: WhatsAppGroupGetRequest = { profile, groupId };
|
|
305
|
+
const response = await request<WhatsAppGroupGetResponse>('POST', '/whatsapp/groups/get', body);
|
|
306
|
+
return response.group;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create a WhatsApp group
|
|
311
|
+
*/
|
|
312
|
+
async whatsappGroupCreate(profile: string, name: string, participants: string[], picture?: string): Promise<WhatsAppGroup> {
|
|
313
|
+
const body: WhatsAppGroupCreateRequest = { profile, name, participants, picture };
|
|
314
|
+
const response = await request<WhatsAppGroupCreateResponse>('POST', '/whatsapp/groups/create', body);
|
|
315
|
+
return response.group;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Update WhatsApp group info
|
|
320
|
+
*/
|
|
321
|
+
async whatsappGroupUpdate(
|
|
322
|
+
profile: string,
|
|
323
|
+
groupId: string,
|
|
324
|
+
options: { subject?: string; description?: string; picture?: string }
|
|
325
|
+
): Promise<boolean> {
|
|
326
|
+
const body: WhatsAppGroupUpdateRequest = { profile, groupId, ...options };
|
|
327
|
+
const response = await request<WhatsAppGroupUpdateResponse>('POST', '/whatsapp/groups/update', body);
|
|
328
|
+
return response.success;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Update WhatsApp group participants
|
|
333
|
+
*/
|
|
334
|
+
async whatsappGroupParticipants(
|
|
335
|
+
profile: string,
|
|
336
|
+
groupId: string,
|
|
337
|
+
participants: string[],
|
|
338
|
+
action: WhatsAppParticipantAction
|
|
339
|
+
): Promise<{ success: boolean; results?: { participant: string; status: string }[] }> {
|
|
340
|
+
const body: WhatsAppGroupParticipantsRequest = { profile, groupId, participants, action };
|
|
341
|
+
const response = await request<WhatsAppGroupParticipantsResponse>('POST', '/whatsapp/groups/participants', body);
|
|
342
|
+
return response;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Leave a WhatsApp group
|
|
347
|
+
*/
|
|
348
|
+
async whatsappGroupLeave(profile: string, groupId: string): Promise<boolean> {
|
|
349
|
+
const body: WhatsAppGroupLeaveRequest = { profile, groupId };
|
|
350
|
+
const response = await request<WhatsAppGroupLeaveResponse>('POST', '/whatsapp/groups/leave', body);
|
|
351
|
+
return response.success;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get WhatsApp group invite link
|
|
356
|
+
*/
|
|
357
|
+
async whatsappGroupInvite(profile: string, groupId: string): Promise<{ inviteCode: string; inviteLink: string }> {
|
|
358
|
+
const body: WhatsAppGroupInviteRequest = { profile, groupId };
|
|
359
|
+
const response = await request<WhatsAppGroupInviteResponse>('POST', '/whatsapp/groups/invite', body);
|
|
360
|
+
return response;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Join WhatsApp group via invite code
|
|
365
|
+
*/
|
|
366
|
+
async whatsappGroupJoin(profile: string, inviteCode: string): Promise<string> {
|
|
367
|
+
const body: WhatsAppGroupJoinRequest = { profile, inviteCode };
|
|
368
|
+
const response = await request<WhatsAppGroupJoinResponse>('POST', '/whatsapp/groups/join', body);
|
|
369
|
+
return response.groupId;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Resolve group name to JID or vice versa
|
|
374
|
+
*/
|
|
375
|
+
async whatsappGroupResolve(profile: string, nameOrId: string): Promise<{ groupId: string | null; groupName: string | null }> {
|
|
376
|
+
const body: WhatsAppGroupResolveRequest = { profile, nameOrId };
|
|
377
|
+
const response = await request<WhatsAppGroupResolveResponse>('POST', '/whatsapp/groups/resolve', body);
|
|
378
|
+
return response;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get a gateway client instance
|
|
384
|
+
*/
|
|
385
|
+
export async function getGatewayClient(): Promise<GatewayClient> {
|
|
386
|
+
// Ensure config is loaded
|
|
387
|
+
await getGatewayConnection();
|
|
388
|
+
return new GatewayClient();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Check if gateway is available
|
|
393
|
+
*/
|
|
394
|
+
export async function isGatewayAvailable(): Promise<boolean> {
|
|
395
|
+
try {
|
|
396
|
+
const client = await getGatewayClient();
|
|
397
|
+
await client.health();
|
|
398
|
+
return true;
|
|
399
|
+
} catch {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|