@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,326 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { google } from 'googleapis';
|
|
3
|
+
import { getValidTokens, createGoogleAuth } from '../auth/token-manager';
|
|
4
|
+
import { setCredentials } from '../auth/token-store';
|
|
5
|
+
import { setProfile } from '../config/config-manager';
|
|
6
|
+
import { createProfileCommands } from '../utils/profile-commands';
|
|
7
|
+
import { performOAuthFlow } from '../auth/oauth';
|
|
8
|
+
import { GTasksClient } from '../services/gtasks/client';
|
|
9
|
+
import {
|
|
10
|
+
printGTasksList,
|
|
11
|
+
printGTaskList,
|
|
12
|
+
printGTaskListCreated,
|
|
13
|
+
printGTaskListDeleted,
|
|
14
|
+
printGTasks,
|
|
15
|
+
printGTask,
|
|
16
|
+
printGTaskCreated,
|
|
17
|
+
printGTaskDeleted,
|
|
18
|
+
printGTasksCleared,
|
|
19
|
+
} from '../utils/output';
|
|
20
|
+
import { CliError, handleError } from '../utils/errors';
|
|
21
|
+
import { readStdin } from '../utils/stdin';
|
|
22
|
+
|
|
23
|
+
async function getGTasksClient(profileName?: string): Promise<{ client: GTasksClient; profile: string }> {
|
|
24
|
+
const { tokens, profile } = await getValidTokens('gtasks', profileName);
|
|
25
|
+
const auth = createGoogleAuth(tokens);
|
|
26
|
+
return { client: new GTasksClient(auth), profile };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerGTasksCommands(program: Command): void {
|
|
30
|
+
const gtasks = program
|
|
31
|
+
.command('gtasks')
|
|
32
|
+
.description('Google Tasks operations');
|
|
33
|
+
|
|
34
|
+
// === Task Lists Commands ===
|
|
35
|
+
|
|
36
|
+
const lists = gtasks
|
|
37
|
+
.command('lists')
|
|
38
|
+
.description('Manage task lists');
|
|
39
|
+
|
|
40
|
+
// List task lists (default subcommand)
|
|
41
|
+
lists
|
|
42
|
+
.command('list', { isDefault: true })
|
|
43
|
+
.description('List all task lists')
|
|
44
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
45
|
+
.option('--limit <n>', 'Max results', '100')
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
try {
|
|
48
|
+
const { client } = await getGTasksClient(options.profile);
|
|
49
|
+
const result = await client.listTaskLists(parseInt(options.limit, 10));
|
|
50
|
+
printGTasksList(result.taskLists, result.nextPageToken);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
handleError(error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Create task list
|
|
57
|
+
lists
|
|
58
|
+
.command('create <title>')
|
|
59
|
+
.description('Create a new task list')
|
|
60
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
61
|
+
.action(async (title: string, options) => {
|
|
62
|
+
try {
|
|
63
|
+
const { client } = await getGTasksClient(options.profile);
|
|
64
|
+
const taskList = await client.createTaskList(title);
|
|
65
|
+
printGTaskListCreated(taskList);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
handleError(error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Delete task list
|
|
72
|
+
lists
|
|
73
|
+
.command('delete <tasklist-id>')
|
|
74
|
+
.description('Delete a task list')
|
|
75
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
76
|
+
.action(async (tasklistId: string, options) => {
|
|
77
|
+
try {
|
|
78
|
+
const { client } = await getGTasksClient(options.profile);
|
|
79
|
+
await client.deleteTaskList(tasklistId);
|
|
80
|
+
printGTaskListDeleted(tasklistId);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
handleError(error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// === Task Commands ===
|
|
87
|
+
|
|
88
|
+
// List tasks in a task list
|
|
89
|
+
gtasks
|
|
90
|
+
.command('list <tasklist-id>')
|
|
91
|
+
.description('List tasks in a task list')
|
|
92
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
93
|
+
.option('--limit <n>', 'Max results', '20')
|
|
94
|
+
.option('--show-completed', 'Include completed tasks', true)
|
|
95
|
+
.option('--no-show-completed', 'Exclude completed tasks')
|
|
96
|
+
.option('--show-hidden', 'Include hidden tasks')
|
|
97
|
+
.option('--due-min <datetime>', 'Filter: due date minimum (RFC3339)')
|
|
98
|
+
.option('--due-max <datetime>', 'Filter: due date maximum (RFC3339)')
|
|
99
|
+
.action(async (tasklistId: string, options) => {
|
|
100
|
+
try {
|
|
101
|
+
const { client } = await getGTasksClient(options.profile);
|
|
102
|
+
const result = await client.listTasks({
|
|
103
|
+
tasklistId,
|
|
104
|
+
maxResults: parseInt(options.limit, 10),
|
|
105
|
+
showCompleted: options.showCompleted,
|
|
106
|
+
showHidden: options.showHidden,
|
|
107
|
+
dueMin: options.dueMin,
|
|
108
|
+
dueMax: options.dueMax,
|
|
109
|
+
});
|
|
110
|
+
printGTasks(result.tasks, result.nextPageToken);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
handleError(error);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Get a specific task
|
|
117
|
+
gtasks
|
|
118
|
+
.command('get <tasklist-id> <task-id>')
|
|
119
|
+
.description('Get a specific task')
|
|
120
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
121
|
+
.action(async (tasklistId: string, taskId: string, options) => {
|
|
122
|
+
try {
|
|
123
|
+
const { client } = await getGTasksClient(options.profile);
|
|
124
|
+
const task = await client.getTask(tasklistId, taskId);
|
|
125
|
+
printGTask(task);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
handleError(error);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Add a new task
|
|
132
|
+
gtasks
|
|
133
|
+
.command('add <tasklist-id>')
|
|
134
|
+
.description('Add a new task')
|
|
135
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
136
|
+
.requiredOption('--title <title>', 'Task title')
|
|
137
|
+
.option('--notes <text>', 'Task notes/description (or pipe via stdin)')
|
|
138
|
+
.option('--due <date>', 'Due date (RFC3339 or YYYY-MM-DD)')
|
|
139
|
+
.option('--parent <task-id>', 'Parent task ID (create as subtask)')
|
|
140
|
+
.option('--previous <task-id>', 'Previous sibling task ID (controls ordering)')
|
|
141
|
+
.action(async (tasklistId: string, options) => {
|
|
142
|
+
try {
|
|
143
|
+
let notes = options.notes;
|
|
144
|
+
if (!notes) {
|
|
145
|
+
const stdin = await readStdin();
|
|
146
|
+
if (stdin) notes = stdin;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { client } = await getGTasksClient(options.profile);
|
|
150
|
+
const task = await client.createTask({
|
|
151
|
+
tasklistId,
|
|
152
|
+
title: options.title,
|
|
153
|
+
notes,
|
|
154
|
+
due: options.due,
|
|
155
|
+
parent: options.parent,
|
|
156
|
+
previous: options.previous,
|
|
157
|
+
});
|
|
158
|
+
printGTaskCreated(task);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
handleError(error);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Update a task
|
|
165
|
+
gtasks
|
|
166
|
+
.command('update <tasklist-id> <task-id>')
|
|
167
|
+
.description('Update an existing task')
|
|
168
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
169
|
+
.option('--title <title>', 'New title')
|
|
170
|
+
.option('--notes <text>', 'New notes (or pipe via stdin)')
|
|
171
|
+
.option('--due <date>', 'New due date (RFC3339 or YYYY-MM-DD, empty to clear)')
|
|
172
|
+
.option('--status <status>', 'New status: needsAction or completed')
|
|
173
|
+
.action(async (tasklistId: string, taskId: string, options) => {
|
|
174
|
+
try {
|
|
175
|
+
let notes = options.notes;
|
|
176
|
+
if (notes === undefined && !process.stdin.isTTY) {
|
|
177
|
+
const stdin = await readStdin();
|
|
178
|
+
if (stdin) notes = stdin;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (options.status && !['needsAction', 'completed'].includes(options.status)) {
|
|
182
|
+
throw new CliError('INVALID_PARAMS', `Invalid status: ${options.status}`, 'Use: needsAction or completed');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { client } = await getGTasksClient(options.profile);
|
|
186
|
+
const task = await client.updateTask({
|
|
187
|
+
tasklistId,
|
|
188
|
+
taskId,
|
|
189
|
+
title: options.title,
|
|
190
|
+
notes,
|
|
191
|
+
due: options.due,
|
|
192
|
+
status: options.status,
|
|
193
|
+
});
|
|
194
|
+
printGTask(task);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
handleError(error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Mark task as done
|
|
201
|
+
gtasks
|
|
202
|
+
.command('done <tasklist-id> <task-id>')
|
|
203
|
+
.alias('complete')
|
|
204
|
+
.description('Mark a task as completed')
|
|
205
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
206
|
+
.action(async (tasklistId: string, taskId: string, options) => {
|
|
207
|
+
try {
|
|
208
|
+
const { client } = await getGTasksClient(options.profile);
|
|
209
|
+
const task = await client.completeTask(tasklistId, taskId);
|
|
210
|
+
console.log(`Task completed: ${task.title}`);
|
|
211
|
+
console.log(`ID: ${task.id}`);
|
|
212
|
+
console.log(`Status: ${task.status}`);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
handleError(error);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Mark task as not done
|
|
219
|
+
gtasks
|
|
220
|
+
.command('undo <tasklist-id> <task-id>')
|
|
221
|
+
.alias('uncomplete')
|
|
222
|
+
.description('Mark a task as needs action (not completed)')
|
|
223
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
224
|
+
.action(async (tasklistId: string, taskId: string, options) => {
|
|
225
|
+
try {
|
|
226
|
+
const { client } = await getGTasksClient(options.profile);
|
|
227
|
+
const task = await client.uncompleteTask(tasklistId, taskId);
|
|
228
|
+
console.log(`Task uncompleted: ${task.title}`);
|
|
229
|
+
console.log(`ID: ${task.id}`);
|
|
230
|
+
console.log(`Status: ${task.status}`);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
handleError(error);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Delete a task
|
|
237
|
+
gtasks
|
|
238
|
+
.command('delete <tasklist-id> <task-id>')
|
|
239
|
+
.description('Delete a task')
|
|
240
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
241
|
+
.action(async (tasklistId: string, taskId: string, options) => {
|
|
242
|
+
try {
|
|
243
|
+
const { client } = await getGTasksClient(options.profile);
|
|
244
|
+
await client.deleteTask(tasklistId, taskId);
|
|
245
|
+
printGTaskDeleted(tasklistId, taskId);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
handleError(error);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Clear completed tasks
|
|
252
|
+
gtasks
|
|
253
|
+
.command('clear <tasklist-id>')
|
|
254
|
+
.description('Clear all completed tasks from a task list')
|
|
255
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
256
|
+
.action(async (tasklistId: string, options) => {
|
|
257
|
+
try {
|
|
258
|
+
const { client } = await getGTasksClient(options.profile);
|
|
259
|
+
await client.clearCompleted(tasklistId);
|
|
260
|
+
printGTasksCleared(tasklistId);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
handleError(error);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Move a task
|
|
267
|
+
gtasks
|
|
268
|
+
.command('move <tasklist-id> <task-id>')
|
|
269
|
+
.description('Move a task (change parent or position)')
|
|
270
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
271
|
+
.option('--parent <task-id>', 'New parent task ID (make subtask)')
|
|
272
|
+
.option('--previous <task-id>', 'Previous sibling task ID (change position)')
|
|
273
|
+
.action(async (tasklistId: string, taskId: string, options) => {
|
|
274
|
+
try {
|
|
275
|
+
if (!options.parent && !options.previous) {
|
|
276
|
+
throw new CliError('INVALID_PARAMS', 'At least one of --parent or --previous is required');
|
|
277
|
+
}
|
|
278
|
+
const { client } = await getGTasksClient(options.profile);
|
|
279
|
+
const task = await client.moveTask(tasklistId, taskId, options.parent, options.previous);
|
|
280
|
+
console.log(`Task moved: ${task.title}`);
|
|
281
|
+
console.log(`ID: ${task.id}`);
|
|
282
|
+
if (task.parent) console.log(`Parent: ${task.parent}`);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
handleError(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Profile management
|
|
289
|
+
const profile = createProfileCommands<{ email?: string }>(gtasks, {
|
|
290
|
+
service: 'gtasks',
|
|
291
|
+
displayName: 'Google Tasks',
|
|
292
|
+
getExtraInfo: (credentials) => credentials?.email ? ` - ${credentials.email}` : '',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
profile
|
|
296
|
+
.command('add')
|
|
297
|
+
.description('Add a new Google Tasks profile')
|
|
298
|
+
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
299
|
+
.action(async (options) => {
|
|
300
|
+
try {
|
|
301
|
+
console.error('Starting OAuth flow for Google Tasks...\n');
|
|
302
|
+
|
|
303
|
+
const tokens = await performOAuthFlow('gtasks');
|
|
304
|
+
|
|
305
|
+
// Fetch user email via oauth2 userinfo
|
|
306
|
+
const auth = createGoogleAuth(tokens);
|
|
307
|
+
const oauth2 = google.oauth2({ version: 'v2', auth });
|
|
308
|
+
const userInfo = await oauth2.userinfo.get();
|
|
309
|
+
const email = userInfo.data.email;
|
|
310
|
+
|
|
311
|
+
if (!email) {
|
|
312
|
+
throw new CliError('AUTH_FAILED', 'Could not fetch email', 'Try again or specify --profile manually');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const profileName = options.profile || email;
|
|
316
|
+
|
|
317
|
+
await setProfile('gtasks', profileName);
|
|
318
|
+
await setCredentials('gtasks', profileName, { ...tokens, email });
|
|
319
|
+
|
|
320
|
+
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
321
|
+
console.log(` Email: ${email}`);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
handleError(error);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
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;
|