@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.
@@ -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
  }
@@ -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
  }
@@ -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.work/install.ps1 | iex');
278
+ console.error(' iwr -useb https://agentio.me/install.ps1 | iex');
279
279
  } else {
280
- console.error(' curl -LsSf https://agentio.work/install | sh');
280
+ console.error(' curl -LsSf https://agentio.me/install | sh');
281
281
  }
282
282
  console.error('');
283
283
  throw error;