@juppytt/fws 0.1.0

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,211 @@
1
+ # fws CLI Reference
2
+
3
+ ## Server
4
+
5
+ ### `fws server start`
6
+
7
+ Start the mock server in the background.
8
+
9
+ | Flag | Description | Default |
10
+ |------|-------------|---------|
11
+ | `-p, --port <port>` | Port number | `4100` |
12
+ | `-s, --snapshot <name>` | Load a snapshot on start | — |
13
+ | `--foreground` | Run in foreground (for debugging) | — |
14
+
15
+ If a server is already running, it is automatically stopped and restarted.
16
+
17
+ ```bash
18
+ fws server start
19
+ fws server start -p 5000
20
+ fws server start --snapshot my-scenario
21
+ ```
22
+
23
+ ### `fws server stop`
24
+
25
+ Stop the running server.
26
+
27
+ ### `fws server status`
28
+
29
+ Show whether the server is running and on which port.
30
+
31
+ ---
32
+
33
+ ## Setup
34
+
35
+ Convenience commands to seed data into a running server. The server must be running first (`fws server start`).
36
+
37
+ ### `fws setup gmail add-message`
38
+
39
+ Add an email message to the mailbox.
40
+
41
+ | Flag | Required | Description | Default |
42
+ |------|----------|-------------|---------|
43
+ | `--from <email>` | **yes** | Sender address | — |
44
+ | `--to <email>` | no | Recipient address | `testuser@example.com` |
45
+ | `--subject <text>` | no | Subject line | `(no subject)` |
46
+ | `--body <text>` | no | Message body (plain text) | empty |
47
+ | `--labels <list>` | no | Comma-separated label IDs | `INBOX,UNREAD` |
48
+ | `-p, --port <port>` | no | Server port | `4100` |
49
+
50
+ ```bash
51
+ fws setup gmail add-message --from alice@company.com --subject "Meeting" --body "See you at 3pm"
52
+ fws setup gmail add-message --from bot@ci.com --subject "Build failed" --labels INBOX,UNREAD,IMPORTANT
53
+ fws setup gmail add-message --from me@example.com --to bob@example.com --subject "Reply" --labels SENT
54
+ ```
55
+
56
+ **HTTP API equivalent:**
57
+
58
+ ```bash
59
+ curl -X POST http://localhost:4100/__fws/setup/gmail/message \
60
+ -H 'Content-Type: application/json' \
61
+ -d '{"from":"alice@company.com","subject":"Meeting","body":"See you at 3pm","labels":["INBOX","UNREAD"]}'
62
+ ```
63
+
64
+ Request body fields:
65
+
66
+ | Field | Type | Description |
67
+ |-------|------|-------------|
68
+ | `from` | string | Sender address |
69
+ | `to` | string | Recipient address |
70
+ | `subject` | string | Subject line |
71
+ | `body` | string | Message body |
72
+ | `labels` | string[] | Label IDs |
73
+ | `date` | string | ISO 8601 date (defaults to now) |
74
+
75
+ Response: `{ "id": "...", "threadId": "..." }`
76
+
77
+ ---
78
+
79
+ ### `fws setup calendar add-event`
80
+
81
+ Add a calendar event.
82
+
83
+ | Flag | Required | Description | Default |
84
+ |------|----------|-------------|---------|
85
+ | `--summary <text>` | **yes** | Event title | — |
86
+ | `--start <datetime>` | **yes** | Start time (ISO 8601) | — |
87
+ | `--duration <dur>` | no | Duration: `30m`, `1h`, `2h`, `1d`, etc. | `1h` |
88
+ | `--calendar <id>` | no | Calendar ID | `primary` |
89
+ | `--location <text>` | no | Location | — |
90
+ | `--attendees <list>` | no | Comma-separated attendee emails | — |
91
+ | `-p, --port <port>` | no | Server port | `4100` |
92
+
93
+ End time is computed automatically from `start + duration`.
94
+
95
+ ```bash
96
+ fws setup calendar add-event --summary "Standup" --start 2026-04-08T09:00:00
97
+ fws setup calendar add-event --summary "Lunch" --start 2026-04-08T12:00:00 --duration 1h --location "Cafeteria"
98
+ fws setup calendar add-event --summary "Review" --start 2026-04-08T14:00:00 --duration 30m --attendees alice@co.com,bob@co.com
99
+ ```
100
+
101
+ **HTTP API equivalent:**
102
+
103
+ ```bash
104
+ curl -X POST http://localhost:4100/__fws/setup/calendar/event \
105
+ -H 'Content-Type: application/json' \
106
+ -d '{"summary":"Standup","start":"2026-04-08T09:00:00Z","duration":"30m"}'
107
+ ```
108
+
109
+ Request body fields:
110
+
111
+ | Field | Type | Description |
112
+ |-------|------|-------------|
113
+ | `summary` | string | Event title |
114
+ | `start` | string | ISO 8601 start time |
115
+ | `duration` | string | Duration (`30m`, `1h`, `2h`, `1d`) — default `1h` |
116
+ | `description` | string | Event description |
117
+ | `location` | string | Location |
118
+ | `calendar` | string | Calendar ID (omit for primary) |
119
+ | `attendees` | string[] | Attendee email addresses |
120
+
121
+ Response: `{ "id": "...", "calendarId": "..." }`
122
+
123
+ ---
124
+
125
+ ### `fws setup drive add-file`
126
+
127
+ Add a file (metadata only) to Drive.
128
+
129
+ | Flag | Required | Description | Default |
130
+ |------|----------|-------------|---------|
131
+ | `--name <text>` | **yes** | File name | — |
132
+ | `--mimeType <type>` | no | MIME type | `application/octet-stream` |
133
+ | `--parent <id>` | no | Parent folder ID | `root` |
134
+ | `--size <bytes>` | no | File size in bytes | — |
135
+ | `-p, --port <port>` | no | Server port | `4100` |
136
+
137
+ ```bash
138
+ fws setup drive add-file --name "report.pdf" --mimeType application/pdf
139
+ fws setup drive add-file --name "Project Docs" --mimeType application/vnd.google-apps.folder
140
+ fws setup drive add-file --name "notes.txt" --mimeType text/plain --parent folder001 --size 1024
141
+ ```
142
+
143
+ **HTTP API equivalent:**
144
+
145
+ ```bash
146
+ curl -X POST http://localhost:4100/__fws/setup/drive/file \
147
+ -H 'Content-Type: application/json' \
148
+ -d '{"name":"report.pdf","mimeType":"application/pdf"}'
149
+ ```
150
+
151
+ Request body fields:
152
+
153
+ | Field | Type | Description |
154
+ |-------|------|-------------|
155
+ | `name` | string | File name |
156
+ | `mimeType` | string | MIME type |
157
+ | `parent` | string | Parent folder ID |
158
+ | `size` | number | File size in bytes |
159
+ | `description` | string | File description |
160
+
161
+ Response: `{ "id": "..." }`
162
+
163
+ ---
164
+
165
+ ## Snapshots
166
+
167
+ All data is in-memory. Snapshots save/restore the full server state to disk.
168
+
169
+ Stored in `~/.local/share/fws/snapshots/<name>/store.json` (override with `FWS_DATA_DIR`).
170
+
171
+ ### `fws snapshot save <name>`
172
+
173
+ Save current server state.
174
+
175
+ ### `fws snapshot load <name>`
176
+
177
+ Replace current server state with a saved snapshot.
178
+
179
+ ### `fws snapshot list`
180
+
181
+ List all saved snapshots.
182
+
183
+ ### `fws snapshot delete <name>`
184
+
185
+ Delete a saved snapshot.
186
+
187
+ ---
188
+
189
+ ## Reset
190
+
191
+ ### `fws reset`
192
+
193
+ Reset server to default seed data.
194
+
195
+ ### `fws reset --snapshot <name>`
196
+
197
+ Reset server to a specific snapshot.
198
+
199
+ ---
200
+
201
+ ## Proxy mode
202
+
203
+ Any command that doesn't match a built-in subcommand is forwarded to `gws` through a temporary mock server.
204
+
205
+ ```bash
206
+ fws gmail users messages list --params '{"userId":"me"}'
207
+ fws drive files list
208
+ fws calendar events list --params '{"calendarId":"primary"}'
209
+ ```
210
+
211
+ This starts a server, runs the gws command, and exits. No `fws server start` needed.
@@ -0,0 +1,276 @@
1
+ # gws API Support Status
2
+
3
+ fws currently mocks **104 REST endpoints + 5 helpers** across 6 of 17 gws services.
4
+
5
+ All supported endpoints are validated through actual `gws` CLI commands in `test/gws-validation.test.ts` (89 tests).
6
+
7
+ ## Summary
8
+
9
+ | Service | Status | Implemented | Total | Notes |
10
+ |---------|--------|-------------|-------|-------|
11
+ | Gmail | Partial | 28 + 5 helpers | 79 | Messages (incl. batch/import), labels, threads (CRUD), profile, drafts, history, settings, +triage/+send/+reply/+forward |
12
+ | Calendar | Partial | 21 | 37 | Calendars (CRUD+clear), calendarList (CRUD), events (CRUD+import/move/quickAdd) |
13
+ | Drive | Partial | 18 | 57 | Files (CRUD+copy+emptyTrash), permissions (CRUD), drives (list/create), about |
14
+ | Tasks | Full | 14 | 14 | Task lists CRUD, tasks CRUD/move/clear |
15
+ | Sheets | Partial | 7 | 17 | Spreadsheets create/get/batchUpdate, values get/update/append/clear |
16
+ | People | Partial | 16 | 24 | Contacts CRUD/search/batch, contact groups CRUD, connections |
17
+ | Events | Not yet | 0 | 15 | |
18
+ | Docs | Not yet | — | — | |
19
+ | Slides | Not yet | — | — | |
20
+ | Chat | Not yet | — | — | |
21
+ | Classroom | Not yet | — | — | |
22
+ | Forms | Not yet | — | — | |
23
+ | Keep | Not yet | — | — | |
24
+ | Meet | Not yet | — | — | |
25
+ | Admin Reports | Not yet | — | — | |
26
+ | Model Armor | Not yet | — | — | |
27
+ | Workflow | Not yet | — | — | |
28
+
29
+ **Status legend:** ✅ Supported + gws-tested · ⚠️ Supported (HTTP only, not gws-tested) · — Not implemented
30
+
31
+ ---
32
+
33
+ ## Gmail (28/79 + 5 helpers)
34
+
35
+ ### Helpers
36
+
37
+ | gws command | Status | Notes |
38
+ |-------------|--------|-------|
39
+ | `gmail +triage` | ✅ gws-tested | Requires MITM proxy (HTTPS_PROXY + SSL_CERT_FILE) |
40
+ | `gmail +send` | ✅ gws-tested | Via MITM proxy |
41
+ | `gmail +reply` | ✅ gws-tested | Via MITM proxy |
42
+ | `gmail +reply-all` | ✅ gws-tested | Via MITM proxy |
43
+ | `gmail +forward` | ✅ gws-tested | Via MITM proxy |
44
+ | `gmail +watch` | — | Requires Pub/Sub (not mockable) |
45
+
46
+ ### Messages
47
+
48
+ | gws command | API method | Status |
49
+ |-------------|-----------|--------|
50
+ | `gmail users messages list` | gmail.users.messages.list | ✅ gws-tested |
51
+ | `gmail users messages get` | gmail.users.messages.get | ✅ gws-tested |
52
+ | `gmail users messages insert` | gmail.users.messages.insert | ✅ gws-tested |
53
+ | `gmail users messages send` | gmail.users.messages.send | ✅ gws-tested |
54
+ | `gmail users messages delete` | gmail.users.messages.delete | ✅ gws-tested |
55
+ | `gmail users messages trash` | gmail.users.messages.trash | ✅ gws-tested |
56
+ | `gmail users messages untrash` | gmail.users.messages.untrash | ✅ gws-tested |
57
+ | `gmail users messages modify` | gmail.users.messages.modify | ✅ gws-tested |
58
+ | `gmail users messages import` | gmail.users.messages.import | ✅ gws-tested |
59
+ | `gmail users messages batchDelete` | gmail.users.messages.batchDelete | ✅ gws-tested |
60
+ | `gmail users messages batchModify` | gmail.users.messages.batchModify | ✅ gws-tested |
61
+
62
+ ### Labels
63
+
64
+ | gws command | API method | Status |
65
+ |-------------|-----------|--------|
66
+ | `gmail users labels list` | gmail.users.labels.list | ✅ gws-tested |
67
+ | `gmail users labels get` | gmail.users.labels.get | ✅ gws-tested |
68
+ | `gmail users labels create` | gmail.users.labels.create | ✅ gws-tested |
69
+ | `gmail users labels patch` | gmail.users.labels.patch | ✅ gws-tested |
70
+ | `gmail users labels delete` | gmail.users.labels.delete | ✅ gws-tested |
71
+ | `gmail users labels update` | gmail.users.labels.update | ✅ gws-tested |
72
+
73
+ ### Threads
74
+
75
+ | gws command | API method | Status |
76
+ |-------------|-----------|--------|
77
+ | `gmail users threads list` | gmail.users.threads.list | ✅ gws-tested |
78
+ | `gmail users threads get` | gmail.users.threads.get | ✅ gws-tested |
79
+ | `gmail users threads delete` | gmail.users.threads.delete | ✅ gws-tested |
80
+ | `gmail users threads trash` | gmail.users.threads.trash | ✅ gws-tested |
81
+ | `gmail users threads untrash` | gmail.users.threads.untrash | ✅ gws-tested |
82
+ | `gmail users threads modify` | gmail.users.threads.modify | ✅ gws-tested |
83
+
84
+ ### Profile
85
+
86
+ | gws command | API method | Status |
87
+ |-------------|-----------|--------|
88
+ | `gmail users getProfile` | gmail.users.getProfile | ✅ gws-tested |
89
+ | `gmail users watch` | gmail.users.watch | — |
90
+ | `gmail users stop` | gmail.users.stop | — |
91
+
92
+ ### Settings
93
+
94
+ | gws command | API method | Status |
95
+ |-------------|-----------|--------|
96
+ | `gmail users settings sendAs list` | gmail.users.settings.sendAs.list | ✅ gws-tested |
97
+ | `gmail users settings sendAs get` | gmail.users.settings.sendAs.get | ✅ gws-tested |
98
+ | Other settings endpoints | | — (26 endpoints) |
99
+
100
+ ### Drafts
101
+
102
+ | gws command | API method | Status |
103
+ |-------------|-----------|--------|
104
+ | `gmail users drafts list` | gmail.users.drafts.list | ✅ gws-tested |
105
+ | `gmail users drafts get` | gmail.users.drafts.get | ✅ gws-tested |
106
+ | `gmail users drafts create` | gmail.users.drafts.create | ✅ gws-tested |
107
+ | `gmail users drafts update` | gmail.users.drafts.update | ✅ gws-tested |
108
+ | `gmail users drafts delete` | gmail.users.drafts.delete | ✅ gws-tested |
109
+ | `gmail users drafts send` | gmail.users.drafts.send | ✅ gws-tested |
110
+
111
+ ### History
112
+
113
+ | gws command | API method | Status |
114
+ |-------------|-----------|--------|
115
+ | `gmail users history list` | gmail.users.history.list | ✅ gws-tested |
116
+
117
+ ### Attachments
118
+
119
+ | gws command | API method | Status |
120
+ |-------------|-----------|--------|
121
+ | `gmail users messages attachments get` | gmail.users.messages.attachments.get | ✅ gws-tested |
122
+
123
+ ---
124
+
125
+ ## Calendar (21/37)
126
+
127
+ ### Calendar List
128
+
129
+ | gws command | API method | Status |
130
+ |-------------|-----------|--------|
131
+ | `calendar calendarList list` | calendar.calendarList.list | ✅ gws-tested |
132
+ | `calendar calendarList get` | calendar.calendarList.get | ✅ gws-tested |
133
+ | `calendar calendarList insert` | calendar.calendarList.insert | ✅ gws-tested |
134
+ | `calendar calendarList patch` | calendar.calendarList.patch | ✅ gws-tested |
135
+ | `calendar calendarList update` | calendar.calendarList.update | ✅ gws-tested |
136
+ | `calendar calendarList delete` | calendar.calendarList.delete | ✅ gws-tested |
137
+ | `calendar calendarList watch` | calendar.calendarList.watch | — |
138
+
139
+ ### Calendars
140
+
141
+ | gws command | API method | Status |
142
+ |-------------|-----------|--------|
143
+ | `calendar calendars insert` | calendar.calendars.insert | ✅ gws-tested |
144
+ | `calendar calendars get` | calendar.calendars.get | ✅ gws-tested |
145
+ | `calendar calendars patch` | calendar.calendars.patch | ✅ gws-tested |
146
+ | `calendar calendars delete` | calendar.calendars.delete | ✅ gws-tested |
147
+ | `calendar calendars update` | calendar.calendars.update | ✅ gws-tested |
148
+ | `calendar calendars clear` | calendar.calendars.clear | ✅ gws-tested |
149
+
150
+ ### Events
151
+
152
+ | gws command | API method | Status |
153
+ |-------------|-----------|--------|
154
+ | `calendar events list` | calendar.events.list | ✅ gws-tested |
155
+ | `calendar events get` | calendar.events.get | ✅ gws-tested |
156
+ | `calendar events insert` | calendar.events.insert | ✅ gws-tested |
157
+ | `calendar events patch` | calendar.events.patch | ✅ gws-tested |
158
+ | `calendar events update` | calendar.events.update | ✅ gws-tested |
159
+ | `calendar events delete` | calendar.events.delete | ✅ gws-tested |
160
+ | `calendar events import` | calendar.events.import | ✅ gws-tested |
161
+ | `calendar events instances` | calendar.events.instances | — |
162
+ | `calendar events move` | calendar.events.move | ✅ gws-tested |
163
+ | `calendar events quickAdd` | calendar.events.quickAdd | ✅ gws-tested |
164
+ | `calendar events watch` | calendar.events.watch | — |
165
+
166
+ ### Other (all unsupported)
167
+
168
+ ACL (7 endpoints), channels, colors, freebusy, settings — not implemented.
169
+
170
+ ---
171
+
172
+ ## Drive (18/57)
173
+
174
+ ### About
175
+
176
+ | gws command | API method | Status |
177
+ |-------------|-----------|--------|
178
+ | `drive about get` | drive.about.get | ✅ gws-tested |
179
+
180
+ ### Files
181
+
182
+ | gws command | API method | Status |
183
+ |-------------|-----------|--------|
184
+ | `drive files list` | drive.files.list | ✅ gws-tested |
185
+ | `drive files get` | drive.files.get | ✅ gws-tested |
186
+ | `drive files create` | drive.files.create | ✅ gws-tested |
187
+ | `drive files update` | drive.files.update | ✅ gws-tested |
188
+ | `drive files delete` | drive.files.delete | ✅ gws-tested |
189
+ | `drive files copy` | drive.files.copy | ✅ gws-tested |
190
+ | `drive files export` | drive.files.export | — |
191
+ | `drive files generateIds` | drive.files.generateIds | — |
192
+ | `drive files download` | drive.files.download | — |
193
+ | `drive files emptyTrash` | drive.files.emptyTrash | ✅ gws-tested |
194
+ | `drive files watch` | drive.files.watch | — |
195
+ | `drive files modifyLabels` | drive.files.modifyLabels | — |
196
+ | `drive files listLabels` | drive.files.listLabels | — |
197
+
198
+ ### Permissions
199
+
200
+ | gws command | API method | Status |
201
+ |-------------|-----------|--------|
202
+ | `drive permissions list` | drive.permissions.list | ✅ gws-tested |
203
+ | `drive permissions get` | drive.permissions.get | ✅ gws-tested |
204
+ | `drive permissions create` | drive.permissions.create | ✅ gws-tested |
205
+ | `drive permissions update` | drive.permissions.update | ✅ gws-tested |
206
+ | `drive permissions delete` | drive.permissions.delete | ✅ gws-tested |
207
+
208
+ ### Drives (Shared Drives)
209
+
210
+ | gws command | API method | Status |
211
+ |-------------|-----------|--------|
212
+ | `drive drives list` | drive.drives.list | ✅ gws-tested |
213
+ | `drive drives create` | drive.drives.create | ✅ gws-tested |
214
+ | `drive drives get` | drive.drives.get | ✅ gws-tested |
215
+ | `drive drives update` | drive.drives.update | ✅ gws-tested |
216
+ | `drive drives delete` | drive.drives.delete | ✅ gws-tested |
217
+ | `drive drives hide` | drive.drives.hide | — |
218
+ | `drive drives unhide` | drive.drives.unhide | — |
219
+
220
+ ### Other (all unsupported)
221
+
222
+ Comments (5), replies (5), revisions (4), changes (3), channels, apps, teamdrives, approvals, accessproposals, operations — not implemented.
223
+
224
+ ---
225
+
226
+ ## Tasks (14/14) — fully supported
227
+
228
+ All endpoints gws-tested: tasklists (list/get/insert/patch/update/delete), tasks (list/get/insert/patch/update/delete/move/clear).
229
+
230
+ ## Sheets (7/17)
231
+
232
+ | gws command | API method | Status |
233
+ |-------------|-----------|--------|
234
+ | `sheets spreadsheets create` | sheets.spreadsheets.create | ✅ gws-tested |
235
+ | `sheets spreadsheets get` | sheets.spreadsheets.get | ✅ gws-tested |
236
+ | `sheets spreadsheets batchUpdate` | sheets.spreadsheets.batchUpdate | ✅ gws-tested |
237
+ | `sheets spreadsheets values get` | sheets.spreadsheets.values.get | ✅ gws-tested |
238
+ | `sheets spreadsheets values update` | sheets.spreadsheets.values.update | ✅ gws-tested |
239
+ | `sheets spreadsheets values append` | sheets.spreadsheets.values.append | ✅ gws-tested |
240
+ | `sheets spreadsheets values clear` | sheets.spreadsheets.values.clear | ✅ gws-tested |
241
+ | `sheets spreadsheets values batchGet` | sheets.spreadsheets.values.batchGet | ✅ gws-tested |
242
+ | Other batch/filter operations | | — (9 endpoints) |
243
+
244
+ ## People (16/24)
245
+
246
+ | gws command | API method | Status |
247
+ |-------------|-----------|--------|
248
+ | `people people get` | people.people.get | ✅ gws-tested |
249
+ | `people people createContact` | people.people.createContact | ✅ gws-tested |
250
+ | `people people updateContact` | people.people.updateContact | ✅ gws-tested |
251
+ | `people people deleteContact` | people.people.deleteContact | ✅ gws-tested |
252
+ | `people people searchContacts` | people.people.searchContacts | ✅ gws-tested |
253
+ | `people people getBatchGet` | people.people.getBatchGet | ✅ gws-tested |
254
+ | `people people batchCreateContacts` | people.people.batchCreateContacts | ✅ gws-tested |
255
+ | `people people batchUpdateContacts` | people.people.batchUpdateContacts | ✅ gws-tested |
256
+ | `people people batchDeleteContacts` | people.people.batchDeleteContacts | ✅ gws-tested |
257
+ | `people people connections list` | people.people.connections.list | ✅ gws-tested |
258
+ | `people people listDirectoryPeople` | people.people.listDirectoryPeople | ✅ gws-tested |
259
+ | `people people searchDirectoryPeople` | people.people.searchDirectoryPeople | ✅ gws-tested |
260
+ | `people contactGroups list` | people.contactGroups.list | ✅ gws-tested |
261
+ | `people contactGroups get` | people.contactGroups.get | ✅ gws-tested |
262
+ | `people contactGroups create` | people.contactGroups.create | ✅ gws-tested |
263
+ | `people contactGroups delete` | people.contactGroups.delete | ✅ gws-tested |
264
+ | `people contactGroups update` | people.contactGroups.update | — |
265
+ | `people contactGroups batchGet` | people.contactGroups.batchGet | — |
266
+ | `people contactGroups members modify` | people.contactGroups.members.modify | ✅ gws-tested |
267
+ | `people otherContacts list` | people.otherContacts.list | ✅ gws-tested |
268
+ | Other (photo, copy) | | — |
269
+
270
+ ## Events (0/15) — not yet supported
271
+
272
+ Workspace event subscriptions, push notifications.
273
+
274
+ ## Other services — not yet supported
275
+
276
+ Docs, Slides, Chat, Classroom, Forms, Keep, Meet, Admin Reports, Model Armor, Workflow — no discovery cache present, not yet implemented.
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@juppytt/fws",
3
+ "version": "0.1.0",
4
+ "description": "Fake Google Workspace — local mock server for gws CLI testing",
5
+ "type": "module",
6
+ "bin": {
7
+ "fws": "./bin/fws-cli.sh"
8
+ },
9
+ "scripts": {
10
+ "start": "tsx bin/fws.ts server start",
11
+ "build": "tsc",
12
+ "dev": "tsx bin/fws.ts",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
15
+ },
16
+ "dependencies": {
17
+ "commander": "^13.0.0",
18
+ "express": "^5.1.0",
19
+ "nanoid": "^5.1.0",
20
+ "tsx": "^4.19.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/express": "^5.0.0",
24
+ "@types/node": "^22.0.0",
25
+ "typescript": "^5.8.0",
26
+ "vitest": "^3.1.0"
27
+ }
28
+ }
@@ -0,0 +1,73 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ const DISCOVERY_SOURCES: Array<{ file: string; api: string; version: string }> = [
6
+ { file: 'gmail_v1.json', api: 'gmail', version: 'v1' },
7
+ { file: 'calendar_v3.json', api: 'calendar', version: 'v3' },
8
+ { file: 'drive_v3.json', api: 'drive', version: 'v3' },
9
+ { file: 'tasks_v1.json', api: 'tasks', version: 'v1' },
10
+ { file: 'sheets_v4.json', api: 'sheets', version: 'v4' },
11
+ { file: 'people_v1.json', api: 'people', version: 'v1' },
12
+ ];
13
+
14
+ const DISCOVERY_URL = 'https://www.googleapis.com/discovery/v1/apis';
15
+
16
+ // Persistent local cache so we only download once
17
+ const localCacheDir = path.join(os.homedir(), '.local', 'share', 'fws', 'discovery-cache');
18
+
19
+ async function readOrDownload(file: string, api: string, version: string): Promise<string> {
20
+ // 1. Try local gws cache
21
+ const gwsCachePath = path.join(
22
+ process.env.GWS_SOURCE_CONFIG_DIR || path.join(os.homedir(), '.config', 'gws'),
23
+ 'cache',
24
+ file,
25
+ );
26
+ try {
27
+ return await fs.readFile(gwsCachePath, 'utf-8');
28
+ } catch {}
29
+
30
+ // 2. Try fws local cache
31
+ const fwsCachePath = path.join(localCacheDir, file);
32
+ try {
33
+ return await fs.readFile(fwsCachePath, 'utf-8');
34
+ } catch {}
35
+
36
+ // 3. Download from Google's public discovery API and save to fws cache
37
+ const url = `${DISCOVERY_URL}/${api}/${version}/rest`;
38
+ console.log(`Downloading discovery doc: ${api} ${version}...`);
39
+ const res = await fetch(url);
40
+ if (!res.ok) {
41
+ throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
42
+ }
43
+ const text = await res.text();
44
+
45
+ await fs.mkdir(localCacheDir, { recursive: true });
46
+ await fs.writeFile(fwsCachePath, text);
47
+
48
+ return text;
49
+ }
50
+
51
+ export async function generateConfigDir(port: number, targetDir: string): Promise<string> {
52
+ const cacheDir = path.join(targetDir, 'cache');
53
+ await fs.mkdir(cacheDir, { recursive: true });
54
+
55
+ const localUrl = `http://localhost:${port}/`;
56
+
57
+ for (const { file, api, version } of DISCOVERY_SOURCES) {
58
+ const raw = await readOrDownload(file, api, version);
59
+ const data = JSON.parse(raw);
60
+
61
+ data.rootUrl = localUrl;
62
+ if (data.baseUrl) {
63
+ data.baseUrl = localUrl + (data.servicePath || '');
64
+ }
65
+ if (data.mtlsRootUrl) {
66
+ data.mtlsRootUrl = localUrl;
67
+ }
68
+
69
+ await fs.writeFile(path.join(cacheDir, file), JSON.stringify(data));
70
+ }
71
+
72
+ return targetDir;
73
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { createApp } from './server/app.js';
2
+ export { getStore, resetStore, loadStore, serializeStore, deserializeStore } from './store/index.js';
3
+ export { generateConfigDir } from './config/rewrite-cache.js';