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.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # gw
2
+
3
+ `gw` is a native Google Workspace CLI with direct Google API access.
4
+ It is built for direct execution: less ceremony, more signal.
5
+
6
+ ## What it does
7
+
8
+ - Browser-based OAuth login (localhost callback)
9
+ - Calendar read access (event listing)
10
+ - Gmail read access (search + metadata fetch)
11
+ - Drive read access (search + recent + file metadata)
12
+ - Google Chat read access (spaces + messages)
13
+ - Time utilities for reliable local/UTC context
14
+ - Compatibility aliases for Workspace-style command names
15
+
16
+ ## Quick start
17
+
18
+ 1. Create a Google Cloud **Desktop OAuth client**.
19
+ 2. Download the OAuth client JSON.
20
+ 3. Save it to `~/.config/gworkspace/credentials.json` (or pass `--credentials`).
21
+
22
+ ```bash
23
+ gw auth login
24
+ gw auth status
25
+ gw time now
26
+ ```
27
+
28
+ `auth login` opens your browser, waits for OAuth callback on localhost, then stores token at `~/.config/gworkspace/token.json`.
29
+
30
+ ## Install and run
31
+
32
+ ### Run from source
33
+
34
+ ```bash
35
+ bun install
36
+ bun run build
37
+ node dist/index.js --help
38
+ ```
39
+
40
+ ### Install globally (optional)
41
+
42
+ ```bash
43
+ bun install -g .
44
+ gw --help
45
+ ```
46
+
47
+ ### Help
48
+
49
+ ```bash
50
+ gw --help
51
+ gw -h
52
+ gw help
53
+ gw help auth
54
+ gw auth --help
55
+ ```
56
+
57
+ ## Common workflows
58
+
59
+ ### Morning context pull
60
+
61
+ ```bash
62
+ gw time now
63
+ gw calendar list --from 2026-02-18T00:00:00Z --to 2026-02-18T23:59:59Z
64
+ gw gmail search --query "newer_than:1d" --max 20
65
+ ```
66
+
67
+ ### Project document scan
68
+
69
+ ```bash
70
+ gw drive search --query "name contains 'spec' and trashed = false"
71
+ gw drive get --id <fileId>
72
+ ```
73
+
74
+ ### Message triage
75
+
76
+ ```bash
77
+ gw gmail search --query "is:unread newer_than:7d" --max 25
78
+ gw gmail get --id <messageId>
79
+ ```
80
+
81
+ ## Command surface
82
+
83
+ ### Auth
84
+
85
+ - `gw auth login [--credentials path/to/credentials.json] [--no-open]`
86
+ - `gw auth status`
87
+ - `gw auth logout`
88
+
89
+ ### Calendar
90
+
91
+ - `gw calendar list --from <ISO> --to <ISO> [--calendarId primary] [--max 20]`
92
+ - `gw calendar list today [--calendarId primary] [--max 20]`
93
+
94
+ ### Gmail
95
+
96
+ - `gw gmail search --query "<gmail query>" [--max 20] [--pageToken <token>]`
97
+ - `gw gmail list today [--max 20]`
98
+ - `gw gmail get --id <messageId>`
99
+
100
+ ### Drive
101
+
102
+ - `gw drive search --query "<drive query>" [--max 20]`
103
+ - `gw drive recent [--max 20]`
104
+ - `gw drive get --id <fileId>`
105
+
106
+ ### Time
107
+
108
+ - `gw time now`
109
+ - `gw time date`
110
+ - `gw time zone`
111
+
112
+ ### Chat
113
+
114
+ - `gw chat spaces [--max 20] [--filter "spaceType = SPACE"] [--pageToken <token>]`
115
+ - `gw chat messages --space spaces/<spaceId> [--max 20] [--orderBy "createTime desc"] [--pageToken <token>]`
116
+ - `gw chat list today --space spaces/<spaceId> [--max 20]`
117
+
118
+ ## Workspace-style aliases (compat)
119
+
120
+ ```bash
121
+ gw calendar_getEvents --timeMin 2026-02-18T00:00:00Z --timeMax 2026-02-19T00:00:00Z
122
+ gw gmail_search --query "newer_than:7d" --max 20
123
+ gw drive_search --query "trashed = false" --max 20
124
+ ```
125
+
126
+ ## Security notes
127
+
128
+ - OAuth tokens are stored locally, not committed by default.
129
+ - Uses Desktop OAuth credentials JSON for browser login.
130
+ - Scopes are read-only:
131
+ - `calendar.readonly`
132
+ - `gmail.readonly`
133
+ - `drive.readonly`
134
+ - `chat.spaces.readonly`
135
+ - `chat.messages.readonly`
136
+ - If you rotate credentials, run `gw auth logout` then `gw auth login`.
137
+
138
+ ## Troubleshooting
139
+
140
+ - `Credentials file not found`
141
+ - Create/download Desktop OAuth credentials and pass `--credentials /path/to/credentials.json`, or place it at `~/.config/gworkspace/credentials.json`.
142
+ - Browser did not open
143
+ - Run `gw auth login --no-open` and open the URL manually.
144
+ - Login timeout
145
+ - Re-run login and complete consent in the browser within the timeout window.
146
+ - `No token found`
147
+ - Run `gw auth login` first.
148
+
149
+ ## Docs
150
+
151
+ - `docs/auth.md` - OAuth browser flow, token storage, and troubleshooting.
152
+ - `docs/commands.md` - full command reference and examples.
153
+
154
+ ## Repo layout
155
+
156
+ ```text
157
+ src/
158
+ index.ts # tiny entrypoint
159
+ cli.ts # command routing
160
+ usage.ts # help output
161
+ config.ts # paths + scopes
162
+ auth/
163
+ index.ts # auth commands + authorize()
164
+ oauth.ts # browser OAuth flow
165
+ token.ts # token read/write/delete
166
+ commands/
167
+ calendar.ts
168
+ gmail.ts
169
+ drive.ts
170
+ chat.ts
171
+ time.ts
172
+ lib/
173
+ args.ts # argv parsing helpers
174
+ io.ts # JSON output + fail handling
175
+ types.ts # shared types
176
+ ```
177
+
178
+ ## Development
179
+
180
+ ```bash
181
+ bun run check
182
+ bun run build
183
+ node dist/index.js --help
184
+ ```
package/bun.lock ADDED
@@ -0,0 +1,220 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "gworkspace",
7
+ "dependencies": {
8
+ "googleapis": "^171.0.0",
9
+ "open": "^10.1.2",
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^24.3.0",
13
+ "typescript": "^5.9.3",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
19
+
20
+ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
21
+
22
+ "@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
23
+
24
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
25
+
26
+ "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
27
+
28
+ "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
29
+
30
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
31
+
32
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
33
+
34
+ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
35
+
36
+ "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
37
+
38
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
39
+
40
+ "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
41
+
42
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
43
+
44
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
45
+
46
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
47
+
48
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
49
+
50
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
51
+
52
+ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
53
+
54
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
55
+
56
+ "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
57
+
58
+ "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
59
+
60
+ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
61
+
62
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
63
+
64
+ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
65
+
66
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
67
+
68
+ "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
69
+
70
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
71
+
72
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
73
+
74
+ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
75
+
76
+ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
77
+
78
+ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
79
+
80
+ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
81
+
82
+ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
83
+
84
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
85
+
86
+ "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
87
+
88
+ "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
89
+
90
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
91
+
92
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
93
+
94
+ "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
95
+
96
+ "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
97
+
98
+ "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
99
+
100
+ "googleapis": ["googleapis@171.4.0", "", { "dependencies": { "google-auth-library": "^10.2.0", "googleapis-common": "^8.0.0" } }, "sha512-xybFL2SmmUgIifgsbsRQYRdNrSAYwxWZDmkZTGjUIaRnX5jPqR8el/cEvo6rCqh7iaZx6MfEPS/lrDgZ0bymkg=="],
101
+
102
+ "googleapis-common": ["googleapis-common@8.0.1", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" } }, "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A=="],
103
+
104
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
105
+
106
+ "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
107
+
108
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
109
+
110
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
111
+
112
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
113
+
114
+ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
115
+
116
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
117
+
118
+ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
119
+
120
+ "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
121
+
122
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
123
+
124
+ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
125
+
126
+ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
127
+
128
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
129
+
130
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
131
+
132
+ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
133
+
134
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
135
+
136
+ "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
137
+
138
+ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
139
+
140
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
141
+
142
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
143
+
144
+ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
145
+
146
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
147
+
148
+ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
149
+
150
+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
151
+
152
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
153
+
154
+ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
155
+
156
+ "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
157
+
158
+ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
159
+
160
+ "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
161
+
162
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
163
+
164
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
165
+
166
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
167
+
168
+ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
169
+
170
+ "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
171
+
172
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
173
+
174
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
175
+
176
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
177
+
178
+ "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
179
+
180
+ "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
181
+
182
+ "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
183
+
184
+ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
185
+
186
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
187
+
188
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
189
+
190
+ "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="],
191
+
192
+ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
193
+
194
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
195
+
196
+ "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
197
+
198
+ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
199
+
200
+ "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
201
+
202
+ "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
203
+
204
+ "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
205
+
206
+ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
207
+
208
+ "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
209
+
210
+ "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
211
+
212
+ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
213
+
214
+ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
215
+
216
+ "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
217
+
218
+ "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
219
+ }
220
+ }
@@ -0,0 +1,49 @@
1
+ import fs from 'node:fs';
2
+ import { SCOPES, TOKEN_PATH, resolveCredentialsPath } from '../config';
3
+ import { fail, output } from '../lib/io';
4
+ import { createOAuthClient, runBrowserOAuthFlow } from './oauth';
5
+ import { clearToken, loadSavedToken, saveToken } from './token';
6
+ export async function authorize(credentialsPath) {
7
+ const oauth = createOAuthClient(credentialsPath, 0);
8
+ const token = loadSavedToken();
9
+ if (token) {
10
+ oauth.setCredentials(token);
11
+ return oauth;
12
+ }
13
+ fail('No token found. Run `gworkspace auth login` first.', {
14
+ tokenPath: TOKEN_PATH,
15
+ credentialsPath,
16
+ });
17
+ }
18
+ export function commandAuthStatus() {
19
+ const token = loadSavedToken();
20
+ output({
21
+ ok: true,
22
+ action: 'auth.status',
23
+ authenticated: !!token,
24
+ tokenPath: TOKEN_PATH,
25
+ scopes: SCOPES,
26
+ });
27
+ }
28
+ export async function commandAuthLogin(options) {
29
+ const credentialsPath = resolveCredentialsPath(options);
30
+ if (!fs.existsSync(credentialsPath)) {
31
+ fail('Credentials file not found.', { credentialsPath });
32
+ }
33
+ const noOpen = options['no-open'] === true;
34
+ const result = await runBrowserOAuthFlow(credentialsPath, noOpen);
35
+ saveToken(result.credentials);
36
+ output({
37
+ ok: true,
38
+ action: 'auth.login',
39
+ credentialsPath,
40
+ tokenPath: TOKEN_PATH,
41
+ browserOpened: result.browserOpened,
42
+ callbackPort: result.callbackPort,
43
+ scopes: SCOPES,
44
+ });
45
+ }
46
+ export function commandAuthLogout() {
47
+ clearToken();
48
+ output({ ok: true, action: 'auth.logout', tokenPath: TOKEN_PATH });
49
+ }
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import { google } from 'googleapis';
3
+ import { SCOPES, TOKEN_PATH, resolveCredentialsPath } from '../config.js';
4
+ import { fail, output } from '../lib/io.js';
5
+ import { createOAuthClient, runBrowserOAuthFlow } from './oauth.js';
6
+ import { clearToken, loadSavedToken, saveToken } from './token.js';
7
+ export async function authorize(credentialsPath) {
8
+ const token = loadSavedToken();
9
+ if (token) {
10
+ const oauth = fs.existsSync(credentialsPath)
11
+ ? createOAuthClient(credentialsPath, 0)
12
+ : new google.auth.OAuth2();
13
+ oauth.setCredentials(token);
14
+ return oauth;
15
+ }
16
+ if (!fs.existsSync(credentialsPath)) {
17
+ fail('Credentials file not found.', { credentialsPath });
18
+ }
19
+ fail('No token found. Run `gw auth login` first.', {
20
+ tokenPath: TOKEN_PATH,
21
+ credentialsPath,
22
+ });
23
+ }
24
+ export function commandAuthStatus() {
25
+ const token = loadSavedToken();
26
+ output({
27
+ ok: true,
28
+ action: 'auth.status',
29
+ authenticated: !!token,
30
+ tokenPath: TOKEN_PATH,
31
+ scopes: SCOPES,
32
+ });
33
+ }
34
+ export async function commandAuthLogin(options) {
35
+ const credentialsPath = resolveCredentialsPath(options);
36
+ if (!fs.existsSync(credentialsPath)) {
37
+ fail('Credentials file not found.', { credentialsPath });
38
+ }
39
+ const noOpen = options['no-open'] === true;
40
+ const result = await runBrowserOAuthFlow(credentialsPath, noOpen);
41
+ saveToken(result.credentials);
42
+ output({
43
+ ok: true,
44
+ action: 'auth.login',
45
+ credentialsPath,
46
+ tokenPath: TOKEN_PATH,
47
+ browserOpened: result.browserOpened,
48
+ callbackPort: result.callbackPort,
49
+ scopes: SCOPES,
50
+ });
51
+ }
52
+ export function commandAuthLogout() {
53
+ clearToken();
54
+ output({ ok: true, action: 'auth.logout', tokenPath: TOKEN_PATH });
55
+ }
@@ -0,0 +1,101 @@
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 { SCOPES } from '../config.js';
8
+ import { fail } from '../lib/io.js';
9
+ export function createOAuthClient(credentialsPath, port) {
10
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'));
11
+ const payload = credentials.installed || credentials.web;
12
+ if (!payload?.client_id || !payload?.client_secret) {
13
+ fail('Invalid credentials file. Expected installed/web OAuth client JSON.');
14
+ }
15
+ const redirectUri = port > 0 ? `http://127.0.0.1:${port}/oauth2callback` : 'http://127.0.0.1';
16
+ return new google.auth.OAuth2(payload.client_id, payload.client_secret, redirectUri);
17
+ }
18
+ export function getAvailablePort() {
19
+ return new Promise((resolve, reject) => {
20
+ const server = net.createServer();
21
+ server.listen(0, '127.0.0.1', () => {
22
+ const address = server.address();
23
+ if (!address || typeof address === 'string') {
24
+ server.close(() => reject(new Error('Could not allocate callback port.')));
25
+ return;
26
+ }
27
+ const { port } = address;
28
+ server.close((err) => {
29
+ if (err)
30
+ reject(err);
31
+ else
32
+ resolve(port);
33
+ });
34
+ });
35
+ server.on('error', reject);
36
+ });
37
+ }
38
+ export function waitForAuthCode(port, authUrl) {
39
+ return new Promise((resolve, reject) => {
40
+ const timeout = setTimeout(() => {
41
+ reject(new Error(`Authentication timed out after 5 minutes. Open this URL manually: ${authUrl}`));
42
+ }, 5 * 60 * 1000);
43
+ const server = http.createServer((req, res) => {
44
+ try {
45
+ if (!req.url)
46
+ throw new Error('Missing callback URL.');
47
+ const parsed = new URL(req.url, `http://127.0.0.1:${port}`);
48
+ if (parsed.pathname !== '/oauth2callback') {
49
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
50
+ res.end('Not found');
51
+ return;
52
+ }
53
+ const error = parsed.searchParams.get('error');
54
+ if (error) {
55
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
56
+ res.end(`OAuth error: ${error}`);
57
+ throw new Error(`OAuth error: ${error}`);
58
+ }
59
+ const code = parsed.searchParams.get('code');
60
+ if (!code) {
61
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
62
+ res.end('Missing code');
63
+ throw new Error('OAuth callback missing code parameter.');
64
+ }
65
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
66
+ res.end('Authentication successful. You can close this tab.');
67
+ clearTimeout(timeout);
68
+ server.close(() => resolve(code));
69
+ }
70
+ catch (err) {
71
+ clearTimeout(timeout);
72
+ server.close(() => reject(err));
73
+ }
74
+ });
75
+ server.on('error', (err) => {
76
+ clearTimeout(timeout);
77
+ reject(err);
78
+ });
79
+ server.listen(port, '127.0.0.1');
80
+ });
81
+ }
82
+ export async function runBrowserOAuthFlow(credentialsPath, noOpen) {
83
+ const callbackPort = await getAvailablePort();
84
+ const oauth = createOAuthClient(credentialsPath, callbackPort);
85
+ const authUrl = oauth.generateAuthUrl({
86
+ access_type: 'offline',
87
+ scope: SCOPES,
88
+ prompt: 'consent',
89
+ });
90
+ if (!noOpen) {
91
+ await open(authUrl);
92
+ }
93
+ const code = await waitForAuthCode(callbackPort, authUrl);
94
+ const tokenResponse = await oauth.getToken(code);
95
+ oauth.setCredentials(tokenResponse.tokens);
96
+ return {
97
+ credentials: oauth.credentials,
98
+ callbackPort,
99
+ browserOpened: !noOpen,
100
+ };
101
+ }
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs';
2
+ import { ensureConfigDir, TOKEN_PATH } from '../config.js';
3
+ export function loadSavedToken() {
4
+ if (!fs.existsSync(TOKEN_PATH))
5
+ return null;
6
+ const raw = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
7
+ if (raw &&
8
+ typeof raw === 'object' &&
9
+ 'credentials' in raw &&
10
+ raw.credentials &&
11
+ typeof raw.credentials === 'object') {
12
+ return raw.credentials;
13
+ }
14
+ return raw;
15
+ }
16
+ export function saveToken(credentials) {
17
+ ensureConfigDir();
18
+ fs.writeFileSync(TOKEN_PATH, `${JSON.stringify(credentials, null, 2)}\n`, 'utf8');
19
+ }
20
+ export function clearToken() {
21
+ if (fs.existsSync(TOKEN_PATH))
22
+ fs.unlinkSync(TOKEN_PATH);
23
+ }