@plosson/agentio 0.1.3 → 0.1.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 +64 -18
- package/package.json +1 -1
- package/src/auth/oauth.ts +10 -3
- package/src/commands/gchat.ts +350 -0
- package/src/commands/gmail.ts +16 -4
- package/src/commands/telegram.ts +6 -3
- package/src/config/credentials.ts +8 -2
- package/src/index.ts +2 -0
- package/src/services/gchat/client.ts +299 -0
- package/src/types/gchat.ts +70 -0
- package/src/types/telegram.ts +1 -0
- package/src/utils/obscure.ts +22 -0
- package/src/utils/output.ts +51 -0
package/README.md
CHANGED
|
@@ -4,32 +4,78 @@ CLI for LLM agents to interact with communication and tracking services.
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### macOS
|
|
8
8
|
|
|
9
|
+
**Homebrew (recommended):**
|
|
9
10
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
brew tap plosson/agentio
|
|
12
|
+
brew install agentio
|
|
13
|
+
```
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
**Or download binary:**
|
|
16
|
+
```bash
|
|
17
|
+
# Apple Silicon
|
|
18
|
+
curl -L https://github.com/plosson/agentio/releases/latest/download/agentio-darwin-arm64 -o agentio
|
|
19
|
+
chmod +x agentio && sudo mv agentio /usr/local/bin/
|
|
15
20
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
# Intel
|
|
22
|
+
curl -L https://github.com/plosson/agentio/releases/latest/download/agentio-darwin-x64 -o agentio
|
|
23
|
+
chmod +x agentio && sudo mv agentio /usr/local/bin/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Linux
|
|
27
|
+
|
|
28
|
+
**Debian/Ubuntu (.deb):**
|
|
29
|
+
```bash
|
|
30
|
+
# x64
|
|
31
|
+
curl -LO https://github.com/plosson/agentio/releases/latest/download/agentio_0.1.3_amd64.deb
|
|
32
|
+
sudo dpkg -i agentio_0.1.3_amd64.deb
|
|
33
|
+
|
|
34
|
+
# ARM64
|
|
35
|
+
curl -LO https://github.com/plosson/agentio/releases/latest/download/agentio_0.1.3_arm64.deb
|
|
36
|
+
sudo dpkg -i agentio_0.1.3_arm64.deb
|
|
20
37
|
```
|
|
21
38
|
|
|
22
|
-
|
|
39
|
+
**Homebrew:**
|
|
40
|
+
```bash
|
|
41
|
+
brew tap plosson/agentio
|
|
42
|
+
brew install agentio
|
|
43
|
+
```
|
|
23
44
|
|
|
24
|
-
|
|
45
|
+
**Or download binary:**
|
|
46
|
+
```bash
|
|
47
|
+
# x64
|
|
48
|
+
curl -L https://github.com/plosson/agentio/releases/latest/download/agentio-linux-x64 -o agentio
|
|
49
|
+
chmod +x agentio && sudo mv agentio /usr/local/bin/
|
|
25
50
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
# ARM64
|
|
52
|
+
curl -L https://github.com/plosson/agentio/releases/latest/download/agentio-linux-arm64 -o agentio
|
|
53
|
+
chmod +x agentio && sudo mv agentio /usr/local/bin/
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Windows
|
|
57
|
+
|
|
58
|
+
**Scoop (recommended):**
|
|
59
|
+
```powershell
|
|
60
|
+
scoop bucket add agentio https://github.com/plosson/scoop-agentio
|
|
61
|
+
scoop install agentio
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Or download binary:**
|
|
65
|
+
|
|
66
|
+
Download `agentio-windows-x64.exe` from [GitHub Releases](https://github.com/plosson/agentio/releases/latest) and add to your PATH.
|
|
67
|
+
|
|
68
|
+
### npm / bun
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run directly
|
|
72
|
+
bunx @plosson/agentio --help
|
|
73
|
+
npx @plosson/agentio --help
|
|
74
|
+
|
|
75
|
+
# Global install
|
|
76
|
+
bun add -g @plosson/agentio
|
|
77
|
+
npm install -g @plosson/agentio
|
|
78
|
+
```
|
|
33
79
|
|
|
34
80
|
## Services
|
|
35
81
|
|
package/package.json
CHANGED
package/src/auth/oauth.ts
CHANGED
|
@@ -5,8 +5,15 @@ import { GOOGLE_OAUTH_CONFIG } from '../config/credentials';
|
|
|
5
5
|
import type { OAuthTokens } from '../types/tokens';
|
|
6
6
|
|
|
7
7
|
const GMAIL_SCOPES = [
|
|
8
|
-
'https://www.googleapis.com/auth/gmail.
|
|
9
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
8
|
+
'https://www.googleapis.com/auth/gmail.readonly', // search & read emails
|
|
9
|
+
'https://www.googleapis.com/auth/gmail.send', // send emails
|
|
10
|
+
'https://www.googleapis.com/auth/gmail.compose', // create/update drafts
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const GCHAT_SCOPES = [
|
|
14
|
+
'https://www.googleapis.com/auth/chat.messages.create', // send messages
|
|
15
|
+
'https://www.googleapis.com/auth/chat.messages.readonly', // read messages (get operations)
|
|
16
|
+
'https://www.googleapis.com/auth/chat.spaces.readonly', // read space info and list
|
|
10
17
|
];
|
|
11
18
|
|
|
12
19
|
const PORT_RANGE_START = 3000;
|
|
@@ -42,7 +49,7 @@ export async function performOAuthFlow(
|
|
|
42
49
|
redirectUri
|
|
43
50
|
);
|
|
44
51
|
|
|
45
|
-
const scopes = service === 'gmail' ? GMAIL_SCOPES : [];
|
|
52
|
+
const scopes = service === 'gmail' ? GMAIL_SCOPES : service === 'gchat' ? GCHAT_SCOPES : [];
|
|
46
53
|
|
|
47
54
|
const authUrl = oauth2Client.generateAuthUrl({
|
|
48
55
|
access_type: 'offline',
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { google } from 'googleapis';
|
|
3
|
+
import { createInterface } from 'readline';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
import { setCredentials, removeCredentials, getCredentials } from '../auth/token-store';
|
|
6
|
+
import { setProfile, removeProfile, listProfiles, getProfile } from '../config/config-manager';
|
|
7
|
+
import { performOAuthFlow } from '../auth/oauth';
|
|
8
|
+
import { createGoogleAuth } from '../auth/token-manager';
|
|
9
|
+
import { GChatClient } from '../services/gchat/client';
|
|
10
|
+
import { CliError, handleError } from '../utils/errors';
|
|
11
|
+
import { readStdin } from '../utils/stdin';
|
|
12
|
+
import { printGChatSendResult, printGChatMessageList, printGChatMessage } from '../utils/output';
|
|
13
|
+
import type { GChatCredentials, GChatWebhookCredentials, GChatOAuthCredentials } from '../types/gchat';
|
|
14
|
+
|
|
15
|
+
function prompt(question: string): Promise<string> {
|
|
16
|
+
const rl = createInterface({
|
|
17
|
+
input: process.stdin,
|
|
18
|
+
output: process.stderr,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(question, (answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
resolve(answer.trim());
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getGChatClient(profileName?: string): Promise<{ client: GChatClient; profile: string }> {
|
|
30
|
+
const profile = await getProfile('gchat', profileName);
|
|
31
|
+
|
|
32
|
+
if (!profile) {
|
|
33
|
+
throw new CliError(
|
|
34
|
+
'PROFILE_NOT_FOUND',
|
|
35
|
+
profileName
|
|
36
|
+
? `Profile "${profileName}" not found for gchat`
|
|
37
|
+
: 'No default profile configured for gchat',
|
|
38
|
+
'Run: agentio gchat profile add'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const credentials = await getCredentials<GChatCredentials>('gchat', profile);
|
|
43
|
+
|
|
44
|
+
if (!credentials) {
|
|
45
|
+
throw new CliError(
|
|
46
|
+
'AUTH_FAILED',
|
|
47
|
+
`No credentials found for gchat profile "${profile}"`,
|
|
48
|
+
`Run: agentio gchat profile add --profile ${profile}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
client: new GChatClient(credentials),
|
|
54
|
+
profile,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function registerGChatCommands(program: Command): void {
|
|
59
|
+
const gchat = program
|
|
60
|
+
.command('gchat')
|
|
61
|
+
.description('Google Chat operations');
|
|
62
|
+
|
|
63
|
+
gchat
|
|
64
|
+
.command('send')
|
|
65
|
+
.description('Send a message to Google Chat')
|
|
66
|
+
.option('--profile <name>', 'Profile name')
|
|
67
|
+
.option('--space <id>', 'Space ID (required for OAuth profiles)')
|
|
68
|
+
.option('--thread <id>', 'Thread ID (optional)')
|
|
69
|
+
.option('--json [file]', 'Send rich message from JSON file (or stdin if no file specified)')
|
|
70
|
+
.argument('[message]', 'Message text (or pipe via stdin)')
|
|
71
|
+
.action(async (message: string | undefined, options) => {
|
|
72
|
+
try {
|
|
73
|
+
let text: string | undefined = message;
|
|
74
|
+
let payload: Record<string, unknown> | undefined;
|
|
75
|
+
|
|
76
|
+
// Handle --json option
|
|
77
|
+
if (options.json !== undefined) {
|
|
78
|
+
// Check mutual exclusivity
|
|
79
|
+
if (message) {
|
|
80
|
+
throw new CliError(
|
|
81
|
+
'INVALID_PARAMS',
|
|
82
|
+
'Cannot use both text message and --json option',
|
|
83
|
+
'Use either: agentio gchat send "text" OR agentio gchat send --json file.json'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let jsonContent: string;
|
|
88
|
+
|
|
89
|
+
if (typeof options.json === 'string') {
|
|
90
|
+
// Read from file
|
|
91
|
+
try {
|
|
92
|
+
jsonContent = await readFile(options.json, 'utf-8');
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new CliError(
|
|
95
|
+
'INVALID_PARAMS',
|
|
96
|
+
`Failed to read JSON file: ${options.json}`,
|
|
97
|
+
'Check that the file exists and is readable'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Read from stdin
|
|
102
|
+
const stdinContent = await readStdin();
|
|
103
|
+
if (!stdinContent) {
|
|
104
|
+
throw new CliError(
|
|
105
|
+
'INVALID_PARAMS',
|
|
106
|
+
'No JSON provided via stdin',
|
|
107
|
+
'Pipe JSON content: cat message.json | agentio gchat send --json'
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
jsonContent = stdinContent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Parse JSON
|
|
114
|
+
try {
|
|
115
|
+
payload = JSON.parse(jsonContent) as Record<string, unknown>;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
throw new CliError(
|
|
118
|
+
'INVALID_PARAMS',
|
|
119
|
+
`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
120
|
+
'Check that the JSON is valid'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Text message mode
|
|
125
|
+
if (!text) {
|
|
126
|
+
text = await readStdin() || undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!text) {
|
|
130
|
+
throw new CliError('INVALID_PARAMS', 'Message is required. Provide as argument or pipe via stdin.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { client } = await getGChatClient(options.profile);
|
|
135
|
+
const result = await client.send({
|
|
136
|
+
text,
|
|
137
|
+
payload,
|
|
138
|
+
threadId: options.thread,
|
|
139
|
+
spaceId: options.space,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
printGChatSendResult(result);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
handleError(error);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
gchat
|
|
149
|
+
.command('list')
|
|
150
|
+
.description('List messages from a Google Chat space (OAuth profiles only)')
|
|
151
|
+
.option('--profile <name>', 'Profile name')
|
|
152
|
+
.requiredOption('--space <id>', 'Space ID')
|
|
153
|
+
.option('--limit <n>', 'Number of messages', '10')
|
|
154
|
+
.action(async (options) => {
|
|
155
|
+
try {
|
|
156
|
+
const { client } = await getGChatClient(options.profile);
|
|
157
|
+
const messages = await client.list({
|
|
158
|
+
spaceId: options.space,
|
|
159
|
+
limit: parseInt(options.limit, 10),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
printGChatMessageList(messages);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
handleError(error);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
gchat
|
|
169
|
+
.command('get <message-id>')
|
|
170
|
+
.description('Get a message from a Google Chat space (OAuth profiles only)')
|
|
171
|
+
.option('--profile <name>', 'Profile name')
|
|
172
|
+
.requiredOption('--space <id>', 'Space ID')
|
|
173
|
+
.action(async (messageId: string, options) => {
|
|
174
|
+
try {
|
|
175
|
+
const { client } = await getGChatClient(options.profile);
|
|
176
|
+
const message = await client.get({
|
|
177
|
+
spaceId: options.space,
|
|
178
|
+
messageId: messageId,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
printGChatMessage(message);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
handleError(error);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Profile management
|
|
188
|
+
const profile = gchat
|
|
189
|
+
.command('profile')
|
|
190
|
+
.description('Manage Google Chat profiles');
|
|
191
|
+
|
|
192
|
+
profile
|
|
193
|
+
.command('add')
|
|
194
|
+
.description('Add a new Google Chat profile (webhook or OAuth)')
|
|
195
|
+
.option('--profile <name>', 'Profile name', 'default')
|
|
196
|
+
.action(async (options) => {
|
|
197
|
+
try {
|
|
198
|
+
const profileName = options.profile;
|
|
199
|
+
|
|
200
|
+
console.error('\nGoogle Chat Setup\n');
|
|
201
|
+
|
|
202
|
+
const profileType = await prompt('Choose profile type (webhook/oauth): ');
|
|
203
|
+
|
|
204
|
+
if (profileType.toLowerCase() === 'webhook') {
|
|
205
|
+
await setupWebhookProfile(profileName);
|
|
206
|
+
} else if (profileType.toLowerCase() === 'oauth') {
|
|
207
|
+
await setupOAuthProfile(profileName);
|
|
208
|
+
} else {
|
|
209
|
+
throw new CliError('INVALID_PARAMS', 'Profile type must be "webhook" or "oauth"');
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
handleError(error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
profile
|
|
217
|
+
.command('list')
|
|
218
|
+
.description('List Google Chat profiles')
|
|
219
|
+
.action(async () => {
|
|
220
|
+
try {
|
|
221
|
+
const result = await listProfiles('gchat');
|
|
222
|
+
const { profiles, default: defaultProfile } = result[0];
|
|
223
|
+
|
|
224
|
+
if (profiles.length === 0) {
|
|
225
|
+
console.log('No profiles configured');
|
|
226
|
+
} else {
|
|
227
|
+
for (const name of profiles) {
|
|
228
|
+
const marker = name === defaultProfile ? ' (default)' : '';
|
|
229
|
+
const credentials = await getCredentials<GChatCredentials>('gchat', name);
|
|
230
|
+
const typeInfo = credentials?.type === 'webhook' ? ' - webhook' : ' - oauth';
|
|
231
|
+
console.log(`${name}${marker}${typeInfo}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
handleError(error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
profile
|
|
240
|
+
.command('remove')
|
|
241
|
+
.description('Remove a Google Chat profile')
|
|
242
|
+
.requiredOption('--profile <name>', 'Profile name')
|
|
243
|
+
.action(async (options) => {
|
|
244
|
+
try {
|
|
245
|
+
const profileName = options.profile;
|
|
246
|
+
|
|
247
|
+
const removed = await removeProfile('gchat', profileName);
|
|
248
|
+
await removeCredentials('gchat', profileName);
|
|
249
|
+
|
|
250
|
+
if (removed) {
|
|
251
|
+
console.log(`Removed profile "${profileName}"`);
|
|
252
|
+
} else {
|
|
253
|
+
console.error(`Profile "${profileName}" not found`);
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
handleError(error);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function printProfileSetupSuccess(profileName: string, authType: 'webhook' | 'oauth'): void {
|
|
262
|
+
const typeLabel = authType.charAt(0).toUpperCase() + authType.slice(1);
|
|
263
|
+
console.log(`\nSuccess! ${typeLabel} profile "${profileName}" configured.`);
|
|
264
|
+
console.log(` Test with: agentio gchat send --profile ${profileName} "Hello from agentio"`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function setupWebhookProfile(profileName: string): Promise<void> {
|
|
268
|
+
console.error('Webhook Setup\n');
|
|
269
|
+
console.error('1. In Google Chat, find or create a space');
|
|
270
|
+
console.error('2. Go to Space Settings → Webhooks');
|
|
271
|
+
console.error('3. Create a new webhook and copy the URL\n');
|
|
272
|
+
|
|
273
|
+
const webhookUrl = await prompt('? Paste your webhook URL: ');
|
|
274
|
+
|
|
275
|
+
if (!webhookUrl) {
|
|
276
|
+
throw new CliError('INVALID_PARAMS', 'Webhook URL is required');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Validate webhook with a test request
|
|
280
|
+
try {
|
|
281
|
+
const response = await fetch(webhookUrl, {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: {
|
|
284
|
+
'Content-Type': 'application/json',
|
|
285
|
+
},
|
|
286
|
+
body: JSON.stringify({ text: 'Test message from agentio setup' }),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
throw new CliError(
|
|
291
|
+
'API_ERROR',
|
|
292
|
+
`Webhook validation failed: ${response.status}`,
|
|
293
|
+
'Check the webhook URL and try again'
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
} catch (err) {
|
|
297
|
+
if (err instanceof CliError) throw err;
|
|
298
|
+
throw new CliError(
|
|
299
|
+
'API_ERROR',
|
|
300
|
+
`Failed to validate webhook: ${err instanceof Error ? err.message : String(err)}`,
|
|
301
|
+
'Check that the URL is correct and accessible'
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const credentials: GChatWebhookCredentials = {
|
|
306
|
+
type: 'webhook',
|
|
307
|
+
webhookUrl: webhookUrl,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
await setProfile('gchat', profileName);
|
|
311
|
+
await setCredentials('gchat', profileName, credentials);
|
|
312
|
+
|
|
313
|
+
printProfileSetupSuccess(profileName, 'webhook');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function setupOAuthProfile(profileName: string): Promise<void> {
|
|
317
|
+
console.error('OAuth Setup\n');
|
|
318
|
+
console.error('Starting OAuth flow for Google Chat profile...\n');
|
|
319
|
+
|
|
320
|
+
const tokens = await performOAuthFlow('gchat');
|
|
321
|
+
|
|
322
|
+
// Optionally fetch user info - Chat API doesn't have a getProfile like Gmail
|
|
323
|
+
// For now, just validate the token works
|
|
324
|
+
try {
|
|
325
|
+
const auth = createGoogleAuth(tokens);
|
|
326
|
+
const chat = google.chat({ version: 'v1', auth });
|
|
327
|
+
// Simple validation: list spaces
|
|
328
|
+
await chat.spaces.list({ pageSize: 1 });
|
|
329
|
+
} catch (error) {
|
|
330
|
+
throw new CliError(
|
|
331
|
+
'AUTH_FAILED',
|
|
332
|
+
'Failed to validate Google Chat access. Check OAuth scopes.',
|
|
333
|
+
'Try again with: agentio gchat profile add --profile ' + profileName
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const credentials: GChatOAuthCredentials = {
|
|
338
|
+
type: 'oauth',
|
|
339
|
+
accessToken: tokens.access_token,
|
|
340
|
+
refreshToken: tokens.refresh_token,
|
|
341
|
+
expiryDate: tokens.expiry_date,
|
|
342
|
+
tokenType: tokens.token_type,
|
|
343
|
+
scope: tokens.scope,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
await setProfile('gchat', profileName);
|
|
347
|
+
await setCredentials('gchat', profileName, credentials);
|
|
348
|
+
|
|
349
|
+
printProfileSetupSuccess(profileName, 'oauth');
|
|
350
|
+
}
|
package/src/commands/gmail.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { basename } from 'path';
|
|
3
|
+
import { google } from 'googleapis';
|
|
3
4
|
import { getValidTokens, createGoogleAuth } from '../auth/token-manager';
|
|
4
|
-
import { setCredentials, removeCredentials } from '../auth/token-store';
|
|
5
|
+
import { setCredentials, removeCredentials, getCredentials } from '../auth/token-store';
|
|
5
6
|
import { setProfile, removeProfile, listProfiles } from '../config/config-manager';
|
|
6
7
|
import { performOAuthFlow } from '../auth/oauth';
|
|
7
8
|
import { GmailClient } from '../services/gmail/client';
|
|
@@ -250,10 +251,19 @@ Query Syntax Examples:
|
|
|
250
251
|
|
|
251
252
|
const tokens = await performOAuthFlow('gmail');
|
|
252
253
|
|
|
254
|
+
// Fetch the user's email to store with the profile
|
|
255
|
+
const auth = createGoogleAuth(tokens);
|
|
256
|
+
const gmail = google.gmail({ version: 'v1', auth });
|
|
257
|
+
const userProfile = await gmail.users.getProfile({ userId: 'me' });
|
|
258
|
+
const email = userProfile.data.emailAddress;
|
|
259
|
+
|
|
253
260
|
await setProfile('gmail', profileName);
|
|
254
|
-
await setCredentials('gmail', profileName, tokens);
|
|
261
|
+
await setCredentials('gmail', profileName, { ...tokens, email });
|
|
255
262
|
|
|
256
|
-
console.
|
|
263
|
+
console.log(`\nSuccess! Profile "${profileName}" for Gmail is now configured.`);
|
|
264
|
+
if (email) {
|
|
265
|
+
console.log(` Email: ${email}`);
|
|
266
|
+
}
|
|
257
267
|
} catch (error) {
|
|
258
268
|
handleError(error);
|
|
259
269
|
}
|
|
@@ -272,7 +282,9 @@ Query Syntax Examples:
|
|
|
272
282
|
} else {
|
|
273
283
|
for (const name of profiles) {
|
|
274
284
|
const marker = name === defaultProfile ? ' (default)' : '';
|
|
275
|
-
|
|
285
|
+
const credentials = await getCredentials<{ email?: string }>('gmail', name);
|
|
286
|
+
const emailInfo = credentials?.email ? ` - ${credentials.email}` : '';
|
|
287
|
+
console.log(`${name}${marker}${emailInfo}`);
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
290
|
} catch (error) {
|
package/src/commands/telegram.ts
CHANGED
|
@@ -191,13 +191,14 @@ export function registerTelegramCommands(program: Command): void {
|
|
|
191
191
|
bot_token: botToken,
|
|
192
192
|
channel_id: channelId,
|
|
193
193
|
bot_username: botInfo.username,
|
|
194
|
+
channel_name: channelName,
|
|
194
195
|
};
|
|
195
196
|
|
|
196
197
|
await setProfile('telegram', profileName);
|
|
197
198
|
await setCredentials('telegram', profileName, credentials);
|
|
198
199
|
|
|
199
|
-
console.
|
|
200
|
-
console.
|
|
200
|
+
console.log(`\n✅ Profile "${profileName}" configured!`);
|
|
201
|
+
console.log(` Test with: agentio telegram send --profile ${profileName} "Hello world"`);
|
|
201
202
|
} catch (error) {
|
|
202
203
|
handleError(error);
|
|
203
204
|
}
|
|
@@ -216,7 +217,9 @@ export function registerTelegramCommands(program: Command): void {
|
|
|
216
217
|
} else {
|
|
217
218
|
for (const name of profiles) {
|
|
218
219
|
const marker = name === defaultProfile ? ' (default)' : '';
|
|
219
|
-
|
|
220
|
+
const credentials = await getCredentials<TelegramCredentials>('telegram', name);
|
|
221
|
+
const channelInfo = credentials?.channel_name ? ` - ${credentials.channel_name}` : '';
|
|
222
|
+
console.log(`${name}${marker}${channelInfo}`);
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
225
|
} catch (error) {
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// Embedded OAuth credentials for agentio
|
|
2
2
|
// These are "public" credentials for a desktop/CLI app - this is standard practice
|
|
3
|
+
// Secret is lightly encrypted to avoid automated secret scanners (not real security)
|
|
4
|
+
|
|
5
|
+
import { reveal } from '../utils/obscure';
|
|
6
|
+
|
|
7
|
+
const CLIENT_ID = '931954287794-4rflctl8lotok5d6rnd4o6teuk02lked.apps.googleusercontent.com';
|
|
8
|
+
const CLIENT_SECRET_ENC = 'H2nByOfMnoQDg9BIGMyt_hznzMMTq-Or4wsZwiqT1ldl6z7bTMIdk9L8rDzQJ4l0i_pA';
|
|
3
9
|
|
|
4
10
|
export const GOOGLE_OAUTH_CONFIG = {
|
|
5
|
-
clientId:
|
|
6
|
-
clientSecret:
|
|
11
|
+
clientId: CLIENT_ID,
|
|
12
|
+
clientSecret: reveal(CLIENT_SECRET_ENC),
|
|
7
13
|
};
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { registerGmailCommands } from './commands/gmail';
|
|
4
4
|
import { registerTelegramCommands } from './commands/telegram';
|
|
5
|
+
import { registerGChatCommands } from './commands/gchat';
|
|
5
6
|
|
|
6
7
|
const program = new Command();
|
|
7
8
|
|
|
@@ -12,5 +13,6 @@ program
|
|
|
12
13
|
|
|
13
14
|
registerGmailCommands(program);
|
|
14
15
|
registerTelegramCommands(program);
|
|
16
|
+
registerGChatCommands(program);
|
|
15
17
|
|
|
16
18
|
program.parse();
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import type { chat_v1 } from 'googleapis';
|
|
3
|
+
import { CliError } from '../../utils/errors';
|
|
4
|
+
import type { ErrorCode } from '../../utils/errors';
|
|
5
|
+
import { GOOGLE_OAUTH_CONFIG } from '../../config/credentials';
|
|
6
|
+
import type {
|
|
7
|
+
GChatCredentials,
|
|
8
|
+
GChatSendOptions,
|
|
9
|
+
GChatSendResult,
|
|
10
|
+
GChatListOptions,
|
|
11
|
+
GChatGetOptions,
|
|
12
|
+
GChatWebhookCredentials,
|
|
13
|
+
GChatOAuthCredentials,
|
|
14
|
+
GChatMessage,
|
|
15
|
+
} from '../../types/gchat';
|
|
16
|
+
|
|
17
|
+
export class GChatClient {
|
|
18
|
+
private credentials: GChatCredentials;
|
|
19
|
+
|
|
20
|
+
constructor(credentials: GChatCredentials) {
|
|
21
|
+
this.credentials = credentials;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async send(options: GChatSendOptions & { spaceId?: string }): Promise<GChatSendResult> {
|
|
25
|
+
if (this.credentials.type === 'webhook') {
|
|
26
|
+
return this.sendViaWebhook(options);
|
|
27
|
+
} else {
|
|
28
|
+
return this.sendViaOAuth(options);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async list(options: GChatListOptions): Promise<GChatMessage[]> {
|
|
33
|
+
if (!options.spaceId?.trim()) {
|
|
34
|
+
throw new CliError(
|
|
35
|
+
'INVALID_PARAMS',
|
|
36
|
+
'spaceId is required for listing messages',
|
|
37
|
+
'Specify with --space or configure default in profile'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (this.credentials.type === 'webhook') {
|
|
41
|
+
throw new CliError(
|
|
42
|
+
'PERMISSION_DENIED',
|
|
43
|
+
'List is not supported for webhook profiles',
|
|
44
|
+
'Use an OAuth profile to read messages'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return this.listViaOAuth(options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async get(options: GChatGetOptions): Promise<GChatMessage> {
|
|
51
|
+
if (!options.spaceId?.trim() || !options.messageId?.trim()) {
|
|
52
|
+
throw new CliError(
|
|
53
|
+
'INVALID_PARAMS',
|
|
54
|
+
'Both spaceId and messageId are required',
|
|
55
|
+
'Specify with --space and message ID'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (this.credentials.type === 'webhook') {
|
|
59
|
+
throw new CliError(
|
|
60
|
+
'PERMISSION_DENIED',
|
|
61
|
+
'Get is not supported for webhook profiles',
|
|
62
|
+
'Use an OAuth profile to read messages'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return this.getViaOAuth(options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async sendViaWebhook(options: GChatSendOptions): Promise<GChatSendResult> {
|
|
69
|
+
const webhookUrl = (this.credentials as GChatWebhookCredentials).webhookUrl;
|
|
70
|
+
|
|
71
|
+
if (!webhookUrl?.trim() || !webhookUrl.startsWith('https://')) {
|
|
72
|
+
throw new CliError(
|
|
73
|
+
'INVALID_PARAMS',
|
|
74
|
+
'Invalid webhook URL - must be HTTPS',
|
|
75
|
+
'Check the webhook URL configuration'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Use raw payload if provided, otherwise construct simple text message
|
|
80
|
+
const payload = options.payload ?? { text: options.text };
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch(webhookUrl, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify(payload),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const error = await response.text();
|
|
93
|
+
throw new CliError(
|
|
94
|
+
'API_ERROR',
|
|
95
|
+
`Failed to send message via webhook: ${response.status} ${error}`,
|
|
96
|
+
'Check that the webhook URL is valid and the bot has permission to post'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Parse response to extract message ID
|
|
101
|
+
let messageId = 'unknown';
|
|
102
|
+
try {
|
|
103
|
+
const responseData = (await response.json()) as Record<string, unknown>;
|
|
104
|
+
const messageName = responseData.name as string | undefined;
|
|
105
|
+
if (messageName) {
|
|
106
|
+
messageId = messageName.split('/').pop() || 'unknown';
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// If response is not JSON or parsing fails, keep messageId as 'unknown'
|
|
110
|
+
// The message was still sent successfully (response.ok was true)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
messageId: messageId,
|
|
115
|
+
text: options.text,
|
|
116
|
+
isJsonPayload: !!options.payload,
|
|
117
|
+
};
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (err instanceof CliError) throw err;
|
|
120
|
+
throw new CliError(
|
|
121
|
+
'NETWORK_ERROR',
|
|
122
|
+
`Webhook request failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
123
|
+
'Verify the webhook URL is correct and accessible'
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async sendViaOAuth(options: GChatSendOptions & { spaceId?: string }): Promise<GChatSendResult> {
|
|
129
|
+
const oauthCreds = this.credentials as GChatOAuthCredentials;
|
|
130
|
+
const auth = this.createOAuthClient(oauthCreds);
|
|
131
|
+
const chat = google.chat({ version: 'v1', auth });
|
|
132
|
+
|
|
133
|
+
if (!options.spaceId) {
|
|
134
|
+
throw new CliError(
|
|
135
|
+
'INVALID_PARAMS',
|
|
136
|
+
'spaceId is required for OAuth profiles',
|
|
137
|
+
'Specify with --space or configure default in profile'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Use raw payload if provided, otherwise construct simple text message
|
|
142
|
+
const requestBody = options.payload ?? { text: options.text };
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const response = await chat.spaces.messages.create({
|
|
146
|
+
parent: `spaces/${options.spaceId}`,
|
|
147
|
+
requestBody: requestBody as chat_v1.Schema$Message,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const messageId = response.data.name?.split('/').pop() || 'unknown';
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
messageId: messageId,
|
|
154
|
+
spaceId: options.spaceId,
|
|
155
|
+
text: options.text,
|
|
156
|
+
isJsonPayload: !!options.payload,
|
|
157
|
+
};
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const code = this.getErrorCode(err);
|
|
160
|
+
const message = this.getErrorMessage(err);
|
|
161
|
+
throw new CliError(
|
|
162
|
+
code,
|
|
163
|
+
`Failed to send message: ${message}`,
|
|
164
|
+
'Check that the space ID is valid and OAuth token is not expired'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private async listViaOAuth(options: GChatListOptions): Promise<GChatMessage[]> {
|
|
170
|
+
const oauthCreds = this.credentials as GChatOAuthCredentials;
|
|
171
|
+
const auth = this.createOAuthClient(oauthCreds);
|
|
172
|
+
const chat = google.chat({ version: 'v1', auth });
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const response = await chat.spaces.messages.list({
|
|
176
|
+
parent: `spaces/${options.spaceId}`,
|
|
177
|
+
pageSize: options.limit || 10,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const messages = response.data.messages || [];
|
|
181
|
+
return messages.map((msg: chat_v1.Schema$Message) => {
|
|
182
|
+
const gchatMsg: GChatMessage = {
|
|
183
|
+
name: msg.name || '',
|
|
184
|
+
createTime: msg.createTime || new Date().toISOString(),
|
|
185
|
+
// updateTime is not defined in chat_v1.Schema$Message, use lastUpdateTime as fallback
|
|
186
|
+
updateTime: (msg as Record<string, unknown>).lastUpdateTime as string || new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
if (msg.text) gchatMsg.text = msg.text;
|
|
189
|
+
if (msg.sender?.name) {
|
|
190
|
+
gchatMsg.sender = {
|
|
191
|
+
name: msg.sender.name,
|
|
192
|
+
displayName: msg.sender.displayName || msg.sender.name,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (msg.thread?.name) {
|
|
196
|
+
gchatMsg.thread = {
|
|
197
|
+
name: msg.thread.name,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return gchatMsg;
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
const code = this.getErrorCode(err);
|
|
204
|
+
const message = this.getErrorMessage(err);
|
|
205
|
+
throw new CliError(
|
|
206
|
+
code,
|
|
207
|
+
`Failed to list messages: ${message}`,
|
|
208
|
+
'Check that the space ID is valid and OAuth token is not expired'
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async getViaOAuth(options: GChatGetOptions): Promise<GChatMessage> {
|
|
214
|
+
const oauthCreds = this.credentials as GChatOAuthCredentials;
|
|
215
|
+
const auth = this.createOAuthClient(oauthCreds);
|
|
216
|
+
const chat = google.chat({ version: 'v1', auth });
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const response = await chat.spaces.messages.get({
|
|
220
|
+
name: `spaces/${options.spaceId}/messages/${options.messageId}`,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!response.data) {
|
|
224
|
+
throw new Error('Message not found');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const msg = response.data as chat_v1.Schema$Message;
|
|
228
|
+
const gchatMsg: GChatMessage = {
|
|
229
|
+
name: msg.name || '',
|
|
230
|
+
createTime: msg.createTime || new Date().toISOString(),
|
|
231
|
+
// updateTime is not defined in chat_v1.Schema$Message, use lastUpdateTime as fallback
|
|
232
|
+
updateTime: (msg as Record<string, unknown>).lastUpdateTime as string || new Date().toISOString(),
|
|
233
|
+
};
|
|
234
|
+
if (msg.text) gchatMsg.text = msg.text;
|
|
235
|
+
if (msg.sender?.name) {
|
|
236
|
+
gchatMsg.sender = {
|
|
237
|
+
name: msg.sender.name,
|
|
238
|
+
displayName: msg.sender.displayName || msg.sender.name,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (msg.thread?.name) {
|
|
242
|
+
gchatMsg.thread = {
|
|
243
|
+
name: msg.thread.name,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return gchatMsg;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
const code = this.getErrorCode(err);
|
|
249
|
+
const message = this.getErrorMessage(err);
|
|
250
|
+
throw new CliError(
|
|
251
|
+
code,
|
|
252
|
+
`Failed to get message: ${message}`,
|
|
253
|
+
'Check that the space ID and message ID are valid'
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private getErrorCode(err: unknown): ErrorCode {
|
|
259
|
+
if (err && typeof err === 'object') {
|
|
260
|
+
const error = err as Record<string, unknown>;
|
|
261
|
+
const code = error.code || error.status;
|
|
262
|
+
if (code === 401) return 'AUTH_FAILED';
|
|
263
|
+
if (code === 403) return 'PERMISSION_DENIED';
|
|
264
|
+
if (code === 404) return 'NOT_FOUND';
|
|
265
|
+
if (code === 429) return 'RATE_LIMITED';
|
|
266
|
+
}
|
|
267
|
+
return 'API_ERROR';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private getErrorMessage(err: unknown): string {
|
|
271
|
+
if (err && typeof err === 'object') {
|
|
272
|
+
const error = err as Record<string, unknown>;
|
|
273
|
+
const code = error.code || error.status;
|
|
274
|
+
if (code === 401) return 'OAuth token expired or invalid';
|
|
275
|
+
if (code === 403) return 'Bot lacks permission for this operation';
|
|
276
|
+
if (code === 404) return 'Space or message not found';
|
|
277
|
+
if (code === 429) return 'Rate limit exceeded, please try again later';
|
|
278
|
+
if (error.message && typeof error.message === 'string') {
|
|
279
|
+
return error.message;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return err instanceof Error ? err.message : String(err);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private createOAuthClient(credentials: GChatOAuthCredentials) {
|
|
286
|
+
const oauth2Client = new google.auth.OAuth2(
|
|
287
|
+
GOOGLE_OAUTH_CONFIG.clientId,
|
|
288
|
+
GOOGLE_OAUTH_CONFIG.clientSecret
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
oauth2Client.setCredentials({
|
|
292
|
+
access_token: credentials.accessToken,
|
|
293
|
+
refresh_token: credentials.refreshToken,
|
|
294
|
+
expiry_date: credentials.expiryDate,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return oauth2Client;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface GChatSender {
|
|
2
|
+
name: string;
|
|
3
|
+
displayName: string;
|
|
4
|
+
avatarUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface GChatThread {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GChatMessage {
|
|
12
|
+
name: string; // 'spaces/SPACE_ID/messages/MESSAGE_ID'
|
|
13
|
+
displayName?: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
createTime: string;
|
|
16
|
+
updateTime: string;
|
|
17
|
+
sender?: GChatSender;
|
|
18
|
+
thread?: GChatThread;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GChatDisplaySettings {
|
|
22
|
+
displayName: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GChatSpace {
|
|
26
|
+
name: string; // 'spaces/SPACE_ID'
|
|
27
|
+
displayName: string;
|
|
28
|
+
type: 'ROOM' | 'DM';
|
|
29
|
+
description?: string;
|
|
30
|
+
displaySettings?: GChatDisplaySettings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GChatWebhookCredentials {
|
|
34
|
+
type: 'webhook';
|
|
35
|
+
webhookUrl: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GChatOAuthCredentials {
|
|
39
|
+
type: 'oauth';
|
|
40
|
+
accessToken: string;
|
|
41
|
+
refreshToken?: string;
|
|
42
|
+
expiryDate?: number;
|
|
43
|
+
tokenType: string;
|
|
44
|
+
scope?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type GChatCredentials = GChatWebhookCredentials | GChatOAuthCredentials;
|
|
48
|
+
|
|
49
|
+
export interface GChatSendOptions {
|
|
50
|
+
threadId?: string;
|
|
51
|
+
text?: string;
|
|
52
|
+
payload?: Record<string, unknown>; // Raw JSON payload for rich messages (cardsV2, etc.)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GChatListOptions {
|
|
56
|
+
spaceId: string;
|
|
57
|
+
limit?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface GChatGetOptions {
|
|
61
|
+
spaceId: string;
|
|
62
|
+
messageId: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface GChatSendResult {
|
|
66
|
+
messageId: string;
|
|
67
|
+
spaceId?: string;
|
|
68
|
+
text?: string;
|
|
69
|
+
isJsonPayload?: boolean;
|
|
70
|
+
}
|
package/src/types/telegram.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// Hardcoded key for obfuscation (not real security, just to avoid secret scanners)
|
|
4
|
+
// This is the same pattern rclone uses
|
|
5
|
+
const OBSCURE_KEY = Buffer.from('9c935b2aa628f0e9d48d5f3e8a4b7c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b', 'hex');
|
|
6
|
+
|
|
7
|
+
export function obscure(plaintext: string): string {
|
|
8
|
+
const iv = randomBytes(16);
|
|
9
|
+
const cipher = createCipheriv('aes-256-ctr', OBSCURE_KEY, iv);
|
|
10
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
11
|
+
const result = Buffer.concat([iv, encrypted]);
|
|
12
|
+
return result.toString('base64url');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function reveal(obscured: string): string {
|
|
16
|
+
const data = Buffer.from(obscured, 'base64url');
|
|
17
|
+
const iv = data.subarray(0, 16);
|
|
18
|
+
const encrypted = data.subarray(16);
|
|
19
|
+
const decipher = createDecipheriv('aes-256-ctr', OBSCURE_KEY, iv);
|
|
20
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
21
|
+
return decrypted.toString('utf8');
|
|
22
|
+
}
|
package/src/utils/output.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GmailMessage } from '../types/gmail';
|
|
2
|
+
import type { GChatMessage } from '../types/gchat';
|
|
2
3
|
|
|
3
4
|
// Format a list of Gmail messages
|
|
4
5
|
export function printMessageList(messages: GmailMessage[], total: number): void {
|
|
@@ -52,3 +53,53 @@ export function printMarked(messageId: string, read: boolean): void {
|
|
|
52
53
|
export function raw(text: string): void {
|
|
53
54
|
console.log(text);
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
// Google Chat specific formatters
|
|
58
|
+
export function printGChatSendResult(result: { messageId: string; spaceId?: string; isJsonPayload?: boolean }): void {
|
|
59
|
+
console.log('Message sent');
|
|
60
|
+
console.log(`ID: ${result.messageId}`);
|
|
61
|
+
if (result.spaceId) {
|
|
62
|
+
console.log(`Space: ${result.spaceId}`);
|
|
63
|
+
}
|
|
64
|
+
if (result.isJsonPayload) {
|
|
65
|
+
console.log('Type: JSON payload');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function printGChatMessageList(messages: GChatMessage[]): void {
|
|
70
|
+
if (messages.length === 0) {
|
|
71
|
+
console.log('No messages found');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`Messages (${messages.length})\n`);
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < messages.length; i++) {
|
|
78
|
+
const msg = messages[i];
|
|
79
|
+
console.log(`[${i + 1}] ${msg.name}`);
|
|
80
|
+
if (msg.sender) {
|
|
81
|
+
console.log(` From: ${msg.sender.displayName || 'Unknown'}`);
|
|
82
|
+
}
|
|
83
|
+
if (msg.text) {
|
|
84
|
+
const snippet = msg.text.length > 100 ? msg.text.substring(0, 100) + '...' : msg.text;
|
|
85
|
+
console.log(` > ${snippet}`);
|
|
86
|
+
}
|
|
87
|
+
console.log(` Date: ${msg.createTime}`);
|
|
88
|
+
console.log('');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function printGChatMessage(msg: GChatMessage): void {
|
|
93
|
+
console.log(`ID: ${msg.name}`);
|
|
94
|
+
if (msg.sender) {
|
|
95
|
+
console.log(`From: ${msg.sender.displayName || 'Unknown'}`);
|
|
96
|
+
}
|
|
97
|
+
console.log(`Date: ${msg.createTime}`);
|
|
98
|
+
if (msg.thread) {
|
|
99
|
+
console.log(`Thread: ${msg.thread.name}`);
|
|
100
|
+
}
|
|
101
|
+
if (msg.text) {
|
|
102
|
+
console.log('---');
|
|
103
|
+
console.log(msg.text);
|
|
104
|
+
}
|
|
105
|
+
}
|