@plosson/agentio 0.4.4 → 0.5.1
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 +1 -1
- package/src/commands/config.ts +13 -7
- package/src/commands/discourse.ts +5 -1
- package/src/commands/gateway.ts +381 -88
- package/src/commands/gcal.ts +14 -5
- package/src/commands/gchat.ts +16 -10
- package/src/commands/gdocs.ts +8 -2
- package/src/commands/gdrive.ts +9 -3
- package/src/commands/github.ts +8 -1
- package/src/commands/gmail.ts +14 -5
- package/src/commands/gsheets.ts +16 -6
- package/src/commands/gtasks.ts +24 -10
- package/src/commands/jira.ts +10 -3
- package/src/commands/slack.ts +10 -4
- package/src/commands/sql.ts +18 -2
- package/src/commands/status.ts +13 -7
- package/src/commands/telegram.ts +14 -2
- package/src/commands/whatsapp.ts +24 -4
- package/src/config/config-manager.ts +104 -14
- package/src/gateway/api.ts +9 -12
- package/src/gateway/client.ts +18 -15
- package/src/gateway/daemon.ts +35 -203
- package/src/gateway/types.ts +4 -4
- package/src/services/gdrive/client.ts +19 -9
- package/src/types/config.ts +30 -21
- package/src/utils/output.ts +8 -15
- package/src/utils/profile-commands.ts +36 -5
- package/src/utils/read-only.ts +22 -0
package/src/commands/gcal.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { GCalClient } from '../services/gcal/client';
|
|
|
9
9
|
import { printGCalCalendarList, printGCalEventList, printGCalEvent, printGCalEventCreated, printGCalEventDeleted, printGCalFreeBusy } from '../utils/output';
|
|
10
10
|
import { CliError, handleError } from '../utils/errors';
|
|
11
11
|
import { readStdin } from '../utils/stdin';
|
|
12
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
12
13
|
|
|
13
14
|
async function getGCalClient(profileName?: string): Promise<{ client: GCalClient; profile: string }> {
|
|
14
15
|
const { tokens, profile } = await getValidTokens('gcal', profileName);
|
|
@@ -155,7 +156,8 @@ export function registerGCalCommands(program: Command): void {
|
|
|
155
156
|
return { method: method as 'email' | 'popup', minutes: parseInt(minutes, 10) };
|
|
156
157
|
});
|
|
157
158
|
|
|
158
|
-
const { client } = await getGCalClient(options.profile);
|
|
159
|
+
const { client, profile } = await getGCalClient(options.profile);
|
|
160
|
+
await enforceWriteAccess('gcal', profile, 'create event');
|
|
159
161
|
const event = await client.createEvent({
|
|
160
162
|
calendarId: calendarId || 'primary',
|
|
161
163
|
summary: options.summary,
|
|
@@ -210,7 +212,8 @@ export function registerGCalCommands(program: Command): void {
|
|
|
210
212
|
throw new CliError('INVALID_PARAMS', 'Cannot use both --attendee and --add-attendee');
|
|
211
213
|
}
|
|
212
214
|
|
|
213
|
-
const { client } = await getGCalClient(options.profile);
|
|
215
|
+
const { client, profile } = await getGCalClient(options.profile);
|
|
216
|
+
await enforceWriteAccess('gcal', profile, 'update event');
|
|
214
217
|
const event = await client.updateEvent({
|
|
215
218
|
calendarId,
|
|
216
219
|
eventId,
|
|
@@ -243,7 +246,8 @@ export function registerGCalCommands(program: Command): void {
|
|
|
243
246
|
.option('--send-updates <mode>', 'Send notifications: all, externalOnly, none', 'all')
|
|
244
247
|
.action(async (calendarId: string, eventId: string, options) => {
|
|
245
248
|
try {
|
|
246
|
-
const { client } = await getGCalClient(options.profile);
|
|
249
|
+
const { client, profile } = await getGCalClient(options.profile);
|
|
250
|
+
await enforceWriteAccess('gcal', profile, 'delete event');
|
|
247
251
|
await client.deleteEvent(calendarId, eventId, options.sendUpdates);
|
|
248
252
|
printGCalEventDeleted(calendarId, eventId);
|
|
249
253
|
} catch (error) {
|
|
@@ -300,7 +304,8 @@ export function registerGCalCommands(program: Command): void {
|
|
|
300
304
|
throw new CliError('INVALID_PARAMS', `Invalid status: ${options.status}`, 'Use: accepted, declined, or tentative');
|
|
301
305
|
}
|
|
302
306
|
|
|
303
|
-
const { client } = await getGCalClient(options.profile);
|
|
307
|
+
const { client, profile } = await getGCalClient(options.profile);
|
|
308
|
+
await enforceWriteAccess('gcal', profile, 'respond to event');
|
|
304
309
|
const event = await client.respond({
|
|
305
310
|
calendarId,
|
|
306
311
|
eventId,
|
|
@@ -353,6 +358,7 @@ export function registerGCalCommands(program: Command): void {
|
|
|
353
358
|
.command('add')
|
|
354
359
|
.description('Add a new Google Calendar profile')
|
|
355
360
|
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
361
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
356
362
|
.action(async (options) => {
|
|
357
363
|
try {
|
|
358
364
|
console.error('Starting OAuth flow for Google Calendar...\n');
|
|
@@ -371,11 +377,14 @@ export function registerGCalCommands(program: Command): void {
|
|
|
371
377
|
|
|
372
378
|
const profileName = options.profile || email;
|
|
373
379
|
|
|
374
|
-
await setProfile('gcal', profileName);
|
|
380
|
+
await setProfile('gcal', profileName, { readOnly: options.readOnly });
|
|
375
381
|
await setCredentials('gcal', profileName, { ...tokens, email });
|
|
376
382
|
|
|
377
383
|
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
378
384
|
console.log(` Email: ${email}`);
|
|
385
|
+
if (options.readOnly) {
|
|
386
|
+
console.log(` Access: read-only`);
|
|
387
|
+
}
|
|
379
388
|
} catch (error) {
|
|
380
389
|
handleError(error);
|
|
381
390
|
}
|
package/src/commands/gchat.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { CliError, handleError } from '../utils/errors';
|
|
|
12
12
|
import { readStdin, prompt } from '../utils/stdin';
|
|
13
13
|
import { interactiveSelect } from '../utils/interactive';
|
|
14
14
|
import { printGChatSendResult, printGChatMessageList, printGChatMessage, printGChatSpaceList } from '../utils/output';
|
|
15
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
15
16
|
import type { GChatCredentials, GChatWebhookCredentials, GChatOAuthCredentials } from '../types/gchat';
|
|
16
17
|
|
|
17
18
|
const getGChatClient = createClientGetter<GChatCredentials, GChatClient>({
|
|
@@ -95,7 +96,8 @@ export function registerGChatCommands(program: Command): void {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
const { client } = await getGChatClient(options.profile);
|
|
99
|
+
const { client, profile } = await getGChatClient(options.profile);
|
|
100
|
+
await enforceWriteAccess('gchat', profile, 'send message');
|
|
99
101
|
const result = await client.send({
|
|
100
102
|
text,
|
|
101
103
|
payload,
|
|
@@ -185,6 +187,7 @@ export function registerGChatCommands(program: Command): void {
|
|
|
185
187
|
.command('add')
|
|
186
188
|
.description('Add a new Google Chat profile (webhook or OAuth)')
|
|
187
189
|
.option('--profile <name>', 'Profile name (required for webhook, auto-detected for OAuth)')
|
|
190
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
188
191
|
.action(async (options) => {
|
|
189
192
|
try {
|
|
190
193
|
console.error('\nGoogle Chat Setup\n');
|
|
@@ -205,9 +208,9 @@ export function registerGChatCommands(program: Command): void {
|
|
|
205
208
|
'Run: agentio gchat profile add --profile <name>'
|
|
206
209
|
);
|
|
207
210
|
}
|
|
208
|
-
await setupWebhookProfile(options.profile);
|
|
211
|
+
await setupWebhookProfile(options.profile, options.readOnly);
|
|
209
212
|
} else {
|
|
210
|
-
await setupOAuthProfile(options.profile);
|
|
213
|
+
await setupOAuthProfile(options.profile, options.readOnly);
|
|
211
214
|
}
|
|
212
215
|
} catch (error) {
|
|
213
216
|
handleError(error);
|
|
@@ -215,13 +218,16 @@ export function registerGChatCommands(program: Command): void {
|
|
|
215
218
|
});
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
function printProfileSetupSuccess(profileName: string, authType: 'webhook' | 'oauth'): void {
|
|
221
|
+
function printProfileSetupSuccess(profileName: string, authType: 'webhook' | 'oauth', readOnly?: boolean): void {
|
|
219
222
|
const typeLabel = authType.charAt(0).toUpperCase() + authType.slice(1);
|
|
220
223
|
console.log(`\nSuccess! ${typeLabel} profile "${profileName}" configured.`);
|
|
224
|
+
if (readOnly) {
|
|
225
|
+
console.log(` Access: read-only`);
|
|
226
|
+
}
|
|
221
227
|
console.log(` Test with: agentio gchat send --profile ${profileName} "Hello from agentio"`);
|
|
222
228
|
}
|
|
223
229
|
|
|
224
|
-
async function setupWebhookProfile(profileName: string): Promise<void> {
|
|
230
|
+
async function setupWebhookProfile(profileName: string, readOnly?: boolean): Promise<void> {
|
|
225
231
|
console.error('Webhook Setup\n');
|
|
226
232
|
console.error('1. In Google Chat, find or create a space');
|
|
227
233
|
console.error('2. Go to Space Settings → Webhooks');
|
|
@@ -264,13 +270,13 @@ async function setupWebhookProfile(profileName: string): Promise<void> {
|
|
|
264
270
|
webhookUrl: webhookUrl,
|
|
265
271
|
};
|
|
266
272
|
|
|
267
|
-
await setProfile('gchat', profileName);
|
|
273
|
+
await setProfile('gchat', profileName, { readOnly });
|
|
268
274
|
await setCredentials('gchat', profileName, credentials);
|
|
269
275
|
|
|
270
|
-
printProfileSetupSuccess(profileName, 'webhook');
|
|
276
|
+
printProfileSetupSuccess(profileName, 'webhook', readOnly);
|
|
271
277
|
}
|
|
272
278
|
|
|
273
|
-
async function setupOAuthProfile(profileNameOverride?: string): Promise<void> {
|
|
279
|
+
async function setupOAuthProfile(profileNameOverride?: string, readOnly?: boolean): Promise<void> {
|
|
274
280
|
console.error('OAuth Setup\n');
|
|
275
281
|
console.error('Starting OAuth flow for Google Chat profile...\n');
|
|
276
282
|
|
|
@@ -320,8 +326,8 @@ async function setupOAuthProfile(profileNameOverride?: string): Promise<void> {
|
|
|
320
326
|
email: userEmail,
|
|
321
327
|
};
|
|
322
328
|
|
|
323
|
-
await setProfile('gchat', profileName);
|
|
329
|
+
await setProfile('gchat', profileName, { readOnly });
|
|
324
330
|
await setCredentials('gchat', profileName, credentials);
|
|
325
331
|
|
|
326
|
-
printProfileSetupSuccess(profileName, 'oauth');
|
|
332
|
+
printProfileSetupSuccess(profileName, 'oauth', readOnly);
|
|
327
333
|
}
|
package/src/commands/gdocs.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { GDocsClient } from '../services/gdocs/client';
|
|
|
11
11
|
import { printGDocsList, printGDocCreated, raw } from '../utils/output';
|
|
12
12
|
import { CliError, handleError } from '../utils/errors';
|
|
13
13
|
import { readStdin } from '../utils/stdin';
|
|
14
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
14
15
|
import type { GDocsCredentials } from '../types/gdocs';
|
|
15
16
|
|
|
16
17
|
const getGDocsClient = createClientGetter<GDocsCredentials, GDocsClient>({
|
|
@@ -77,7 +78,8 @@ export function registerGDocsCommands(program: Command): void {
|
|
|
77
78
|
throw new CliError('INVALID_PARAMS', 'No content provided', 'Provide --content or pipe markdown via stdin');
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
const { client } = await getGDocsClient(options.profile);
|
|
81
|
+
const { client, profile } = await getGDocsClient(options.profile);
|
|
82
|
+
await enforceWriteAccess('gdocs', profile, 'create document');
|
|
81
83
|
const result = await client.create(options.title, content, options.folder);
|
|
82
84
|
|
|
83
85
|
printGDocCreated(result);
|
|
@@ -138,6 +140,7 @@ Query Syntax Examples:
|
|
|
138
140
|
.command('add')
|
|
139
141
|
.description('Add a new Google Docs profile')
|
|
140
142
|
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
143
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
141
144
|
.action(async (options) => {
|
|
142
145
|
try {
|
|
143
146
|
console.error('Starting OAuth flow for Google Docs...\n');
|
|
@@ -174,11 +177,14 @@ Query Syntax Examples:
|
|
|
174
177
|
email: userEmail,
|
|
175
178
|
};
|
|
176
179
|
|
|
177
|
-
await setProfile('gdocs', profileName);
|
|
180
|
+
await setProfile('gdocs', profileName, { readOnly: options.readOnly });
|
|
178
181
|
await setCredentials('gdocs', profileName, credentials);
|
|
179
182
|
|
|
180
183
|
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
181
184
|
console.log(` Email: ${userEmail}`);
|
|
185
|
+
if (options.readOnly) {
|
|
186
|
+
console.log(` Access: read-only`);
|
|
187
|
+
}
|
|
182
188
|
console.log(` Test with: agentio gdocs list --profile ${profileName}`);
|
|
183
189
|
} catch (error) {
|
|
184
190
|
handleError(error);
|
package/src/commands/gdrive.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { GDriveClient } from '../services/gdrive/client';
|
|
|
10
10
|
import { printGDriveFileList, printGDriveFile, printGDriveDownloaded, printGDriveUploaded } from '../utils/output';
|
|
11
11
|
import { CliError, handleError } from '../utils/errors';
|
|
12
12
|
import { prompt } from '../utils/stdin';
|
|
13
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
13
14
|
import type { GDriveCredentials, GDriveAccessLevel } from '../types/gdrive';
|
|
14
15
|
|
|
15
16
|
const getGDriveClient = createClientGetter<GDriveCredentials, GDriveClient>({
|
|
@@ -189,7 +190,8 @@ Examples:
|
|
|
189
190
|
`)
|
|
190
191
|
.action(async (filePath: string, options) => {
|
|
191
192
|
try {
|
|
192
|
-
const { client } = await getGDriveClient(options.profile);
|
|
193
|
+
const { client, profile } = await getGDriveClient(options.profile);
|
|
194
|
+
await enforceWriteAccess('gdrive', profile, 'upload file');
|
|
193
195
|
const result = await client.upload({
|
|
194
196
|
filePath,
|
|
195
197
|
name: options.name,
|
|
@@ -220,6 +222,7 @@ Examples:
|
|
|
220
222
|
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
221
223
|
.option('--readonly', 'Create a read-only profile (skip access level prompt)')
|
|
222
224
|
.option('--full', 'Create a full access profile (skip access level prompt)')
|
|
225
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
223
226
|
.action(async (options) => {
|
|
224
227
|
try {
|
|
225
228
|
console.error('Google Drive Setup\n');
|
|
@@ -274,12 +277,15 @@ Examples:
|
|
|
274
277
|
accessLevel,
|
|
275
278
|
};
|
|
276
279
|
|
|
277
|
-
await setProfile('gdrive', profileName);
|
|
280
|
+
await setProfile('gdrive', profileName, { readOnly: options.readOnly });
|
|
278
281
|
await setCredentials('gdrive', profileName, credentials);
|
|
279
282
|
|
|
280
283
|
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
281
284
|
console.log(` Email: ${userEmail}`);
|
|
282
|
-
console.log(` Access: ${accessLevel === 'full' ? 'Full (read & write)' : 'Read-only'}`);
|
|
285
|
+
console.log(` API Access: ${accessLevel === 'full' ? 'Full (read & write)' : 'Read-only'}`);
|
|
286
|
+
if (options.readOnly) {
|
|
287
|
+
console.log(` Profile Access: read-only`);
|
|
288
|
+
}
|
|
283
289
|
console.log(` Test with: agentio gdrive list --profile ${profileName}`);
|
|
284
290
|
} catch (error) {
|
|
285
291
|
handleError(error);
|
package/src/commands/github.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { GitHubClient } from '../services/github/client';
|
|
|
7
7
|
import { performGitHubOAuthFlow } from '../auth/github-oauth';
|
|
8
8
|
import { generateExportData } from './config';
|
|
9
9
|
import { CliError, handleError } from '../utils/errors';
|
|
10
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
10
11
|
import type { GitHubCredentials } from '../types/github';
|
|
11
12
|
|
|
12
13
|
const getGitHubClient = createClientGetter<GitHubCredentials, GitHubClient>({
|
|
@@ -42,6 +43,7 @@ export function registerGitHubCommands(program: Command): void {
|
|
|
42
43
|
parseRepo(repo);
|
|
43
44
|
|
|
44
45
|
const { client, profile } = await getGitHubClient(options.profile);
|
|
46
|
+
await enforceWriteAccess('github', profile, 'install secrets');
|
|
45
47
|
|
|
46
48
|
console.error(`Using GitHub profile: ${profile}`);
|
|
47
49
|
console.error(`Installing secrets to: ${repo}`);
|
|
@@ -77,6 +79,7 @@ export function registerGitHubCommands(program: Command): void {
|
|
|
77
79
|
parseRepo(repo);
|
|
78
80
|
|
|
79
81
|
const { client, profile } = await getGitHubClient(options.profile);
|
|
82
|
+
await enforceWriteAccess('github', profile, 'uninstall secrets');
|
|
80
83
|
|
|
81
84
|
console.error(`Using GitHub profile: ${profile}`);
|
|
82
85
|
console.error(`Removing secrets from: ${repo}`);
|
|
@@ -105,6 +108,7 @@ export function registerGitHubCommands(program: Command): void {
|
|
|
105
108
|
.command('add')
|
|
106
109
|
.description('Add a new GitHub profile')
|
|
107
110
|
.option('--profile <name>', 'Profile name (auto-detected from username if not provided)')
|
|
111
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
108
112
|
.action(async (options) => {
|
|
109
113
|
try {
|
|
110
114
|
console.error('\nGitHub Setup\n');
|
|
@@ -133,10 +137,13 @@ export function registerGitHubCommands(program: Command): void {
|
|
|
133
137
|
console.error(`\nAuthenticated as: ${user.login}${user.email ? ` (${user.email})` : ''}`);
|
|
134
138
|
|
|
135
139
|
// Save credentials
|
|
136
|
-
await setProfile('github', profileName);
|
|
140
|
+
await setProfile('github', profileName, { readOnly: options.readOnly });
|
|
137
141
|
await setCredentials('github', profileName, credentials);
|
|
138
142
|
|
|
139
143
|
console.log(`\nProfile "${profileName}" configured!`);
|
|
144
|
+
if (options.readOnly) {
|
|
145
|
+
console.log(` Access: read-only`);
|
|
146
|
+
}
|
|
140
147
|
console.log(` Install secrets: agentio github install owner/repo --profile ${profileName}`);
|
|
141
148
|
} catch (error) {
|
|
142
149
|
handleError(error);
|
package/src/commands/gmail.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { GmailClient } from '../services/gmail/client';
|
|
|
11
11
|
import { printMessageList, printMessage, printSendResult, printArchived, printMarked, printAttachmentList, printAttachmentDownloaded, raw } from '../utils/output';
|
|
12
12
|
import { CliError, handleError } from '../utils/errors';
|
|
13
13
|
import { readStdin } from '../utils/stdin';
|
|
14
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
14
15
|
import type { GmailAttachment } from '../types/gmail';
|
|
15
16
|
|
|
16
17
|
function escapeHtml(text: string): string {
|
|
@@ -228,7 +229,8 @@ Query Syntax Examples:
|
|
|
228
229
|
? [...regularAttachments, ...inlineAttachments]
|
|
229
230
|
: undefined;
|
|
230
231
|
|
|
231
|
-
const { client } = await getGmailClient(options.profile);
|
|
232
|
+
const { client, profile } = await getGmailClient(options.profile);
|
|
233
|
+
await enforceWriteAccess('gmail', profile, 'send email');
|
|
232
234
|
const result = await client.send({
|
|
233
235
|
to: options.to,
|
|
234
236
|
cc: options.cc.length ? options.cc : undefined,
|
|
@@ -263,7 +265,8 @@ Query Syntax Examples:
|
|
|
263
265
|
throw new CliError('INVALID_PARAMS', 'Body is required. Use --body or pipe via stdin.');
|
|
264
266
|
}
|
|
265
267
|
|
|
266
|
-
const { client } = await getGmailClient(options.profile);
|
|
268
|
+
const { client, profile } = await getGmailClient(options.profile);
|
|
269
|
+
await enforceWriteAccess('gmail', profile, 'reply to email');
|
|
267
270
|
const result = await client.reply({
|
|
268
271
|
threadId: options.threadId,
|
|
269
272
|
body,
|
|
@@ -282,7 +285,8 @@ Query Syntax Examples:
|
|
|
282
285
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
283
286
|
.action(async (messageIds: string[], options) => {
|
|
284
287
|
try {
|
|
285
|
-
const { client } = await getGmailClient(options.profile);
|
|
288
|
+
const { client, profile } = await getGmailClient(options.profile);
|
|
289
|
+
await enforceWriteAccess('gmail', profile, 'archive email');
|
|
286
290
|
for (const messageId of messageIds) {
|
|
287
291
|
await client.archive(messageId);
|
|
288
292
|
printArchived(messageId);
|
|
@@ -308,7 +312,8 @@ Query Syntax Examples:
|
|
|
308
312
|
throw new CliError('INVALID_PARAMS', 'Cannot specify both --read and --unread');
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
const { client } = await getGmailClient(options.profile);
|
|
315
|
+
const { client, profile } = await getGmailClient(options.profile);
|
|
316
|
+
await enforceWriteAccess('gmail', profile, 'mark email');
|
|
312
317
|
for (const messageId of messageIds) {
|
|
313
318
|
await client.mark(messageId, options.read);
|
|
314
319
|
printMarked(messageId, options.read);
|
|
@@ -458,6 +463,7 @@ ${emailHeader}
|
|
|
458
463
|
.command('add')
|
|
459
464
|
.description('Add a new Gmail profile')
|
|
460
465
|
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
466
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
461
467
|
.action(async (options) => {
|
|
462
468
|
try {
|
|
463
469
|
console.error('Starting OAuth flow for Gmail...\n');
|
|
@@ -476,11 +482,14 @@ ${emailHeader}
|
|
|
476
482
|
|
|
477
483
|
const profileName = options.profile || email;
|
|
478
484
|
|
|
479
|
-
await setProfile('gmail', profileName);
|
|
485
|
+
await setProfile('gmail', profileName, { readOnly: options.readOnly });
|
|
480
486
|
await setCredentials('gmail', profileName, { ...tokens, email });
|
|
481
487
|
|
|
482
488
|
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
483
489
|
console.log(` Email: ${email}`);
|
|
490
|
+
if (options.readOnly) {
|
|
491
|
+
console.log(` Access: read-only`);
|
|
492
|
+
}
|
|
484
493
|
} catch (error) {
|
|
485
494
|
handleError(error);
|
|
486
495
|
}
|
package/src/commands/gsheets.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
printGSheetsCreated,
|
|
19
19
|
} from '../utils/output';
|
|
20
20
|
import { CliError, handleError } from '../utils/errors';
|
|
21
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
21
22
|
import type { GSheetsCredentials } from '../types/gsheets';
|
|
22
23
|
|
|
23
24
|
const getGSheetsClient = createClientGetter<GSheetsCredentials, GSheetsClient>({
|
|
@@ -152,7 +153,8 @@ Input Options:
|
|
|
152
153
|
.action(async (spreadsheetId: string, range: string, valueArgs: string[], options) => {
|
|
153
154
|
try {
|
|
154
155
|
const values = parseValues(valueArgs, options.valuesJson);
|
|
155
|
-
const { client } = await getGSheetsClient(options.profile);
|
|
156
|
+
const { client, profile } = await getGSheetsClient(options.profile);
|
|
157
|
+
await enforceWriteAccess('gsheets', profile, 'update values');
|
|
156
158
|
const result = await client.update(spreadsheetId, range, values, {
|
|
157
159
|
valueInputOption: options.input,
|
|
158
160
|
});
|
|
@@ -192,7 +194,8 @@ Insert Options:
|
|
|
192
194
|
.action(async (spreadsheetId: string, range: string, valueArgs: string[], options) => {
|
|
193
195
|
try {
|
|
194
196
|
const values = parseValues(valueArgs, options.valuesJson);
|
|
195
|
-
const { client } = await getGSheetsClient(options.profile);
|
|
197
|
+
const { client, profile } = await getGSheetsClient(options.profile);
|
|
198
|
+
await enforceWriteAccess('gsheets', profile, 'append values');
|
|
196
199
|
const result = await client.append(spreadsheetId, range, values, {
|
|
197
200
|
valueInputOption: options.input,
|
|
198
201
|
insertDataOption: options.insert,
|
|
@@ -211,7 +214,8 @@ Insert Options:
|
|
|
211
214
|
.option('--profile <name>', 'Profile name')
|
|
212
215
|
.action(async (spreadsheetId: string, range: string, options) => {
|
|
213
216
|
try {
|
|
214
|
-
const { client } = await getGSheetsClient(options.profile);
|
|
217
|
+
const { client, profile } = await getGSheetsClient(options.profile);
|
|
218
|
+
await enforceWriteAccess('gsheets', profile, 'clear values');
|
|
215
219
|
const result = await client.clear(spreadsheetId, range);
|
|
216
220
|
printGSheetsClearResult(result);
|
|
217
221
|
} catch (error) {
|
|
@@ -242,7 +246,8 @@ Insert Options:
|
|
|
242
246
|
.option('--sheets <names>', 'Comma-separated sheet names to create')
|
|
243
247
|
.action(async (title: string, options) => {
|
|
244
248
|
try {
|
|
245
|
-
const { client } = await getGSheetsClient(options.profile);
|
|
249
|
+
const { client, profile } = await getGSheetsClient(options.profile);
|
|
250
|
+
await enforceWriteAccess('gsheets', profile, 'create spreadsheet');
|
|
246
251
|
const sheetNames = options.sheets ? options.sheets.split(',').map((n: string) => n.trim()) : undefined;
|
|
247
252
|
const result = await client.create(title, sheetNames);
|
|
248
253
|
printGSheetsCreated(result);
|
|
@@ -260,7 +265,8 @@ Insert Options:
|
|
|
260
265
|
.option('--parent <folder-id>', 'Destination folder ID')
|
|
261
266
|
.action(async (spreadsheetId: string, title: string, options) => {
|
|
262
267
|
try {
|
|
263
|
-
const { client } = await getGSheetsClient(options.profile);
|
|
268
|
+
const { client, profile } = await getGSheetsClient(options.profile);
|
|
269
|
+
await enforceWriteAccess('gsheets', profile, 'copy spreadsheet');
|
|
264
270
|
const result = await client.copy(spreadsheetId, title, options.parent);
|
|
265
271
|
printGSheetsCreated(result);
|
|
266
272
|
} catch (error) {
|
|
@@ -320,6 +326,7 @@ Examples:
|
|
|
320
326
|
.command('add')
|
|
321
327
|
.description('Add a new Google Sheets profile')
|
|
322
328
|
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
329
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
323
330
|
.action(async (options) => {
|
|
324
331
|
try {
|
|
325
332
|
console.error('Starting OAuth flow for Google Sheets...\n');
|
|
@@ -352,11 +359,14 @@ Examples:
|
|
|
352
359
|
email: userEmail,
|
|
353
360
|
};
|
|
354
361
|
|
|
355
|
-
await setProfile('gsheets', profileName);
|
|
362
|
+
await setProfile('gsheets', profileName, { readOnly: options.readOnly });
|
|
356
363
|
await setCredentials('gsheets', profileName, credentials);
|
|
357
364
|
|
|
358
365
|
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
359
366
|
console.log(` Email: ${userEmail}`);
|
|
367
|
+
if (options.readOnly) {
|
|
368
|
+
console.log(` Access: read-only`);
|
|
369
|
+
}
|
|
360
370
|
console.log(` Test with: agentio gsheets list --profile ${profileName}`);
|
|
361
371
|
} catch (error) {
|
|
362
372
|
handleError(error);
|
package/src/commands/gtasks.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from '../utils/output';
|
|
20
20
|
import { CliError, handleError } from '../utils/errors';
|
|
21
21
|
import { readStdin } from '../utils/stdin';
|
|
22
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
22
23
|
|
|
23
24
|
async function getGTasksClient(profileName?: string): Promise<{ client: GTasksClient; profile: string }> {
|
|
24
25
|
const { tokens, profile } = await getValidTokens('gtasks', profileName);
|
|
@@ -60,7 +61,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
60
61
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
61
62
|
.action(async (title: string, options) => {
|
|
62
63
|
try {
|
|
63
|
-
const { client } = await getGTasksClient(options.profile);
|
|
64
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
65
|
+
await enforceWriteAccess('gtasks', profile, 'create task list');
|
|
64
66
|
const taskList = await client.createTaskList(title);
|
|
65
67
|
printGTaskListCreated(taskList);
|
|
66
68
|
} catch (error) {
|
|
@@ -75,7 +77,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
75
77
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
76
78
|
.action(async (tasklistId: string, options) => {
|
|
77
79
|
try {
|
|
78
|
-
const { client } = await getGTasksClient(options.profile);
|
|
80
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
81
|
+
await enforceWriteAccess('gtasks', profile, 'delete task list');
|
|
79
82
|
await client.deleteTaskList(tasklistId);
|
|
80
83
|
printGTaskListDeleted(tasklistId);
|
|
81
84
|
} catch (error) {
|
|
@@ -146,7 +149,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
146
149
|
if (stdin) notes = stdin;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
const { client } = await getGTasksClient(options.profile);
|
|
152
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
153
|
+
await enforceWriteAccess('gtasks', profile, 'create task');
|
|
150
154
|
const task = await client.createTask({
|
|
151
155
|
tasklistId,
|
|
152
156
|
title: options.title,
|
|
@@ -182,7 +186,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
182
186
|
throw new CliError('INVALID_PARAMS', `Invalid status: ${options.status}`, 'Use: needsAction or completed');
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
const { client } = await getGTasksClient(options.profile);
|
|
189
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
190
|
+
await enforceWriteAccess('gtasks', profile, 'update task');
|
|
186
191
|
const task = await client.updateTask({
|
|
187
192
|
tasklistId,
|
|
188
193
|
taskId,
|
|
@@ -205,7 +210,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
205
210
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
206
211
|
.action(async (tasklistId: string, taskId: string, options) => {
|
|
207
212
|
try {
|
|
208
|
-
const { client } = await getGTasksClient(options.profile);
|
|
213
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
214
|
+
await enforceWriteAccess('gtasks', profile, 'complete task');
|
|
209
215
|
const task = await client.completeTask(tasklistId, taskId);
|
|
210
216
|
console.log(`Task completed: ${task.title}`);
|
|
211
217
|
console.log(`ID: ${task.id}`);
|
|
@@ -223,7 +229,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
223
229
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
224
230
|
.action(async (tasklistId: string, taskId: string, options) => {
|
|
225
231
|
try {
|
|
226
|
-
const { client } = await getGTasksClient(options.profile);
|
|
232
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
233
|
+
await enforceWriteAccess('gtasks', profile, 'uncomplete task');
|
|
227
234
|
const task = await client.uncompleteTask(tasklistId, taskId);
|
|
228
235
|
console.log(`Task uncompleted: ${task.title}`);
|
|
229
236
|
console.log(`ID: ${task.id}`);
|
|
@@ -240,7 +247,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
240
247
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
241
248
|
.action(async (tasklistId: string, taskId: string, options) => {
|
|
242
249
|
try {
|
|
243
|
-
const { client } = await getGTasksClient(options.profile);
|
|
250
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
251
|
+
await enforceWriteAccess('gtasks', profile, 'delete task');
|
|
244
252
|
await client.deleteTask(tasklistId, taskId);
|
|
245
253
|
printGTaskDeleted(tasklistId, taskId);
|
|
246
254
|
} catch (error) {
|
|
@@ -255,7 +263,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
255
263
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
256
264
|
.action(async (tasklistId: string, options) => {
|
|
257
265
|
try {
|
|
258
|
-
const { client } = await getGTasksClient(options.profile);
|
|
266
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
267
|
+
await enforceWriteAccess('gtasks', profile, 'clear tasks');
|
|
259
268
|
await client.clearCompleted(tasklistId);
|
|
260
269
|
printGTasksCleared(tasklistId);
|
|
261
270
|
} catch (error) {
|
|
@@ -275,7 +284,8 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
275
284
|
if (!options.parent && !options.previous) {
|
|
276
285
|
throw new CliError('INVALID_PARAMS', 'At least one of --parent or --previous is required');
|
|
277
286
|
}
|
|
278
|
-
const { client } = await getGTasksClient(options.profile);
|
|
287
|
+
const { client, profile } = await getGTasksClient(options.profile);
|
|
288
|
+
await enforceWriteAccess('gtasks', profile, 'move task');
|
|
279
289
|
const task = await client.moveTask(tasklistId, taskId, options.parent, options.previous);
|
|
280
290
|
console.log(`Task moved: ${task.title}`);
|
|
281
291
|
console.log(`ID: ${task.id}`);
|
|
@@ -296,6 +306,7 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
296
306
|
.command('add')
|
|
297
307
|
.description('Add a new Google Tasks profile')
|
|
298
308
|
.option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
|
|
309
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
299
310
|
.action(async (options) => {
|
|
300
311
|
try {
|
|
301
312
|
console.error('Starting OAuth flow for Google Tasks...\n');
|
|
@@ -314,11 +325,14 @@ export function registerGTasksCommands(program: Command): void {
|
|
|
314
325
|
|
|
315
326
|
const profileName = options.profile || email;
|
|
316
327
|
|
|
317
|
-
await setProfile('gtasks', profileName);
|
|
328
|
+
await setProfile('gtasks', profileName, { readOnly: options.readOnly });
|
|
318
329
|
await setCredentials('gtasks', profileName, { ...tokens, email });
|
|
319
330
|
|
|
320
331
|
console.log(`\nSuccess! Profile "${profileName}" configured.`);
|
|
321
332
|
console.log(` Email: ${email}`);
|
|
333
|
+
if (options.readOnly) {
|
|
334
|
+
console.log(` Access: read-only`);
|
|
335
|
+
}
|
|
322
336
|
} catch (error) {
|
|
323
337
|
handleError(error);
|
|
324
338
|
}
|
package/src/commands/jira.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { JiraClient } from '../services/jira/client';
|
|
|
7
7
|
import { CliError, handleError } from '../utils/errors';
|
|
8
8
|
import { readStdin } from '../utils/stdin';
|
|
9
9
|
import { interactiveSelect } from '../utils/interactive';
|
|
10
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
10
11
|
import {
|
|
11
12
|
printJiraProjectList,
|
|
12
13
|
printJiraIssueList,
|
|
@@ -163,7 +164,8 @@ export function registerJiraCommands(program: Command): void {
|
|
|
163
164
|
throw new CliError('INVALID_PARAMS', 'Comment body is required. Provide as argument or pipe via stdin.');
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
const { client } = await getJiraClient(options.profile);
|
|
167
|
+
const { client, profile } = await getJiraClient(options.profile);
|
|
168
|
+
await enforceWriteAccess('jira', profile, 'add comment');
|
|
167
169
|
const result = await client.addComment(issueKey, text);
|
|
168
170
|
printJiraCommentResult(result);
|
|
169
171
|
} catch (error) {
|
|
@@ -196,7 +198,8 @@ export function registerJiraCommands(program: Command): void {
|
|
|
196
198
|
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
197
199
|
.action(async (issueKey: string, transitionId: string, options) => {
|
|
198
200
|
try {
|
|
199
|
-
const { client } = await getJiraClient(options.profile);
|
|
201
|
+
const { client, profile } = await getJiraClient(options.profile);
|
|
202
|
+
await enforceWriteAccess('jira', profile, 'transition issue');
|
|
200
203
|
const result = await client.transitionIssue(issueKey, transitionId);
|
|
201
204
|
printJiraTransitionResult(result);
|
|
202
205
|
} catch (error) {
|
|
@@ -215,6 +218,7 @@ export function registerJiraCommands(program: Command): void {
|
|
|
215
218
|
.command('add')
|
|
216
219
|
.description('Add a new JIRA profile with OAuth authentication')
|
|
217
220
|
.option('--profile <name>', 'Profile name (auto-detected from site URL if not provided)')
|
|
221
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
218
222
|
.action(async (options) => {
|
|
219
223
|
try {
|
|
220
224
|
console.error('\nJIRA OAuth Setup\n');
|
|
@@ -248,10 +252,13 @@ export function registerJiraCommands(program: Command): void {
|
|
|
248
252
|
siteUrl: result.siteUrl,
|
|
249
253
|
};
|
|
250
254
|
|
|
251
|
-
await setProfile('jira', profileName);
|
|
255
|
+
await setProfile('jira', profileName, { readOnly: options.readOnly });
|
|
252
256
|
await setCredentials('jira', profileName, credentials);
|
|
253
257
|
|
|
254
258
|
console.log(`\nProfile "${profileName}" configured!`);
|
|
259
|
+
if (options.readOnly) {
|
|
260
|
+
console.log(` Access: read-only`);
|
|
261
|
+
}
|
|
255
262
|
console.log(` Test with: agentio jira projects --profile ${profileName}`);
|
|
256
263
|
} catch (error) {
|
|
257
264
|
handleError(error);
|
package/src/commands/slack.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { SlackClient } from '../services/slack/client';
|
|
|
8
8
|
import { CliError, handleError } from '../utils/errors';
|
|
9
9
|
import { readStdin, prompt } from '../utils/stdin';
|
|
10
10
|
import { printSlackSendResult } from '../utils/output';
|
|
11
|
+
import { enforceWriteAccess } from '../utils/read-only';
|
|
11
12
|
import type { SlackCredentials, SlackWebhookCredentials } from '../types/slack';
|
|
12
13
|
|
|
13
14
|
const getSlackClient = createClientGetter<SlackCredentials, SlackClient>({
|
|
@@ -89,7 +90,8 @@ export function registerSlackCommands(program: Command): void {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
const { client } = await getSlackClient(options.profile);
|
|
93
|
+
const { client, profile } = await getSlackClient(options.profile);
|
|
94
|
+
await enforceWriteAccess('slack', profile, 'send message');
|
|
93
95
|
const result = await client.send({
|
|
94
96
|
text,
|
|
95
97
|
payload,
|
|
@@ -112,16 +114,17 @@ export function registerSlackCommands(program: Command): void {
|
|
|
112
114
|
.command('add')
|
|
113
115
|
.description('Add a new Slack profile (webhook)')
|
|
114
116
|
.requiredOption('--profile <name>', 'Profile name (required)')
|
|
117
|
+
.option('--read-only', 'Create as read-only profile (blocks write operations)')
|
|
115
118
|
.action(async (options) => {
|
|
116
119
|
try {
|
|
117
|
-
await setupWebhookProfile(options.profile);
|
|
120
|
+
await setupWebhookProfile(options.profile, options.readOnly);
|
|
118
121
|
} catch (error) {
|
|
119
122
|
handleError(error);
|
|
120
123
|
}
|
|
121
124
|
});
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
async function setupWebhookProfile(profileName: string): Promise<void> {
|
|
127
|
+
async function setupWebhookProfile(profileName: string, readOnly?: boolean): Promise<void> {
|
|
125
128
|
console.error('\nSlack Webhook Setup\n');
|
|
126
129
|
console.error('1. Go to https://api.slack.com/apps and create a new app (or use existing)');
|
|
127
130
|
console.error('2. Enable "Incoming Webhooks" in Features');
|
|
@@ -177,9 +180,12 @@ async function setupWebhookProfile(profileName: string): Promise<void> {
|
|
|
177
180
|
channelName: channelName || undefined,
|
|
178
181
|
};
|
|
179
182
|
|
|
180
|
-
await setProfile('slack', profileName);
|
|
183
|
+
await setProfile('slack', profileName, { readOnly });
|
|
181
184
|
await setCredentials('slack', profileName, credentials);
|
|
182
185
|
|
|
183
186
|
console.log(`\nSuccess! Webhook profile "${profileName}" configured.`);
|
|
187
|
+
if (readOnly) {
|
|
188
|
+
console.log(` Access: read-only`);
|
|
189
|
+
}
|
|
184
190
|
console.log(` Test with: agentio slack send --profile ${profileName} "Hello from agentio"`);
|
|
185
191
|
}
|