@plosson/agentio 0.3.2 → 0.4.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.
@@ -0,0 +1,28 @@
1
+ export interface GDocsCredentials {
2
+ accessToken: string;
3
+ refreshToken?: string;
4
+ expiryDate?: number;
5
+ tokenType: string;
6
+ scope?: string;
7
+ email: string;
8
+ }
9
+
10
+ export interface GDocsDocument {
11
+ id: string;
12
+ title: string;
13
+ owner?: string;
14
+ createdTime?: string;
15
+ modifiedTime?: string;
16
+ webViewLink: string;
17
+ }
18
+
19
+ export interface GDocsListOptions {
20
+ limit?: number;
21
+ query?: string;
22
+ }
23
+
24
+ export interface GDocsCreateResult {
25
+ id: string;
26
+ title: string;
27
+ webViewLink: string;
28
+ }
@@ -0,0 +1,81 @@
1
+ export type GDriveAccessLevel = 'readonly' | 'full';
2
+
3
+ export interface GDriveCredentials {
4
+ accessToken: string;
5
+ refreshToken?: string;
6
+ expiryDate?: number;
7
+ tokenType: string;
8
+ scope?: string;
9
+ email: string;
10
+ accessLevel: GDriveAccessLevel;
11
+ }
12
+
13
+ export interface GDriveFile {
14
+ id: string;
15
+ name: string;
16
+ mimeType: string;
17
+ size?: number;
18
+ createdTime?: string;
19
+ modifiedTime?: string;
20
+ owners?: string[];
21
+ parents?: string[];
22
+ webViewLink?: string;
23
+ webContentLink?: string;
24
+ starred: boolean;
25
+ trashed: boolean;
26
+ shared: boolean;
27
+ description?: string;
28
+ }
29
+
30
+ export interface GDriveListOptions {
31
+ limit?: number;
32
+ folderId?: string;
33
+ query?: string;
34
+ orderBy?: string;
35
+ includeTrash?: boolean;
36
+ }
37
+
38
+ export interface GDriveSearchOptions {
39
+ query: string;
40
+ mimeType?: string;
41
+ limit?: number;
42
+ folderId?: string;
43
+ }
44
+
45
+ export interface GDriveFolderListOptions {
46
+ limit?: number;
47
+ parentId?: string;
48
+ query?: string;
49
+ }
50
+
51
+ export interface GDriveDownloadOptions {
52
+ fileIdOrUrl: string;
53
+ outputPath: string;
54
+ exportFormat?: GDriveExportFormat;
55
+ }
56
+
57
+ export interface GDriveDownloadResult {
58
+ filename: string;
59
+ path: string;
60
+ size: number;
61
+ mimeType: string;
62
+ }
63
+
64
+ // Export formats for Google Workspace files
65
+ export type GDriveExportFormat = 'pdf' | 'docx' | 'xlsx' | 'csv' | 'pptx' | 'txt' | 'html' | 'odt' | 'ods' | 'odp' | 'rtf' | 'tsv' | 'png' | 'jpeg' | 'svg';
66
+
67
+ export interface GDriveUploadOptions {
68
+ filePath: string;
69
+ name?: string;
70
+ folderId?: string;
71
+ mimeType?: string;
72
+ convert?: boolean;
73
+ }
74
+
75
+ export interface GDriveUploadResult {
76
+ id: string;
77
+ name: string;
78
+ mimeType: string;
79
+ size: number;
80
+ webViewLink?: string;
81
+ }
@@ -1,5 +1,5 @@
1
1
  import { getCredentials } from '../auth/token-store';
2
- import { getProfile } from '../config/config-manager';
2
+ import { resolveProfile } from '../config/config-manager';
3
3
  import { CliError } from './errors';
4
4
  import type { ServiceName } from '../types/config';
5
5
 
