gworkspace 0.2.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,63 @@
1
+ # Command Reference
2
+
3
+ ## Help
4
+
5
+ - `gw --help`
6
+ - `gw -h`
7
+ - `gw help`
8
+ - `gw help <domain>`
9
+ - `gw <domain> --help`
10
+
11
+ ## Auth
12
+
13
+ - `gw auth login [--credentials <path>] [--no-open]`
14
+ - `gw auth status`
15
+ - `gw auth logout`
16
+
17
+ ## Calendar
18
+
19
+ - `gw calendar list --from <ISO> --to <ISO> [--calendarId primary] [--max 20]`
20
+ - `gw calendar list today [--calendarId primary] [--max 20]`
21
+ - Alias: `gw calendar_getEvents --timeMin <ISO> --timeMax <ISO> [--calendarId primary] [--maxResults 20]`
22
+
23
+ ## Gmail
24
+
25
+ - `gw gmail search --query "<gmail_query>" [--max 20] [--pageToken <token>]`
26
+ - `gw gmail list today [--max 20]`
27
+ - `gw gmail get --id <message_id>`
28
+ - Alias: `gw gmail_search --query "<gmail_query>" [--max 20]`
29
+
30
+ ## Drive
31
+
32
+ - `gw drive search --query "<drive_query>" [--max 20]`
33
+ - `gw drive recent [--max 20]`
34
+ - `gw drive get --id <file_id>`
35
+ - Alias: `gw drive_search --query "<drive_query>" [--max 20]`
36
+
37
+ ## Chat
38
+
39
+ - `gw chat spaces [--max 20] [--filter "spaceType = SPACE"] [--pageToken <token>]`
40
+ - `gw chat messages --space spaces/<space_id> [--max 20] [--orderBy "createTime desc"] [--pageToken <token>]`
41
+ - `gw chat list today --space spaces/<space_id> [--max 20]`
42
+
43
+ ## Time
44
+
45
+ - `gw time now`
46
+ - `gw time date`
47
+ - `gw time zone`
48
+
49
+ ## Query examples
50
+
51
+ Gmail:
52
+
53
+ - `newer_than:7d`
54
+ - `from:alice@example.com subject:\"roadmap\"`
55
+
56
+ Drive:
57
+
58
+ - `trashed = false`
59
+ - `name contains 'Q1' and trashed = false`
60
+
61
+ Calendar:
62
+
63
+ - `--from 2026-02-18T00:00:00Z --to 2026-02-19T00:00:00Z`
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "gworkspace",
3
+ "version": "0.2.1",
4
+ "description": "Native Google Workspace CLI (no MCP)",
5
+ "type": "module",
6
+ "bin": {
7
+ "gw": "dist/index.js",
8
+ "gworkspace": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "dev": "bun run src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "check": "tsc --noEmit"
15
+ },
16
+ "dependencies": {
17
+ "googleapis": "^171.0.0",
18
+ "open": "^10.1.2"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^24.3.0",
22
+ "typescript": "^5.9.3"
23
+ }
24
+ }
@@ -0,0 +1,65 @@
1
+ import fs from 'node:fs';
2
+ import { google } from 'googleapis';
3
+ import type { OAuth2Client } from 'google-auth-library';
4
+ import { SCOPES, TOKEN_PATH, resolveCredentialsPath } from '../config.js';
5
+ import { fail, output } from '../lib/io.js';
6
+ import type { Options } from '../lib/types.js';
7
+ import { createOAuthClient, runBrowserOAuthFlow } from './oauth.js';
8
+ import { clearToken, loadSavedToken, saveToken } from './token.js';
9
+
10
+ export async function authorize(credentialsPath: string): Promise<OAuth2Client> {
11
+ const token = loadSavedToken();
12
+ if (token) {
13
+ const oauth = fs.existsSync(credentialsPath)
14
+ ? createOAuthClient(credentialsPath, 0)
15
+ : new google.auth.OAuth2();
16
+ oauth.setCredentials(token);
17
+ return oauth;
18
+ }
19
+
20
+ if (!fs.existsSync(credentialsPath)) {
21
+ fail('Credentials file not found.', { credentialsPath });
22
+ }
23
+
24
+ fail('No token found. Run `gw auth login` first.', {
25
+ tokenPath: TOKEN_PATH,
26
+ credentialsPath,
27
+ });
28
+ }
29
+
30
+ export function commandAuthStatus() {
31
+ const token = loadSavedToken();
32
+ output({
33
+ ok: true,
34
+ action: 'auth.status',
35
+ authenticated: !!token,
36
+ tokenPath: TOKEN_PATH,
37
+ scopes: SCOPES,
38
+ });
39
+ }
40
+
41
+ export async function commandAuthLogin(options: Options) {
42
+ const credentialsPath = resolveCredentialsPath(options);
43
+ if (!fs.existsSync(credentialsPath)) {
44
+ fail('Credentials file not found.', { credentialsPath });
45
+ }
46
+
47
+ const noOpen = options['no-open'] === true;
48
+ const result = await runBrowserOAuthFlow(credentialsPath, noOpen);
49
+ saveToken(result.credentials);
50
+
51
+ output({
52
+ ok: true,
53
+ action: 'auth.login',
54
+ credentialsPath,
55
+ tokenPath: TOKEN_PATH,
56
+ browserOpened: result.browserOpened,
57
+ callbackPort: result.callbackPort,
58
+ scopes: SCOPES,
59
+ });
60
+ }
61
+
62
+ export function commandAuthLogout() {
63
+ clearToken();
64
+ output({ ok: true, action: 'auth.logout', tokenPath: TOKEN_PATH });
65
+ }
@@ -0,0 +1,120 @@
1
+ import fs from 'node:fs';
2
+ import http from 'node:http';
3
+ import net from 'node:net';
4
+ import { URL } from 'node:url';
5
+ import { google } from 'googleapis';
6
+ import open from 'open';
7
+ import type { Credentials, OAuth2Client } from 'google-auth-library';
8
+ import { SCOPES } from '../config.js';
9
+ import { fail } from '../lib/io.js';
10
+
11
+ export function createOAuthClient(credentialsPath: string, port: number): OAuth2Client {
12
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'));
13
+ const payload = credentials.installed || credentials.web;
14
+ if (!payload?.client_id || !payload?.client_secret) {
15
+ fail('Invalid credentials file. Expected installed/web OAuth client JSON.');
16
+ }
17
+
18
+ const redirectUri = port > 0 ? `http://127.0.0.1:${port}/oauth2callback` : 'http://127.0.0.1';
19
+ return new google.auth.OAuth2(payload.client_id, payload.client_secret, redirectUri);
20
+ }
21
+
22
+ export function getAvailablePort(): Promise<number> {
23
+ return new Promise((resolve, reject) => {
24
+ const server = net.createServer();
25
+ server.listen(0, '127.0.0.1', () => {
26
+ const address = server.address();
27
+ if (!address || typeof address === 'string') {
28
+ server.close(() => reject(new Error('Could not allocate callback port.')));
29
+ return;
30
+ }
31
+ const { port } = address;
32
+ server.close((err) => {
33
+ if (err) reject(err);
34
+ else resolve(port);
35
+ });
36
+ });
37
+ server.on('error', reject);
38
+ });
39
+ }
40
+
41
+ export function waitForAuthCode(port: number, authUrl: string): Promise<string> {
42
+ return new Promise((resolve, reject) => {
43
+ const timeout = setTimeout(() => {
44
+ reject(
45
+ new Error(
46
+ `Authentication timed out after 5 minutes. Open this URL manually: ${authUrl}`,
47
+ ),
48
+ );
49
+ }, 5 * 60 * 1000);
50
+
51
+ const server = http.createServer((req, res) => {
52
+ try {
53
+ if (!req.url) throw new Error('Missing callback URL.');
54
+ const parsed = new URL(req.url, `http://127.0.0.1:${port}`);
55
+
56
+ if (parsed.pathname !== '/oauth2callback') {
57
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
58
+ res.end('Not found');
59
+ return;
60
+ }
61
+
62
+ const error = parsed.searchParams.get('error');
63
+ if (error) {
64
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
65
+ res.end(`OAuth error: ${error}`);
66
+ throw new Error(`OAuth error: ${error}`);
67
+ }
68
+
69
+ const code = parsed.searchParams.get('code');
70
+ if (!code) {
71
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
72
+ res.end('Missing code');
73
+ throw new Error('OAuth callback missing code parameter.');
74
+ }
75
+
76
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
77
+ res.end('Authentication successful. You can close this tab.');
78
+ clearTimeout(timeout);
79
+ server.close(() => resolve(code));
80
+ } catch (err) {
81
+ clearTimeout(timeout);
82
+ server.close(() => reject(err));
83
+ }
84
+ });
85
+
86
+ server.on('error', (err) => {
87
+ clearTimeout(timeout);
88
+ reject(err);
89
+ });
90
+
91
+ server.listen(port, '127.0.0.1');
92
+ });
93
+ }
94
+
95
+ export async function runBrowserOAuthFlow(
96
+ credentialsPath: string,
97
+ noOpen: boolean,
98
+ ): Promise<{ credentials: Credentials; callbackPort: number; browserOpened: boolean }> {
99
+ const callbackPort = await getAvailablePort();
100
+ const oauth = createOAuthClient(credentialsPath, callbackPort);
101
+ const authUrl = oauth.generateAuthUrl({
102
+ access_type: 'offline',
103
+ scope: SCOPES,
104
+ prompt: 'consent',
105
+ });
106
+
107
+ if (!noOpen) {
108
+ await open(authUrl);
109
+ }
110
+
111
+ const code = await waitForAuthCode(callbackPort, authUrl);
112
+ const tokenResponse = await oauth.getToken(code);
113
+ oauth.setCredentials(tokenResponse.tokens);
114
+
115
+ return {
116
+ credentials: oauth.credentials,
117
+ callbackPort,
118
+ browserOpened: !noOpen,
119
+ };
120
+ }
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs';
2
+ import type { Credentials } from 'google-auth-library';
3
+ import { ensureConfigDir, TOKEN_PATH } from '../config.js';
4
+
5
+ export type SavedToken = Credentials;
6
+
7
+ export function loadSavedToken(): SavedToken | null {
8
+ if (!fs.existsSync(TOKEN_PATH)) return null;
9
+ const raw = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8')) as Record<string, unknown>;
10
+ if (
11
+ raw &&
12
+ typeof raw === 'object' &&
13
+ 'credentials' in raw &&
14
+ raw.credentials &&
15
+ typeof raw.credentials === 'object'
16
+ ) {
17
+ return raw.credentials as Credentials;
18
+ }
19
+
20
+ return raw as Credentials;
21
+ }
22
+
23
+ export function saveToken(credentials: Credentials) {
24
+ ensureConfigDir();
25
+ fs.writeFileSync(TOKEN_PATH, `${JSON.stringify(credentials, null, 2)}\n`, 'utf8');
26
+ }
27
+
28
+ export function clearToken() {
29
+ if (fs.existsSync(TOKEN_PATH)) fs.unlinkSync(TOKEN_PATH);
30
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,114 @@
1
+ import {
2
+ authorize,
3
+ commandAuthLogin,
4
+ commandAuthLogout,
5
+ commandAuthStatus,
6
+ } from './auth/index.js';
7
+ import { resolveCredentialsPath } from './config.js';
8
+ import { commandCalendarList } from './commands/calendar.js';
9
+ import { commandChatMessages, commandChatSpaces } from './commands/chat.js';
10
+ import { commandDriveGet, commandDriveRecent, commandDriveSearch } from './commands/drive.js';
11
+ import { commandGmailGet, commandGmailSearch } from './commands/gmail.js';
12
+ import { commandTimeDate, commandTimeNow, commandTimeZone } from './commands/time.js';
13
+ import { optString, parseArgs } from './lib/args.js';
14
+ import { fail } from './lib/io.js';
15
+ import type { Options } from './lib/types.js';
16
+ import { usage } from './usage.js';
17
+
18
+ export async function runCli(argv: string[]) {
19
+ const { positional, options } = parseArgs(argv);
20
+ const [domain, command, subcommand] = positional;
21
+ const helpRequested = options.help === true || options.h === true;
22
+
23
+ if (!domain) usage();
24
+ if (domain === 'help') usage(command);
25
+ if (helpRequested) usage(domain);
26
+ if (!command && ['auth', 'calendar', 'gmail', 'drive', 'chat', 'time'].includes(domain)) {
27
+ usage(domain);
28
+ }
29
+
30
+ if (domain === 'auth' && command === 'status') commandAuthStatus();
31
+ if (domain === 'auth' && command === 'login') await commandAuthLogin(options);
32
+ if (domain === 'auth' && command === 'logout') commandAuthLogout();
33
+
34
+ if (domain === 'time' && command === 'now') commandTimeNow();
35
+ if (domain === 'time' && command === 'date') commandTimeDate();
36
+ if (domain === 'time' && command === 'zone') commandTimeZone();
37
+
38
+ const alias = domain;
39
+ if (alias === 'calendar_getEvents') {
40
+ const auth = await authorize(resolveCredentialsPath(options));
41
+ const patched: Options = {
42
+ ...options,
43
+ from: optString(options, 'timeMin') || optString(options, 'from') || true,
44
+ to: optString(options, 'timeMax') || optString(options, 'to') || true,
45
+ max: optString(options, 'maxResults') || optString(options, 'max') || true,
46
+ };
47
+ await commandCalendarList(auth, patched);
48
+ }
49
+
50
+ if (alias === 'gmail_search') {
51
+ const auth = await authorize(resolveCredentialsPath(options));
52
+ await commandGmailSearch(auth, options);
53
+ }
54
+
55
+ if (alias === 'drive_search') {
56
+ const auth = await authorize(resolveCredentialsPath(options));
57
+ await commandDriveSearch(auth, options);
58
+ }
59
+
60
+ if (domain === 'calendar' && command === 'list') {
61
+ const auth = await authorize(resolveCredentialsPath(options));
62
+ await commandCalendarList(auth, {
63
+ ...options,
64
+ today: subcommand === 'today' ? true : options.today,
65
+ });
66
+ }
67
+
68
+ if (domain === 'gmail' && command === 'search') {
69
+ const auth = await authorize(resolveCredentialsPath(options));
70
+ await commandGmailSearch(auth, options);
71
+ }
72
+
73
+ if (domain === 'gmail' && command === 'list' && subcommand === 'today') {
74
+ const auth = await authorize(resolveCredentialsPath(options));
75
+ await commandGmailSearch(auth, { ...options, today: true });
76
+ }
77
+
78
+ if (domain === 'gmail' && command === 'get') {
79
+ const auth = await authorize(resolveCredentialsPath(options));
80
+ await commandGmailGet(auth, options);
81
+ }
82
+
83
+ if (domain === 'drive' && command === 'search') {
84
+ const auth = await authorize(resolveCredentialsPath(options));
85
+ await commandDriveSearch(auth, options);
86
+ }
87
+
88
+ if (domain === 'drive' && command === 'recent') {
89
+ const auth = await authorize(resolveCredentialsPath(options));
90
+ await commandDriveRecent(auth, options);
91
+ }
92
+
93
+ if (domain === 'drive' && command === 'get') {
94
+ const auth = await authorize(resolveCredentialsPath(options));
95
+ await commandDriveGet(auth, options);
96
+ }
97
+
98
+ if (domain === 'chat' && command === 'spaces') {
99
+ const auth = await authorize(resolveCredentialsPath(options));
100
+ await commandChatSpaces(auth, options);
101
+ }
102
+
103
+ if (domain === 'chat' && command === 'messages') {
104
+ const auth = await authorize(resolveCredentialsPath(options));
105
+ await commandChatMessages(auth, options);
106
+ }
107
+
108
+ if (domain === 'chat' && command === 'list' && subcommand === 'today') {
109
+ const auth = await authorize(resolveCredentialsPath(options));
110
+ await commandChatMessages(auth, { ...options, today: true });
111
+ }
112
+
113
+ fail(`Unknown command: ${[domain, command, subcommand].filter(Boolean).join(' ')}`);
114
+ }
@@ -0,0 +1,52 @@
1
+ import { google } from 'googleapis';
2
+ import { optNumber, optString } from '../lib/args.js';
3
+ import { output } from '../lib/io.js';
4
+ import type { AnyRecord, Options } from '../lib/types.js';
5
+
6
+ function getTodayRangeIso() {
7
+ const start = new Date();
8
+ start.setHours(0, 0, 0, 0);
9
+ const end = new Date(start);
10
+ end.setDate(end.getDate() + 1);
11
+ return { start: start.toISOString(), end: end.toISOString() };
12
+ }
13
+
14
+ export async function commandCalendarList(auth: any, options: Options) {
15
+ const calendar = google.calendar({ version: 'v3', auth });
16
+ const fromOption = optString(options, 'from');
17
+ const toOption = optString(options, 'to');
18
+ const useToday = options.today === true;
19
+ const todayRange = getTodayRangeIso();
20
+ const now = new Date();
21
+ const from = fromOption || (useToday ? todayRange.start : now.toISOString());
22
+ const to = toOption || (useToday ? todayRange.end : new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString());
23
+ const max = Math.max(1, Math.min(optNumber(options, 'max', 20), 250));
24
+
25
+ const res = await calendar.events.list({
26
+ calendarId: optString(options, 'calendarId') || 'primary',
27
+ timeMin: from,
28
+ timeMax: to,
29
+ singleEvents: true,
30
+ orderBy: 'startTime',
31
+ maxResults: max,
32
+ });
33
+
34
+ const items = res.data.items || [];
35
+ output({
36
+ ok: true,
37
+ action: 'calendar.list',
38
+ today: useToday,
39
+ from,
40
+ to,
41
+ count: items.length,
42
+ events: items.map((item: AnyRecord) => ({
43
+ id: item.id,
44
+ status: item.status,
45
+ summary: item.summary,
46
+ description: item.description,
47
+ start: item.start,
48
+ end: item.end,
49
+ htmlLink: item.htmlLink,
50
+ })),
51
+ });
52
+ }
@@ -0,0 +1,89 @@
1
+ import { google } from 'googleapis';
2
+ import { optNumber, optString } from '../lib/args.js';
3
+ import { fail, output } from '../lib/io.js';
4
+ import type { AnyRecord, Options } from '../lib/types.js';
5
+
6
+ function getTodayStartLocal(): Date {
7
+ const start = new Date();
8
+ start.setHours(0, 0, 0, 0);
9
+ return start;
10
+ }
11
+
12
+ function isTodayLocal(isoValue: unknown): boolean {
13
+ if (typeof isoValue !== 'string') return false;
14
+ const date = new Date(isoValue);
15
+ if (Number.isNaN(date.getTime())) return false;
16
+ const start = getTodayStartLocal();
17
+ const end = new Date(start);
18
+ end.setDate(end.getDate() + 1);
19
+ return date >= start && date < end;
20
+ }
21
+
22
+ export async function commandChatSpaces(auth: any, options: Options) {
23
+ const chat = google.chat({ version: 'v1', auth });
24
+ const max = Math.max(1, Math.min(optNumber(options, 'max', 20), 1000));
25
+
26
+ const res = await chat.spaces.list({
27
+ pageSize: max,
28
+ pageToken: optString(options, 'pageToken'),
29
+ filter: optString(options, 'filter'),
30
+ });
31
+
32
+ const spaces = res.data.spaces || [];
33
+ output({
34
+ ok: true,
35
+ action: 'chat.spaces',
36
+ count: spaces.length,
37
+ nextPageToken: res.data.nextPageToken || null,
38
+ spaces: spaces.map((space: AnyRecord) => ({
39
+ name: space.name,
40
+ displayName: space.displayName,
41
+ spaceType: space.spaceType,
42
+ spaceThreadingState: space.spaceThreadingState,
43
+ createTime: space.createTime,
44
+ lastActiveTime: space.lastActiveTime,
45
+ membershipCount: space.membershipCount,
46
+ singleUserBotDm: space.singleUserBotDm,
47
+ externalUserAllowed: space.externalUserAllowed,
48
+ })),
49
+ });
50
+ }
51
+
52
+ export async function commandChatMessages(auth: any, options: Options) {
53
+ const parent = optString(options, 'space');
54
+ if (!parent) fail('Missing required --space for chat messages.');
55
+
56
+ const chat = google.chat({ version: 'v1', auth });
57
+ const max = Math.max(1, Math.min(optNumber(options, 'max', 20), 1000));
58
+
59
+ const useToday = options.today === true;
60
+ const res = await chat.spaces.messages.list({
61
+ parent,
62
+ pageSize: max,
63
+ pageToken: optString(options, 'pageToken'),
64
+ filter: optString(options, 'filter'),
65
+ orderBy: optString(options, 'orderBy') || (useToday ? 'createTime desc' : undefined),
66
+ });
67
+
68
+ const allMessages = res.data.messages || [];
69
+ const messages = useToday
70
+ ? allMessages.filter((message: AnyRecord) => isTodayLocal(message.createTime))
71
+ : allMessages;
72
+ output({
73
+ ok: true,
74
+ action: 'chat.messages',
75
+ today: useToday,
76
+ space: parent,
77
+ count: messages.length,
78
+ nextPageToken: res.data.nextPageToken || null,
79
+ messages: messages.map((message: AnyRecord) => ({
80
+ name: message.name,
81
+ createTime: message.createTime,
82
+ lastUpdateTime: message.lastUpdateTime,
83
+ sender: message.sender?.name || null,
84
+ thread: message.thread?.name || null,
85
+ text: message.text || '',
86
+ argumentText: message.argumentText || '',
87
+ })),
88
+ });
89
+ }
@@ -0,0 +1,61 @@
1
+ import { google } from 'googleapis';
2
+ import { optNumber, optString } from '../lib/args.js';
3
+ import { fail, output } from '../lib/io.js';
4
+ import type { Options } from '../lib/types.js';
5
+
6
+ export async function commandDriveSearch(auth: any, options: Options) {
7
+ const drive = google.drive({ version: 'v3', auth });
8
+ const max = Math.max(1, Math.min(optNumber(options, 'max', 20), 200));
9
+
10
+ const res = await drive.files.list({
11
+ q: optString(options, 'query') || 'trashed = false',
12
+ pageSize: max,
13
+ fields: 'files(id,name,mimeType,modifiedTime,owners,webViewLink),nextPageToken',
14
+ includeItemsFromAllDrives: true,
15
+ supportsAllDrives: true,
16
+ });
17
+
18
+ const files = res.data.files || [];
19
+ output({
20
+ ok: true,
21
+ action: 'drive.search',
22
+ count: files.length,
23
+ nextPageToken: res.data.nextPageToken || null,
24
+ files,
25
+ });
26
+ }
27
+
28
+ export async function commandDriveRecent(auth: any, options: Options) {
29
+ const drive = google.drive({ version: 'v3', auth });
30
+ const max = Math.max(1, Math.min(optNumber(options, 'max', 20), 200));
31
+
32
+ const res = await drive.files.list({
33
+ q: 'trashed = false',
34
+ orderBy: 'modifiedTime desc',
35
+ pageSize: max,
36
+ fields: 'files(id,name,mimeType,modifiedTime,owners,webViewLink),nextPageToken',
37
+ includeItemsFromAllDrives: true,
38
+ supportsAllDrives: true,
39
+ });
40
+
41
+ output({
42
+ ok: true,
43
+ action: 'drive.recent',
44
+ count: (res.data.files || []).length,
45
+ files: res.data.files || [],
46
+ });
47
+ }
48
+
49
+ export async function commandDriveGet(auth: any, options: Options) {
50
+ const id = optString(options, 'id');
51
+ if (!id) fail('Missing required --id for drive get.');
52
+
53
+ const drive = google.drive({ version: 'v3', auth });
54
+ const res = await drive.files.get({
55
+ fileId: id,
56
+ fields: 'id,name,mimeType,modifiedTime,owners,webViewLink,size',
57
+ supportsAllDrives: true,
58
+ });
59
+
60
+ output({ ok: true, action: 'drive.get', file: res.data });
61
+ }