@plosson/agentio 0.5.7 → 0.5.9
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/package.json +3 -2
- package/src/auth/oauth.ts +1 -0
- package/src/commands/gcal.ts +11 -2
- package/src/commands/gchat.ts +11 -2
- package/src/commands/gdocs.ts +11 -2
- package/src/commands/gdrive.ts +11 -2
- package/src/commands/gmail.ts +11 -2
- package/src/commands/gsheets.ts +11 -2
- package/src/commands/gtasks.ts +11 -2
- package/src/commands/reauth.ts +278 -0
- package/src/commands/status.ts +62 -49
- package/src/index.ts +2 -0
- package/src/polyfills.ts +5 -5
- package/src/services/gchat/client.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plosson/agentio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "CLI for LLM agents to interact with communication and tracking services",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,8 +55,9 @@
|
|
|
55
55
|
"@inquirer/prompts": "^8.2.0",
|
|
56
56
|
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
|
57
57
|
"commander": "^14.0.2",
|
|
58
|
-
"google-auth-library": "^
|
|
58
|
+
"google-auth-library": "^10.0.0",
|
|
59
59
|
"libsodium-wrappers": "^0.8.1",
|
|
60
|
+
"long": "^5.3.2",
|
|
60
61
|
"qrcode-terminal": "^0.12.0",
|
|
61
62
|
"rss-parser": "^3.13.0"
|
|
62
63
|
}
|
package/src/auth/oauth.ts
CHANGED
|
@@ -7,6 +7,7 @@ const GMAIL_SCOPES = [
|
|
|
7
7
|
'https://www.googleapis.com/auth/gmail.readonly', // search & read emails
|
|
8
8
|
'https://www.googleapis.com/auth/gmail.send', // send emails
|
|
9
9
|
'https://www.googleapis.com/auth/gmail.compose', // create/update drafts
|
|
10
|
+
'https://www.googleapis.com/auth/userinfo.email', // get email for profile naming
|
|
10
11
|
];
|
|
11
12
|
|
|
12
13
|
const GCHAT_SCOPES = [
|
package/src/commands/gcal.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { calendar } from '@googleapis/calendar';
|
|
3
3
|
import { getValidTokens, createGoogleAuth, fetchGoogleUserEmail } from '../auth/token-manager';
|
|
4
4
|
import { setCredentials } from '../auth/token-store';
|
|
5
|
-
import { setProfile } from '../config/config-manager';
|
|
5
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
6
6
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
7
7
|
import { performOAuthFlow } from '../auth/oauth';
|
|
8
8
|
import { GCalClient } from '../services/gcal/client';
|
|
@@ -373,7 +373,16 @@ export function registerGCalCommands(program: Command): void {
|
|
|
373
373
|
throw new CliError('AUTH_FAILED', 'Could not fetch email from Calendar', 'Try again or specify --profile manually');
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
|
|
376
|
+
// Determine profile name: use explicit --profile, or email, or email-readonly if conflict
|
|
377
|
+
let profileName: string;
|
|
378
|
+
if (options.profile) {
|
|
379
|
+
profileName = options.profile;
|
|
380
|
+
} else if (options.readOnly && await getProfile('gcal', email)) {
|
|
381
|
+
// Profile with email already exists, use -readonly suffix
|
|
382
|
+
profileName = `${email}-readonly`;
|
|
383
|
+
} else {
|
|
384
|
+
profileName = email;
|
|
385
|
+
}
|
|
377
386
|
|
|
378
387
|
await setProfile('gcal', profileName, { readOnly: options.readOnly });
|
|
379
388
|
await setCredentials('gcal', profileName, { ...tokens, email });
|
package/src/commands/gchat.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { chat as gchat } from '@googleapis/chat';
|
|
3
3
|
import { readFile } from 'fs/promises';
|
|
4
4
|
import { setCredentials } from '../auth/token-store';
|
|
5
|
-
import { setProfile } from '../config/config-manager';
|
|
5
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
6
6
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
7
7
|
import { createClientGetter } from '../utils/client-factory';
|
|
8
8
|
import { performOAuthFlow } from '../auth/oauth';
|
|
@@ -309,7 +309,16 @@ async function setupOAuthProfile(profileNameOverride?: string, readOnly?: boolea
|
|
|
309
309
|
);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
// Determine profile name: use explicit override, or email, or email-readonly if conflict
|
|
313
|
+
let profileName: string;
|
|
314
|
+
if (profileNameOverride) {
|
|
315
|
+
profileName = profileNameOverride;
|
|
316
|
+
} else if (readOnly && await getProfile('gchat', userEmail)) {
|
|
317
|
+
// Profile with email already exists, use -readonly suffix
|
|
318
|
+
profileName = `${userEmail}-readonly`;
|
|
319
|
+
} else {
|
|
320
|
+
profileName = userEmail;
|
|
321
|
+
}
|
|
313
322
|
|
|
314
323
|
const credentials: GChatOAuthCredentials = {
|
|
315
324
|
type: 'oauth',
|
package/src/commands/gdocs.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { writeFile } from 'fs/promises';
|
|
3
3
|
import { createGoogleAuth, fetchGoogleUserEmail } from '../auth/token-manager';
|
|
4
4
|
import { setCredentials } from '../auth/token-store';
|
|
5
|
-
import { setProfile } from '../config/config-manager';
|
|
5
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
6
6
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
7
7
|
import { createClientGetter } from '../utils/client-factory';
|
|
8
8
|
import { performOAuthFlow } from '../auth/oauth';
|
|
@@ -159,7 +159,16 @@ Query Syntax Examples:
|
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
// Determine profile name: use explicit --profile, or email, or email-readonly if conflict
|
|
163
|
+
let profileName: string;
|
|
164
|
+
if (options.profile) {
|
|
165
|
+
profileName = options.profile;
|
|
166
|
+
} else if (options.readOnly && await getProfile('gdocs', userEmail)) {
|
|
167
|
+
// Profile with email already exists, use -readonly suffix
|
|
168
|
+
profileName = `${userEmail}-readonly`;
|
|
169
|
+
} else {
|
|
170
|
+
profileName = userEmail;
|
|
171
|
+
}
|
|
163
172
|
|
|
164
173
|
const credentials: GDocsCredentials = {
|
|
165
174
|
accessToken: tokens.access_token,
|
package/src/commands/gdrive.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { createGoogleAuth, fetchGoogleUserEmail } from '../auth/token-manager';
|
|
3
3
|
import { setCredentials } from '../auth/token-store';
|
|
4
|
-
import { setProfile } from '../config/config-manager';
|
|
4
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
5
5
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
6
6
|
import { createClientGetter } from '../utils/client-factory';
|
|
7
7
|
import { performOAuthFlow } from '../auth/oauth';
|
|
@@ -258,7 +258,16 @@ Examples:
|
|
|
258
258
|
);
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
// Determine profile name: use explicit --profile, or email, or email-readonly if conflict
|
|
262
|
+
let profileName: string;
|
|
263
|
+
if (options.profile) {
|
|
264
|
+
profileName = options.profile;
|
|
265
|
+
} else if (options.readOnly && await getProfile('gdrive', userEmail)) {
|
|
266
|
+
// Profile with email already exists, use -readonly suffix
|
|
267
|
+
profileName = `${userEmail}-readonly`;
|
|
268
|
+
} else {
|
|
269
|
+
profileName = userEmail;
|
|
270
|
+
}
|
|
262
271
|
|
|
263
272
|
const credentials: GDriveCredentials = {
|
|
264
273
|
accessToken: tokens.access_token,
|
package/src/commands/gmail.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { basename, join } from 'path';
|
|
|
3
3
|
import { tmpdir } from 'os';
|
|
4
4
|
import { getValidTokens, createGoogleAuth, fetchGoogleUserEmail } from '../auth/token-manager';
|
|
5
5
|
import { setCredentials } from '../auth/token-store';
|
|
6
|
-
import { setProfile } from '../config/config-manager';
|
|
6
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
7
7
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
8
8
|
import { performOAuthFlow } from '../auth/oauth';
|
|
9
9
|
import { GmailClient } from '../services/gmail/client';
|
|
@@ -477,7 +477,16 @@ ${emailHeader}
|
|
|
477
477
|
throw new CliError('AUTH_FAILED', 'Could not fetch email from Gmail', 'Try again or specify --profile manually');
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
|
|
480
|
+
// Determine profile name: use explicit --profile, or email, or email-readonly if conflict
|
|
481
|
+
let profileName: string;
|
|
482
|
+
if (options.profile) {
|
|
483
|
+
profileName = options.profile;
|
|
484
|
+
} else if (options.readOnly && await getProfile('gmail', email)) {
|
|
485
|
+
// Profile with email already exists, use -readonly suffix
|
|
486
|
+
profileName = `${email}-readonly`;
|
|
487
|
+
} else {
|
|
488
|
+
profileName = email;
|
|
489
|
+
}
|
|
481
490
|
|
|
482
491
|
await setProfile('gmail', profileName, { readOnly: options.readOnly });
|
|
483
492
|
await setCredentials('gmail', profileName, { ...tokens, email });
|
package/src/commands/gsheets.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { writeFile } from 'fs/promises';
|
|
3
3
|
import { createGoogleAuth, fetchGoogleUserEmail } from '../auth/token-manager';
|
|
4
4
|
import { setCredentials } from '../auth/token-store';
|
|
5
|
-
import { setProfile } from '../config/config-manager';
|
|
5
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
6
6
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
7
7
|
import { createClientGetter } from '../utils/client-factory';
|
|
8
8
|
import { performOAuthFlow } from '../auth/oauth';
|
|
@@ -341,7 +341,16 @@ Examples:
|
|
|
341
341
|
throw new CliError('AUTH_FAILED', `Failed to fetch user email: ${errorMessage}`, 'Ensure the account has an email address');
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
|
|
344
|
+
// Determine profile name: use explicit --profile, or email, or email-readonly if conflict
|
|
345
|
+
let profileName: string;
|
|
346
|
+
if (options.profile) {
|
|
347
|
+
profileName = options.profile;
|
|
348
|
+
} else if (options.readOnly && await getProfile('gsheets', userEmail)) {
|
|
349
|
+
// Profile with email already exists, use -readonly suffix
|
|
350
|
+
profileName = `${userEmail}-readonly`;
|
|
351
|
+
} else {
|
|
352
|
+
profileName = userEmail;
|
|
353
|
+
}
|
|
345
354
|
|
|
346
355
|
const credentials: GSheetsCredentials = {
|
|
347
356
|
accessToken: tokens.access_token,
|
package/src/commands/gtasks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { getValidTokens, createGoogleAuth, fetchGoogleUserEmail } from '../auth/token-manager';
|
|
3
3
|
import { setCredentials } from '../auth/token-store';
|
|
4
|
-
import { setProfile } from '../config/config-manager';
|
|
4
|
+
import { setProfile, getProfile } from '../config/config-manager';
|
|
5
5
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
6
6
|
import { performOAuthFlow } from '../auth/oauth';
|
|
7
7
|
import { GTasksClient } from '../services/gtasks/client';
|
|
@@ -320,7 +320,16 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
320
320
|
throw new CliError('AUTH_FAILED', 'Could not fetch email', 'Try again or specify --profile manually');
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
-
|
|
323
|
+
// Determine profile name: use explicit --profile, or email, or email-readonly if conflict
|
|
324
|
+
let profileName: string;
|
|
325
|
+
if (options.profile) {
|
|
326
|
+
profileName = options.profile;
|
|
327
|
+
} else if (options.readOnly && await getProfile('gtasks', email)) {
|
|
328
|
+
// Profile with email already exists, use -readonly suffix
|
|
329
|
+
profileName = `${email}-readonly`;
|
|
330
|
+
} else {
|
|
331
|
+
profileName = email;
|
|
332
|
+
}
|
|
324
333
|
|
|
325
334
|
await setProfile('gtasks', profileName, { readOnly: options.readOnly });
|
|
326
335
|
await setCredentials('gtasks', profileName, { ...tokens, email });
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getProfileStatuses, type ProfileStatus } from './status';
|
|
3
|
+
import { getCredentials, setCredentials } from '../auth/token-store';
|
|
4
|
+
import { performOAuthFlow, type OAuthService } from '../auth/oauth';
|
|
5
|
+
import { performGitHubOAuthFlow } from '../auth/github-oauth';
|
|
6
|
+
import { performJiraOAuthFlow, type AtlassianSite } from '../auth/jira-oauth';
|
|
7
|
+
import { fetchGoogleUserEmail } from '../auth/token-manager';
|
|
8
|
+
import { GitHubClient } from '../services/github/client';
|
|
9
|
+
import { interactiveCheckbox, interactiveSelect } from '../utils/interactive';
|
|
10
|
+
import { handleError } from '../utils/errors';
|
|
11
|
+
import type { ServiceName } from '../types/config';
|
|
12
|
+
import type { GDocsCredentials } from '../types/gdocs';
|
|
13
|
+
import type { GDriveCredentials } from '../types/gdrive';
|
|
14
|
+
import type { GChatCredentials } from '../types/gchat';
|
|
15
|
+
import type { GSheetsCredentials } from '../types/gsheets';
|
|
16
|
+
import type { GitHubCredentials } from '../types/github';
|
|
17
|
+
import type { JiraCredentials } from '../types/jira';
|
|
18
|
+
import type { OAuthTokens } from '../types/tokens';
|
|
19
|
+
|
|
20
|
+
type GmailCredentials = OAuthTokens & { email?: string };
|
|
21
|
+
type GCalCredentials = OAuthTokens & { email?: string };
|
|
22
|
+
type GTasksCredentials = OAuthTokens & { email?: string };
|
|
23
|
+
|
|
24
|
+
// Services that use Google OAuth and store { ...tokens, email }
|
|
25
|
+
const GOOGLE_SIMPLE_SERVICES: ServiceName[] = ['gmail', 'gcal', 'gtasks'];
|
|
26
|
+
|
|
27
|
+
// Services that use Google OAuth with custom credential objects
|
|
28
|
+
const GOOGLE_CUSTOM_SERVICES: ServiceName[] = ['gdocs', 'gsheets'];
|
|
29
|
+
|
|
30
|
+
// Services that require manual credential setup
|
|
31
|
+
const MANUAL_SERVICES: ServiceName[] = ['telegram', 'slack', 'discourse', 'sql'];
|
|
32
|
+
|
|
33
|
+
async function reauthGoogleSimple(
|
|
34
|
+
service: ServiceName,
|
|
35
|
+
profileName: string
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const oauthService = service as OAuthService;
|
|
38
|
+
console.error(`\nRe-authenticating ${service} / ${profileName}...`);
|
|
39
|
+
|
|
40
|
+
const tokens = await performOAuthFlow(oauthService);
|
|
41
|
+
const email = await fetchGoogleUserEmail(tokens.access_token);
|
|
42
|
+
|
|
43
|
+
// Preserve existing credential fields, update tokens and email
|
|
44
|
+
const existing = await getCredentials<GmailCredentials | GCalCredentials | GTasksCredentials>(service, profileName);
|
|
45
|
+
await setCredentials(service, profileName, { ...existing, ...tokens, email });
|
|
46
|
+
|
|
47
|
+
console.error(` Done (${email})`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function reauthGoogleCustom(
|
|
51
|
+
service: ServiceName,
|
|
52
|
+
profileName: string
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const oauthService = service as OAuthService;
|
|
55
|
+
console.error(`\nRe-authenticating ${service} / ${profileName}...`);
|
|
56
|
+
|
|
57
|
+
const tokens = await performOAuthFlow(oauthService);
|
|
58
|
+
const email = await fetchGoogleUserEmail(tokens.access_token);
|
|
59
|
+
|
|
60
|
+
const existing = await getCredentials<GDocsCredentials | GSheetsCredentials>(service, profileName);
|
|
61
|
+
const credentials = {
|
|
62
|
+
...existing,
|
|
63
|
+
accessToken: tokens.access_token,
|
|
64
|
+
refreshToken: tokens.refresh_token,
|
|
65
|
+
expiryDate: tokens.expiry_date,
|
|
66
|
+
tokenType: tokens.token_type,
|
|
67
|
+
scope: tokens.scope,
|
|
68
|
+
email,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await setCredentials(service, profileName, credentials);
|
|
72
|
+
console.error(` Done (${email})`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function reauthGDrive(profileName: string): Promise<void> {
|
|
76
|
+
console.error(`\nRe-authenticating gdrive / ${profileName}...`);
|
|
77
|
+
|
|
78
|
+
// Read existing credentials to preserve accessLevel
|
|
79
|
+
const existing = await getCredentials<GDriveCredentials>('gdrive', profileName);
|
|
80
|
+
const accessLevel = existing?.accessLevel || 'readonly';
|
|
81
|
+
const oauthService: OAuthService = accessLevel === 'full' ? 'gdrive-full' : 'gdrive-readonly';
|
|
82
|
+
|
|
83
|
+
const tokens = await performOAuthFlow(oauthService);
|
|
84
|
+
const email = await fetchGoogleUserEmail(tokens.access_token);
|
|
85
|
+
|
|
86
|
+
const credentials: GDriveCredentials = {
|
|
87
|
+
...existing,
|
|
88
|
+
accessToken: tokens.access_token,
|
|
89
|
+
refreshToken: tokens.refresh_token,
|
|
90
|
+
expiryDate: tokens.expiry_date,
|
|
91
|
+
tokenType: tokens.token_type,
|
|
92
|
+
scope: tokens.scope,
|
|
93
|
+
email,
|
|
94
|
+
accessLevel,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await setCredentials('gdrive', profileName, credentials);
|
|
98
|
+
console.error(` Done (${email}, ${accessLevel})`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function reauthGChat(profileName: string): Promise<void> {
|
|
102
|
+
const existing = await getCredentials<GChatCredentials>('gchat', profileName);
|
|
103
|
+
|
|
104
|
+
if (existing?.type === 'webhook') {
|
|
105
|
+
console.error(`\nSkipping gchat / ${profileName}: webhook profiles don't expire. Run 'agentio gchat profile add' to update.`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.error(`\nRe-authenticating gchat / ${profileName}...`);
|
|
110
|
+
|
|
111
|
+
const tokens = await performOAuthFlow('gchat');
|
|
112
|
+
const email = await fetchGoogleUserEmail(tokens.access_token);
|
|
113
|
+
|
|
114
|
+
const credentials = {
|
|
115
|
+
...existing,
|
|
116
|
+
type: 'oauth' as const,
|
|
117
|
+
accessToken: tokens.access_token,
|
|
118
|
+
refreshToken: tokens.refresh_token,
|
|
119
|
+
expiryDate: tokens.expiry_date,
|
|
120
|
+
tokenType: tokens.token_type,
|
|
121
|
+
scope: tokens.scope,
|
|
122
|
+
email,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
await setCredentials('gchat', profileName, credentials);
|
|
126
|
+
console.error(` Done (${email})`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function reauthGitHub(profileName: string): Promise<void> {
|
|
130
|
+
console.error(`\nRe-authenticating github / ${profileName}...`);
|
|
131
|
+
|
|
132
|
+
const oauthResult = await performGitHubOAuthFlow();
|
|
133
|
+
|
|
134
|
+
// Fetch updated user info
|
|
135
|
+
const tempCreds: GitHubCredentials = {
|
|
136
|
+
accessToken: oauthResult.accessToken,
|
|
137
|
+
username: '',
|
|
138
|
+
email: null,
|
|
139
|
+
};
|
|
140
|
+
const client = new GitHubClient(tempCreds);
|
|
141
|
+
const user = await client.getUser();
|
|
142
|
+
|
|
143
|
+
// Preserve existing fields, update token and user info
|
|
144
|
+
const existing = await getCredentials<GitHubCredentials>('github', profileName);
|
|
145
|
+
const credentials: GitHubCredentials = {
|
|
146
|
+
...existing,
|
|
147
|
+
accessToken: oauthResult.accessToken,
|
|
148
|
+
username: user.login,
|
|
149
|
+
email: user.email,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
await setCredentials('github', profileName, credentials);
|
|
153
|
+
console.error(` Done (${user.login})`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function reauthJira(profileName: string): Promise<void> {
|
|
157
|
+
console.error(`\nRe-authenticating jira / ${profileName}...`);
|
|
158
|
+
|
|
159
|
+
const selectSite = async (sites: AtlassianSite[]): Promise<AtlassianSite> => {
|
|
160
|
+
return interactiveSelect({
|
|
161
|
+
message: 'Select a JIRA site:',
|
|
162
|
+
choices: sites.map((site) => ({
|
|
163
|
+
name: site.name,
|
|
164
|
+
value: site,
|
|
165
|
+
description: site.url,
|
|
166
|
+
})),
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const result = await performJiraOAuthFlow(selectSite);
|
|
171
|
+
|
|
172
|
+
const existing = await getCredentials<JiraCredentials>('jira', profileName);
|
|
173
|
+
const credentials: JiraCredentials = {
|
|
174
|
+
...existing,
|
|
175
|
+
accessToken: result.accessToken,
|
|
176
|
+
refreshToken: result.refreshToken,
|
|
177
|
+
expiryDate: result.expiryDate,
|
|
178
|
+
cloudId: result.cloudId,
|
|
179
|
+
siteUrl: result.siteUrl,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
await setCredentials('jira', profileName, credentials);
|
|
183
|
+
console.error(` Done (${result.siteUrl})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function reauthProfile(service: ServiceName, profileName: string): Promise<void> {
|
|
187
|
+
if (GOOGLE_SIMPLE_SERVICES.includes(service)) {
|
|
188
|
+
await reauthGoogleSimple(service, profileName);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (GOOGLE_CUSTOM_SERVICES.includes(service)) {
|
|
193
|
+
await reauthGoogleCustom(service, profileName);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
switch (service) {
|
|
198
|
+
case 'gdrive':
|
|
199
|
+
await reauthGDrive(profileName);
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case 'gchat':
|
|
203
|
+
await reauthGChat(profileName);
|
|
204
|
+
break;
|
|
205
|
+
|
|
206
|
+
case 'github':
|
|
207
|
+
await reauthGitHub(profileName);
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
case 'jira':
|
|
211
|
+
await reauthJira(profileName);
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case 'whatsapp':
|
|
215
|
+
console.error(`\nSkipping whatsapp / ${profileName}: use 'agentio whatsapp profile add' to re-pair.`);
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
default:
|
|
219
|
+
if (MANUAL_SERVICES.includes(service)) {
|
|
220
|
+
console.error(`\nSkipping ${service} / ${profileName}: uses manual credentials. Run 'agentio ${service} profile add' to update.`);
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function registerReauthCommand(program: Command): void {
|
|
227
|
+
program
|
|
228
|
+
.command('reauth')
|
|
229
|
+
.description('Re-authenticate expired or invalid profiles')
|
|
230
|
+
.option('--all', 'Re-authenticate all invalid profiles without prompting')
|
|
231
|
+
.action(async (options) => {
|
|
232
|
+
try {
|
|
233
|
+
console.error('Checking profile credentials...\n');
|
|
234
|
+
|
|
235
|
+
const statuses = await getProfileStatuses();
|
|
236
|
+
const invalid = statuses.filter(
|
|
237
|
+
(s) => s.status === 'invalid' || s.status === 'no-creds'
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (invalid.length === 0) {
|
|
241
|
+
console.log('All profiles are valid.');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let selected: ProfileStatus[];
|
|
246
|
+
|
|
247
|
+
if (options.all) {
|
|
248
|
+
selected = invalid;
|
|
249
|
+
} else {
|
|
250
|
+
const choices = invalid.map((s) => ({
|
|
251
|
+
name: `${s.service} / ${s.profile} (${s.error || 'no credentials'})`,
|
|
252
|
+
value: s,
|
|
253
|
+
checked: true,
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
selected = await interactiveCheckbox({
|
|
257
|
+
message: 'Select profiles to re-authenticate:',
|
|
258
|
+
choices,
|
|
259
|
+
required: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const s of selected) {
|
|
264
|
+
try {
|
|
265
|
+
await reauthProfile(s.service, s.profile);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error(
|
|
268
|
+
`\n Failed to reauth ${s.service} / ${s.profile}: ${error instanceof Error ? error.message : String(error)}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log('\nDone.');
|
|
274
|
+
} catch (error) {
|
|
275
|
+
handleError(error);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
package/src/commands/status.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { JiraClient } from '../services/jira/client';
|
|
|
14
14
|
import { GChatClient } from '../services/gchat/client';
|
|
15
15
|
import { SlackClient } from '../services/slack/client';
|
|
16
16
|
import { DiscourseClient } from '../services/discourse/client';
|
|
17
|
+
import { GSheetsClient } from '../services/gsheets/client';
|
|
17
18
|
import { SqlClient } from '../services/sql/client';
|
|
18
19
|
import type { ServiceClient, ValidationResult } from '../types/service';
|
|
19
20
|
import type { ServiceName } from '../types/config';
|
|
@@ -26,6 +27,7 @@ import type { GDriveCredentials } from '../types/gdrive';
|
|
|
26
27
|
import type { GCalCredentials } from '../types/gcal';
|
|
27
28
|
import type { GTasksCredentials } from '../types/gtasks';
|
|
28
29
|
import type { GChatCredentials } from '../types/gchat';
|
|
30
|
+
import type { GSheetsCredentials } from '../types/gsheets';
|
|
29
31
|
import type { SlackCredentials } from '../types/slack';
|
|
30
32
|
import type { DiscourseCredentials } from '../types/discourse';
|
|
31
33
|
import type { SqlCredentials } from '../types/sql';
|
|
@@ -66,6 +68,11 @@ async function createServiceClient(
|
|
|
66
68
|
return new GDriveClient(creds);
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
case 'gsheets': {
|
|
72
|
+
const creds = credentials as GSheetsCredentials;
|
|
73
|
+
return new GSheetsClient(creds);
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
case 'gcal': {
|
|
70
77
|
const creds = credentials as GCalCredentials;
|
|
71
78
|
const auth = createGoogleAuth({
|
|
@@ -240,8 +247,8 @@ async function createServiceClient(
|
|
|
240
247
|
}
|
|
241
248
|
}
|
|
242
249
|
|
|
243
|
-
interface ProfileStatus {
|
|
244
|
-
service:
|
|
250
|
+
export interface ProfileStatus {
|
|
251
|
+
service: ServiceName;
|
|
245
252
|
profile: string;
|
|
246
253
|
readOnly?: boolean;
|
|
247
254
|
status: 'ok' | 'invalid' | 'no-creds' | 'skipped';
|
|
@@ -249,6 +256,58 @@ interface ProfileStatus {
|
|
|
249
256
|
error?: string;
|
|
250
257
|
}
|
|
251
258
|
|
|
259
|
+
export async function getProfileStatuses(options?: { test?: boolean }): Promise<ProfileStatus[]> {
|
|
260
|
+
const shouldTest = options?.test !== false;
|
|
261
|
+
const allProfiles = await listProfiles();
|
|
262
|
+
const statuses: ProfileStatus[] = [];
|
|
263
|
+
|
|
264
|
+
for (const { service, profiles } of allProfiles) {
|
|
265
|
+
for (const entry of profiles) {
|
|
266
|
+
const credentials = await getCredentials(service, entry.name);
|
|
267
|
+
|
|
268
|
+
if (!credentials) {
|
|
269
|
+
statuses.push({
|
|
270
|
+
service,
|
|
271
|
+
profile: entry.name,
|
|
272
|
+
readOnly: entry.readOnly,
|
|
273
|
+
status: 'no-creds',
|
|
274
|
+
});
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!shouldTest) {
|
|
279
|
+
statuses.push({
|
|
280
|
+
service,
|
|
281
|
+
profile: entry.name,
|
|
282
|
+
readOnly: entry.readOnly,
|
|
283
|
+
status: 'skipped',
|
|
284
|
+
});
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const client = await createServiceClient(service, credentials, entry.name);
|
|
289
|
+
let result: ValidationResult;
|
|
290
|
+
|
|
291
|
+
if (client) {
|
|
292
|
+
result = await client.validate();
|
|
293
|
+
} else {
|
|
294
|
+
result = { valid: true, info: 'unknown service' };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
statuses.push({
|
|
298
|
+
service,
|
|
299
|
+
profile: entry.name,
|
|
300
|
+
readOnly: entry.readOnly,
|
|
301
|
+
status: result.valid ? 'ok' : 'invalid',
|
|
302
|
+
info: result.info,
|
|
303
|
+
error: result.error,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return statuses;
|
|
309
|
+
}
|
|
310
|
+
|
|
252
311
|
export function registerStatusCommand(program: Command): void {
|
|
253
312
|
program
|
|
254
313
|
.command('status')
|
|
@@ -257,57 +316,11 @@ export function registerStatusCommand(program: Command): void {
|
|
|
257
316
|
.option('--json', 'Output in JSON format')
|
|
258
317
|
.action(async (options) => {
|
|
259
318
|
try {
|
|
260
|
-
const allProfiles = await listProfiles();
|
|
261
319
|
const version = program.version();
|
|
262
320
|
const envVars = await listEnv();
|
|
263
321
|
const envKeys = Object.keys(envVars).sort();
|
|
264
322
|
|
|
265
|
-
|
|
266
|
-
const statuses: ProfileStatus[] = [];
|
|
267
|
-
|
|
268
|
-
for (const { service, profiles } of allProfiles) {
|
|
269
|
-
for (const entry of profiles) {
|
|
270
|
-
const credentials = await getCredentials(service, entry.name);
|
|
271
|
-
|
|
272
|
-
if (!credentials) {
|
|
273
|
-
statuses.push({
|
|
274
|
-
service,
|
|
275
|
-
profile: entry.name,
|
|
276
|
-
readOnly: entry.readOnly,
|
|
277
|
-
status: 'no-creds',
|
|
278
|
-
});
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (options.test === false) {
|
|
283
|
-
statuses.push({
|
|
284
|
-
service,
|
|
285
|
-
profile: entry.name,
|
|
286
|
-
readOnly: entry.readOnly,
|
|
287
|
-
status: 'skipped',
|
|
288
|
-
});
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const client = await createServiceClient(service, credentials, entry.name);
|
|
293
|
-
let result: ValidationResult;
|
|
294
|
-
|
|
295
|
-
if (client) {
|
|
296
|
-
result = await client.validate();
|
|
297
|
-
} else {
|
|
298
|
-
result = { valid: true, info: 'unknown service' };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
statuses.push({
|
|
302
|
-
service,
|
|
303
|
-
profile: entry.name,
|
|
304
|
-
readOnly: entry.readOnly,
|
|
305
|
-
status: result.valid ? 'ok' : 'invalid',
|
|
306
|
-
info: result.info,
|
|
307
|
-
error: result.error,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
323
|
+
const statuses = await getProfileStatuses({ test: options.test });
|
|
311
324
|
|
|
312
325
|
// JSON output mode
|
|
313
326
|
if (options.json) {
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { registerClaudeCommands } from './commands/claude';
|
|
|
24
24
|
import { registerConfigCommands } from './commands/config';
|
|
25
25
|
import { registerDocsCommand } from './commands/docs';
|
|
26
26
|
import { registerGatewayCommands } from './commands/gateway';
|
|
27
|
+
import { registerReauthCommand } from './commands/reauth';
|
|
27
28
|
import { registerStatusCommand } from './commands/status';
|
|
28
29
|
import { registerUpdateCommand } from './commands/update';
|
|
29
30
|
|
|
@@ -66,6 +67,7 @@ registerClaudeCommands(program);
|
|
|
66
67
|
registerConfigCommands(program);
|
|
67
68
|
registerDocsCommand(program);
|
|
68
69
|
registerGatewayCommands(program);
|
|
70
|
+
registerReauthCommand(program);
|
|
69
71
|
registerStatusCommand(program);
|
|
70
72
|
registerUpdateCommand(program);
|
|
71
73
|
|
package/src/polyfills.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// This must be imported BEFORE any code that uses protobufjs (e.g., Baileys)
|
|
3
3
|
|
|
4
4
|
import Long from 'long';
|
|
5
|
-
import protobuf from 'protobufjs';
|
|
6
5
|
|
|
7
|
-
// Fix for protobufjs Long support in Bun native binaries
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
// Fix for protobufjs Long support in Bun native binaries.
|
|
7
|
+
// protobufjs/minimal uses inquire("long") with eval-based require that fails in Bun bundles.
|
|
8
|
+
// It falls back to checking global.Long (util/minimal.js:181), so we set it on globalThis
|
|
9
|
+
// BEFORE any protobufjs code initializes (WAProto evaluates $util.Long at module load time).
|
|
10
|
+
(globalThis as any).Long = Long;
|
|
@@ -33,7 +33,7 @@ export class GChatClient implements ServiceClient {
|
|
|
33
33
|
const auth = this.createOAuthClient(oauthCreds);
|
|
34
34
|
const chat = gchat({ version: 'v1', auth: auth as any });
|
|
35
35
|
await chat.spaces.list({ pageSize: 1 });
|
|
36
|
-
return { valid: true, info: 'oauth' };
|
|
36
|
+
return { valid: true, info: oauthCreds.email || 'oauth' };
|
|
37
37
|
} catch (error) {
|
|
38
38
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
39
39
|
if (message.includes('invalid_grant') || message.includes('Token has been expired or revoked')) {
|