@@ -10,6 +10,7 @@ export interface ClientFactoryConfig<TCredentials, TClient> {
10
10
 
11
11
  /**
12
12
  * Creates a type-safe client getter function for a service.
13
+ * Profile is optional - if only one profile exists, it will be used automatically.
13
14
  *
14
15
  * Usage:
15
16
  * ```typescript
@@ -21,18 +22,20 @@ export interface ClientFactoryConfig<TCredentials, TClient> {
21
22
  */
22
23
  export function createClientGetter<TCredentials, TClient>(
23
24
  config: ClientFactoryConfig<TCredentials, TClient>
24
- ): (profileName: string) => Promise<{ client: TClient; profile: string }> {
25
+ ): (profileName?: string) => Promise<{ client: TClient; profile: string }> {
25
26
  const { service, createClient } = config;
26
27
 
27
- return async (profileName: string): Promise<{ client: TClient; profile: string }> => {
28
- const profile = await getProfile(service, profileName);
28
+ return async (profileName?: string): Promise<{ client: TClient; profile: string }> => {
29
+ const { profile, error } = await resolveProfile(service, profileName);
29
30
 
30
31
  if (!profile) {
31
- throw new CliError(
32
- 'PROFILE_NOT_FOUND',
33
- `Profile "${profileName}" not found for ${service}`,
34
- `Run: agentio ${service} profile add`
35
- );
32
+ if (error === 'none') {
33
+ throw new CliError('PROFILE_NOT_FOUND', `No ${service} profile configured`, `Run: agentio ${service} profile add`);
34
+ }
35
+ if (error === 'multiple') {
36
+ throw new CliError('PROFILE_NOT_FOUND', `Multiple ${service} profiles exist`, 'Specify --profile <name>');
37
+ }
38
+ throw new CliError('PROFILE_NOT_FOUND', `Profile "${profileName}" not found for ${service}`, `Run: agentio ${service} profile add`);
36
39
  }
37
40
 
38
41
  const credentials = await getCredentials<TCredentials>(service, profile);
@@ -0,0 +1,145 @@
1
+ import {
2
+ select,
3
+ checkbox,
4
+ confirm as inquirerConfirm,
5
+ input,
6
+ } from '@inquirer/prompts';
7
+ import { CliError } from './errors';
8
+
9
+ export interface SelectChoice<T> {
10
+ name: string;
11
+ value: T;
12
+ description?: string;
13
+ }
14
+
15
+ export interface CheckboxChoice<T> {
16
+ name: string;
17
+ value: T;
18
+ checked?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Check if running in an interactive terminal.
23
+ */
24
+ export function isInteractive(): boolean {
25
+ return process.stdin.isTTY === true;
26
+ }
27
+
28
+ /**
29
+ * Interactive select prompt. Falls back to default or throws if not in TTY.
30
+ */
31
+ export async function interactiveSelect<T>(options: {
32
+ message: string;
33
+ choices: SelectChoice<T>[];
34
+ default?: T;
35
+ }): Promise<T> {
36
+ if (!isInteractive()) {
37
+ if (options.default !== undefined) {
38
+ return options.default;
39
+ }
40
+ throw new CliError(
41
+ 'INVALID_PARAMS',
42
+ 'Interactive input required but not running in terminal',
43
+ 'Run this command in an interactive terminal'
44
+ );
45
+ }
46
+
47
+ return select({
48
+ message: options.message,
49
+ choices: options.choices,
50
+ default: options.default,
51
+ loop: false,
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Interactive checkbox (multi-select) prompt. Falls back to default or throws if not in TTY.
57
+ */
58
+ export async function interactiveCheckbox<T>(options: {
59
+ message: string;
60
+ choices: CheckboxChoice<T>[];
61
+ required?: boolean;
62
+ }): Promise<T[]> {
63
+ if (!isInteractive()) {
64
+ // Return all checked items as default
65
+ const defaults = options.choices.filter((c) => c.checked).map((c) => c.value);
66
+ if (defaults.length > 0 || !options.required) {
67
+ return defaults;
68
+ }
69
+ throw new CliError(
70
+ 'INVALID_PARAMS',
71
+ 'Interactive input required but not running in terminal',
72
+ 'Run this command in an interactive terminal'
73
+ );
74
+ }
75
+
76
+ const result = await checkbox({
77
+ message: options.message,
78
+ choices: options.choices,
79
+ loop: false,
80
+ });
81
+
82
+ if (options.required && result.length === 0) {
83
+ throw new CliError('INVALID_PARAMS', 'At least one option must be selected');
84
+ }
85
+
86
+ return result;
87
+ }
88
+
89
+ /**
90
+ * Interactive confirm prompt. Falls back to default or throws if not in TTY.
91
+ */
92
+ export async function interactiveConfirm(options: {
93
+ message: string;
94
+ default?: boolean;
95
+ }): Promise<boolean> {
96
+ if (!isInteractive()) {
97
+ if (options.default !== undefined) {
98
+ return options.default;
99
+ }
100
+ throw new CliError(
101
+ 'INVALID_PARAMS',
102
+ 'Interactive input required but not running in terminal',
103
+ 'Run this command in an interactive terminal'
104
+ );
105
+ }
106
+
107
+ return inquirerConfirm({
108
+ message: options.message,
109
+ default: options.default,
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Interactive text input prompt. Falls back to default or throws if not in TTY.
115
+ */
116
+ export async function interactiveInput(options: {
117
+ message: string;
118
+ default?: string;
119
+ required?: boolean;
120
+ }): Promise<string> {
121
+ if (!isInteractive()) {
122
+ if (options.default !== undefined) {
123
+ return options.default;
124
+ }
125
+ if (!options.required) {
126
+ return '';
127
+ }
128
+ throw new CliError(
129
+ 'INVALID_PARAMS',
130
+ 'Interactive input required but not running in terminal',
131
+ 'Run this command in an interactive terminal'
132
+ );
133
+ }
134
+
135
+ const result = await input({
136
+ message: options.message,
137
+ default: options.default,
138
+ });
139
+
140
+ if (options.required && !result.trim()) {
141
+ throw new CliError('INVALID_PARAMS', 'Input is required');
142
+ }
143
+
144
+ return result;
145
+ }
@@ -1,5 +1,7 @@
1
1
  import type { GmailMessage, GmailAttachmentInfo } from '../types/gmail';
2
2
  import type { GChatMessage, GChatSpace } from '../types/gchat';
3
+ import type { GDocsDocument, GDocsCreateResult } from '../types/gdocs';
4
+ import type { GDriveFile, GDriveDownloadResult, GDriveUploadResult } from '../types/gdrive';
3
5
  import type { JiraProject, JiraIssue, JiraTransition, JiraCommentResult, JiraTransitionResult } from '../types/jira';
4
6
  import type { SlackSendResult } from '../types/slack';
5
7
  import type { RssFeed, RssArticle } from '../types/rss';
@@ -373,3 +375,110 @@ export function printDiscourseTopic(topic: DiscourseTopicDetail): void {
373
375
  console.log(content);
374
376
  }
375
377
  }
378
+
379
+ // Google Docs specific formatters
380
+ export function printGDocsList(docs: GDocsDocument[]): void {
381
+ if (docs.length === 0) {
382
+ console.log('No documents found');
383
+ return;
384
+ }
385
+
386
+ console.log(`Documents (${docs.length})\n`);
387
+
388
+ for (let i = 0; i < docs.length; i++) {
389
+ const doc = docs[i];
390
+ console.log(`[${i + 1}] ${doc.title}`);
391
+ console.log(` ID: ${doc.id}`);
392
+ if (doc.owner) console.log(` Owner: ${doc.owner}`);
393
+ if (doc.modifiedTime) console.log(` Modified: ${doc.modifiedTime}`);
394
+ console.log(` Link: ${doc.webViewLink}`);
395
+ console.log('');
396
+ }
397
+ }
398
+
399
+ export function printGDocCreated(result: GDocsCreateResult): void {
400
+ console.log('Document created');
401
+ console.log(`ID: ${result.id}`);
402
+ console.log(`Title: ${result.title}`);
403
+ console.log(`Link: ${result.webViewLink}`);
404
+ }
405
+
406
+ // Google Drive specific formatters
407
+ function getShortMimeType(mimeType: string): string {
408
+ const shortTypes: Record<string, string> = {
409
+ 'application/vnd.google-apps.folder': 'folder',
410
+ 'application/vnd.google-apps.document': 'gdoc',
411
+ 'application/vnd.google-apps.spreadsheet': 'gsheet',
412
+ 'application/vnd.google-apps.presentation': 'gslide',
413
+ 'application/pdf': 'pdf',
414
+ 'image/png': 'png',
415
+ 'image/jpeg': 'jpg',
416
+ 'text/plain': 'txt',
417
+ 'application/zip': 'zip',
418
+ };
419
+ if (shortTypes[mimeType]) return shortTypes[mimeType];
420
+ if (mimeType.startsWith('image/')) return 'image';
421
+ if (mimeType.startsWith('video/')) return 'video';
422
+ if (mimeType.startsWith('audio/')) return 'audio';
423
+ if (mimeType.startsWith('text/')) return 'text';
424
+ return 'file';
425
+ }
426
+
427
+ export function printGDriveFileList(files: GDriveFile[], title: string = 'Files'): void {
428
+ if (files.length === 0) {
429
+ console.log('No files found');
430
+ return;
431
+ }
432
+
433
+ console.log(`${title} (${files.length})\n`);
434
+
435
+ for (let i = 0; i < files.length; i++) {
436
+ const file = files[i];
437
+ const isFolder = file.mimeType === 'application/vnd.google-apps.folder';
438
+ const typeIndicator = isFolder ? '[folder]' : `[${getShortMimeType(file.mimeType)}]`;
439
+ const flags: string[] = [];
440
+ if (file.starred) flags.push('*');
441
+ if (file.shared) flags.push('shared');
442
+ const flagStr = flags.length > 0 ? ` (${flags.join(', ')})` : '';
443
+
444
+ console.log(`[${i + 1}] ${file.name} ${typeIndicator}${flagStr}`);
445
+ console.log(` ID: ${file.id}`);
446
+ if (file.size) console.log(` Size: ${formatBytes(file.size)}`);
447
+ if (file.owners?.length) console.log(` Owner: ${file.owners[0]}`);
448
+ if (file.modifiedTime) console.log(` Modified: ${file.modifiedTime}`);
449
+ if (file.webViewLink) console.log(` Link: ${file.webViewLink}`);
450
+ console.log('');
451
+ }
452
+ }
453
+
454
+ export function printGDriveFile(file: GDriveFile): void {
455
+ console.log(`ID: ${file.id}`);
456
+ console.log(`Name: ${file.name}`);
457
+ console.log(`Type: ${file.mimeType}`);
458
+ if (file.size) console.log(`Size: ${formatBytes(file.size)}`);
459
+ if (file.description) console.log(`Description: ${file.description}`);
460
+ if (file.owners?.length) console.log(`Owners: ${file.owners.join(', ')}`);
461
+ if (file.parents?.length) console.log(`Parents: ${file.parents.join(', ')}`);
462
+ console.log(`Starred: ${file.starred ? 'yes' : 'no'}`);
463
+ console.log(`Shared: ${file.shared ? 'yes' : 'no'}`);
464
+ console.log(`Trashed: ${file.trashed ? 'yes' : 'no'}`);
465
+ if (file.createdTime) console.log(`Created: ${file.createdTime}`);
466
+ if (file.modifiedTime) console.log(`Modified: ${file.modifiedTime}`);
467
+ if (file.webViewLink) console.log(`View: ${file.webViewLink}`);
468
+ if (file.webContentLink) console.log(`Download: ${file.webContentLink}`);
469
+ }
470
+
471
+ export function printGDriveDownloaded(result: GDriveDownloadResult): void {
472
+ console.log(`Downloaded: ${result.filename}`);
473
+ console.log(` Path: ${result.path}`);
474
+ console.log(` Size: ${formatBytes(result.size)}`);
475
+ console.log(` Type: ${result.mimeType}`);
476
+ }
477
+
478
+ export function printGDriveUploaded(result: GDriveUploadResult): void {
479
+ console.log(`Uploaded: ${result.name}`);
480
+ console.log(` ID: ${result.id}`);
481
+ console.log(` Size: ${formatBytes(result.size)}`);
482
+ console.log(` Type: ${result.mimeType}`);
483
+ if (result.webViewLink) console.log(` Link: ${result.webViewLink}`);
484
+ }