@plosson/agentio 0.4.2 → 0.4.4
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 +22 -2
- package/src/commands/gateway.ts +259 -0
- package/src/commands/gcal.ts +383 -0
- package/src/commands/gsheets.ts +365 -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 +34 -16
- package/src/polyfills.ts +10 -0
- package/src/services/gcal/client.ts +380 -0
- package/src/services/gsheets/client.ts +362 -0
- package/src/services/gtasks/client.ts +301 -0
- package/src/types/config.ts +37 -1
- package/src/types/gcal.ts +135 -0
- package/src/types/gsheets.ts +81 -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 +586 -0
package/src/commands/status.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { TelegramClient } from '../services/telegram/client';
|
|
|
7
7
|
import { GmailClient } from '../services/gmail/client';
|
|
8
8
|
import { GDocsClient } from '../services/gdocs/client';
|
|
9
9
|
import { GDriveClient } from '../services/gdrive/client';
|
|
10
|
+
import { GCalClient } from '../services/gcal/client';
|
|
11
|
+
import { GTasksClient } from '../services/gtasks/client';
|
|
10
12
|
import { GitHubClient } from '../services/github/client';
|
|
11
13
|
import { JiraClient } from '../services/jira/client';
|
|
12
14
|
import { GChatClient } from '../services/gchat/client';
|
|
@@ -21,10 +23,14 @@ import type { GitHubCredentials } from '../types/github';
|
|
|
21
23
|
import type { JiraCredentials } from '../types/jira';
|
|
22
24
|
import type { GDocsCredentials } from '../types/gdocs';
|
|
23
25
|
import type { GDriveCredentials } from '../types/gdrive';
|
|
26
|
+
import type { GCalCredentials } from '../types/gcal';
|
|
27
|
+
import type { GTasksCredentials } from '../types/gtasks';
|
|
24
28
|
import type { GChatCredentials } from '../types/gchat';
|
|
25
29
|
import type { SlackCredentials } from '../types/slack';
|
|
26
30
|
import type { DiscourseCredentials } from '../types/discourse';
|
|
27
31
|
import type { SqlCredentials } from '../types/sql';
|
|
32
|
+
import type { WhatsAppCredentials } from '../types/whatsapp';
|
|
33
|
+
import { isGatewayAvailable, getGatewayClient } from '../gateway/client';
|
|
28
34
|
|
|
29
35
|
type GmailCredentials = OAuthTokens & { email?: string };
|
|
30
36
|
|
|
@@ -60,6 +66,30 @@ async function createServiceClient(
|
|
|
60
66
|
return new GDriveClient(creds);
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
case 'gcal': {
|
|
70
|
+
const creds = credentials as GCalCredentials;
|
|
71
|
+
const auth = createGoogleAuth({
|
|
72
|
+
access_token: creds.access_token,
|
|
73
|
+
refresh_token: creds.refresh_token,
|
|
74
|
+
expiry_date: creds.expiry_date,
|
|
75
|
+
token_type: creds.token_type || 'Bearer',
|
|
76
|
+
scope: creds.scope,
|
|
77
|
+
});
|
|
78
|
+
return new GCalClient(auth);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case 'gtasks': {
|
|
82
|
+
const creds = credentials as GTasksCredentials;
|
|
83
|
+
const auth = createGoogleAuth({
|
|
84
|
+
access_token: creds.access_token,
|
|
85
|
+
refresh_token: creds.refresh_token,
|
|
86
|
+
expiry_date: creds.expiry_date,
|
|
87
|
+
token_type: creds.token_type || 'Bearer',
|
|
88
|
+
scope: creds.scope,
|
|
89
|
+
});
|
|
90
|
+
return new GTasksClient(auth);
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
case 'telegram': {
|
|
64
94
|
const creds = credentials as TelegramCredentials;
|
|
65
95
|
return new TelegramClient(creds.botToken, creds.channelId);
|
|
@@ -150,6 +180,61 @@ async function createServiceClient(
|
|
|
150
180
|
return new SqlClient(creds);
|
|
151
181
|
}
|
|
152
182
|
|
|
183
|
+
case 'whatsapp': {
|
|
184
|
+
const creds = credentials as WhatsAppCredentials;
|
|
185
|
+
return {
|
|
186
|
+
validate: async (): Promise<ValidationResult> => {
|
|
187
|
+
// Check if gateway is running
|
|
188
|
+
const gatewayAvailable = await isGatewayAvailable();
|
|
189
|
+
if (!gatewayAvailable) {
|
|
190
|
+
if (creds.paired) {
|
|
191
|
+
return {
|
|
192
|
+
valid: true,
|
|
193
|
+
info: `${creds.phoneNumber || 'paired'} (gateway not running)`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return { valid: false, error: 'not paired (gateway not running)' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check connection status via gateway - this is the source of truth
|
|
200
|
+
try {
|
|
201
|
+
const client = await getGatewayClient();
|
|
202
|
+
const status = await client.status();
|
|
203
|
+
const adapter = status.adapters.find(
|
|
204
|
+
(a) => a.service === 'whatsapp' && a.profile === profileName
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (adapter?.connected) {
|
|
208
|
+
// Connected via gateway = working
|
|
209
|
+
return { valid: true, info: creds.phoneNumber || 'connected' };
|
|
210
|
+
} else if (adapter) {
|
|
211
|
+
// Adapter exists but not connected
|
|
212
|
+
return {
|
|
213
|
+
valid: true,
|
|
214
|
+
info: `${creds.phoneNumber || 'configured'} (disconnected)`,
|
|
215
|
+
};
|
|
216
|
+
} else if (creds.paired) {
|
|
217
|
+
// Has paired credentials but no adapter in gateway
|
|
218
|
+
return {
|
|
219
|
+
valid: true,
|
|
220
|
+
info: `${creds.phoneNumber || 'paired'} (not loaded in gateway)`,
|
|
221
|
+
};
|
|
222
|
+
} else {
|
|
223
|
+
return { valid: false, error: 'not paired' };
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
if (creds.paired) {
|
|
227
|
+
return {
|
|
228
|
+
valid: true,
|
|
229
|
+
info: `${creds.phoneNumber || 'paired'} (gateway error)`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return { valid: false, error: 'gateway error' };
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
153
238
|
default:
|
|
154
239
|
return null;
|
|
155
240
|
}
|
package/src/commands/telegram.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { setCredentials } from '../auth/token-store';
|
|
3
|
-
import { setProfile } from '../config/config-manager';
|
|
3
|
+
import { setProfile, resolveProfile } from '../config/config-manager';
|
|
4
4
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
5
5
|
import { createClientGetter } from '../utils/client-factory';
|
|
6
6
|
import { TelegramClient } from '../services/telegram/client';
|
|
7
7
|
import { CliError, handleError } from '../utils/errors';
|
|
8
8
|
import { readStdin, prompt } from '../utils/stdin';
|
|
9
|
+
import { getGatewayClient, isGatewayAvailable } from '../gateway/client';
|
|
10
|
+
import {
|
|
11
|
+
printInboxMessageList,
|
|
12
|
+
printInboxMessage,
|
|
13
|
+
printInboxStats,
|
|
14
|
+
printInboxAckResult,
|
|
15
|
+
printInboxReplyResult,
|
|
16
|
+
printOutboxMessageList,
|
|
17
|
+
printOutboxMessage,
|
|
18
|
+
printOutboxSendResult,
|
|
19
|
+
} from '../utils/output';
|
|
9
20
|
import type { TelegramCredentials, TelegramSendOptions } from '../types/telegram';
|
|
10
21
|
|
|
11
22
|
const getTelegramClient = createClientGetter<TelegramCredentials, TelegramClient>({
|
|
@@ -169,4 +180,201 @@ export function registerTelegramCommands(program: Command): void {
|
|
|
169
180
|
handleError(error);
|
|
170
181
|
}
|
|
171
182
|
});
|
|
183
|
+
|
|
184
|
+
// Inbox subcommands (requires gateway)
|
|
185
|
+
const inbox = telegram.command('inbox').description('Inbox operations (requires gateway)');
|
|
186
|
+
|
|
187
|
+
inbox
|
|
188
|
+
.command('pull')
|
|
189
|
+
.description('Get pending messages from inbox')
|
|
190
|
+
.option('--profile <name>', 'Profile name')
|
|
191
|
+
.option('--limit <n>', 'Maximum messages to retrieve', '50')
|
|
192
|
+
.option('--status <status>', 'Filter by status: pending or done', 'pending')
|
|
193
|
+
.action(async (options) => {
|
|
194
|
+
try {
|
|
195
|
+
const profileResult = await resolveProfile('telegram', options.profile);
|
|
196
|
+
if (!profileResult.profile) {
|
|
197
|
+
if (profileResult.error === 'none') {
|
|
198
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No Telegram profiles configured', 'Run: agentio telegram profile add');
|
|
199
|
+
}
|
|
200
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const client = await getGatewayClient();
|
|
204
|
+
const messages = await client.inboxPull({
|
|
205
|
+
service: 'telegram',
|
|
206
|
+
profile: profileResult.profile,
|
|
207
|
+
limit: parseInt(options.limit, 10),
|
|
208
|
+
status: options.status as 'pending' | 'done',
|
|
209
|
+
});
|
|
210
|
+
printInboxMessageList(messages);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
handleError(error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
inbox
|
|
217
|
+
.command('get')
|
|
218
|
+
.description('Get a specific inbox message')
|
|
219
|
+
.argument('<id>', 'Message ID')
|
|
220
|
+
.action(async (id: string) => {
|
|
221
|
+
try {
|
|
222
|
+
const client = await getGatewayClient();
|
|
223
|
+
const message = await client.inboxGet(id);
|
|
224
|
+
if (!message) {
|
|
225
|
+
throw new CliError('NOT_FOUND', `Message not found: ${id}`);
|
|
226
|
+
}
|
|
227
|
+
printInboxMessage(message);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
handleError(error);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
inbox
|
|
234
|
+
.command('ack')
|
|
235
|
+
.description('Mark a message as done')
|
|
236
|
+
.argument('<id>', 'Message ID')
|
|
237
|
+
.action(async (id: string) => {
|
|
238
|
+
try {
|
|
239
|
+
const client = await getGatewayClient();
|
|
240
|
+
const success = await client.inboxAck(id);
|
|
241
|
+
printInboxAckResult(success, id);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
handleError(error);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
inbox
|
|
248
|
+
.command('reply')
|
|
249
|
+
.description('Reply to an inbox message')
|
|
250
|
+
.argument('<id>', 'Message ID to reply to')
|
|
251
|
+
.argument('[message]', 'Reply text (or pipe via stdin)')
|
|
252
|
+
.action(async (id: string, message: string | undefined) => {
|
|
253
|
+
try {
|
|
254
|
+
let text = message;
|
|
255
|
+
if (!text) {
|
|
256
|
+
text = await readStdin() || undefined;
|
|
257
|
+
}
|
|
258
|
+
if (!text) {
|
|
259
|
+
throw new CliError('INVALID_PARAMS', 'Message is required. Provide as argument or pipe via stdin.');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const client = await getGatewayClient();
|
|
263
|
+
const result = await client.inboxReply(id, text);
|
|
264
|
+
printInboxReplyResult(result);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
handleError(error);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
inbox
|
|
271
|
+
.command('stats')
|
|
272
|
+
.description('Get inbox statistics')
|
|
273
|
+
.option('--profile <name>', 'Profile name')
|
|
274
|
+
.action(async (options) => {
|
|
275
|
+
try {
|
|
276
|
+
const profileResult = await resolveProfile('telegram', options.profile);
|
|
277
|
+
const client = await getGatewayClient();
|
|
278
|
+
const stats = await client.inboxStats({
|
|
279
|
+
service: 'telegram',
|
|
280
|
+
profile: profileResult.profile ?? undefined,
|
|
281
|
+
});
|
|
282
|
+
printInboxStats(stats);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
handleError(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Outbox subcommands (requires gateway)
|
|
289
|
+
const outbox = telegram.command('outbox').description('Outbox operations (requires gateway)');
|
|
290
|
+
|
|
291
|
+
outbox
|
|
292
|
+
.command('send')
|
|
293
|
+
.description('Queue a message for sending')
|
|
294
|
+
.option('--profile <name>', 'Profile name')
|
|
295
|
+
.option('--to <chat-id>', 'Destination chat ID (required)')
|
|
296
|
+
.option('--parse-mode <mode>', 'Message format: html or markdown')
|
|
297
|
+
.argument('[message]', 'Message text (or pipe via stdin)')
|
|
298
|
+
.action(async (message: string | undefined, options) => {
|
|
299
|
+
try {
|
|
300
|
+
const profileResult = await resolveProfile('telegram', options.profile);
|
|
301
|
+
if (!profileResult.profile) {
|
|
302
|
+
if (profileResult.error === 'none') {
|
|
303
|
+
throw new CliError('PROFILE_NOT_FOUND', 'No Telegram profiles configured', 'Run: agentio telegram profile add');
|
|
304
|
+
}
|
|
305
|
+
throw new CliError('INVALID_PARAMS', 'Multiple profiles exist. Use --profile to specify one.');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!options.to) {
|
|
309
|
+
throw new CliError('INVALID_PARAMS', 'Destination chat ID is required. Use --to <chat-id>');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let text = message;
|
|
313
|
+
if (!text) {
|
|
314
|
+
text = await readStdin() || undefined;
|
|
315
|
+
}
|
|
316
|
+
if (!text) {
|
|
317
|
+
throw new CliError('INVALID_PARAMS', 'Message is required. Provide as argument or pipe via stdin.');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const metadata: Record<string, unknown> = {};
|
|
321
|
+
if (options.parseMode) {
|
|
322
|
+
const mode = options.parseMode.toLowerCase();
|
|
323
|
+
if (mode === 'html') metadata.parse_mode = 'HTML';
|
|
324
|
+
else if (mode === 'markdown') metadata.parse_mode = 'MarkdownV2';
|
|
325
|
+
else throw new CliError('INVALID_PARAMS', 'parse-mode must be "html" or "markdown"');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const client = await getGatewayClient();
|
|
329
|
+
const result = await client.outboxSend({
|
|
330
|
+
service: 'telegram',
|
|
331
|
+
profile: profileResult.profile,
|
|
332
|
+
conversationId: options.to,
|
|
333
|
+
content: text,
|
|
334
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
335
|
+
});
|
|
336
|
+
printOutboxSendResult(result);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
handleError(error);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
outbox
|
|
343
|
+
.command('status')
|
|
344
|
+
.description('Check send status of a message')
|
|
345
|
+
.argument('<id>', 'Outbox message ID')
|
|
346
|
+
.action(async (id: string) => {
|
|
347
|
+
try {
|
|
348
|
+
const client = await getGatewayClient();
|
|
349
|
+
const message = await client.outboxStatus(id);
|
|
350
|
+
if (!message) {
|
|
351
|
+
throw new CliError('NOT_FOUND', `Message not found: ${id}`);
|
|
352
|
+
}
|
|
353
|
+
printOutboxMessage(message);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
handleError(error);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
outbox
|
|
360
|
+
.command('list')
|
|
361
|
+
.description('List outbox messages')
|
|
362
|
+
.option('--profile <name>', 'Profile name')
|
|
363
|
+
.option('--status <status>', 'Filter by status: pending, sending, sent, or failed')
|
|
364
|
+
.option('--limit <n>', 'Maximum messages to retrieve', '50')
|
|
365
|
+
.action(async (options) => {
|
|
366
|
+
try {
|
|
367
|
+
const profileResult = await resolveProfile('telegram', options.profile);
|
|
368
|
+
const client = await getGatewayClient();
|
|
369
|
+
const messages = await client.outboxList({
|
|
370
|
+
service: 'telegram',
|
|
371
|
+
profile: profileResult.profile ?? undefined,
|
|
372
|
+
status: options.status as 'pending' | 'sending' | 'sent' | 'failed' | undefined,
|
|
373
|
+
limit: parseInt(options.limit, 10),
|
|
374
|
+
});
|
|
375
|
+
printOutboxMessageList(messages);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
handleError(error);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
172
380
|
}
|
package/src/commands/update.ts
CHANGED
|
@@ -275,9 +275,9 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
275
275
|
console.error('Automatic update failed. You can update manually:');
|
|
276
276
|
console.error('');
|
|
277
277
|
if (os.platform() === 'win32') {
|
|
278
|
-
console.error(' iwr -useb https://agentio.
|
|
278
|
+
console.error(' iwr -useb https://agentio.me/install.ps1 | iex');
|
|
279
279
|
} else {
|
|
280
|
-
console.error(' curl -LsSf https://agentio.
|
|
280
|
+
console.error(' curl -LsSf https://agentio.me/install | sh');
|
|
281
281
|
}
|
|
282
282
|
console.error('');
|
|
283
283
|
throw error;
|