@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.
- package/.claude/settings.local.json +72 -0
- package/README.md +126 -0
- package/bin/fws-cli.sh +4 -0
- package/bin/fws.ts +421 -0
- package/docs/cli-reference.md +211 -0
- package/docs/gws-support.md +276 -0
- package/package.json +28 -0
- package/src/config/rewrite-cache.ts +73 -0
- package/src/index.ts +3 -0
- package/src/proxy/mitm.ts +285 -0
- package/src/server/app.ts +26 -0
- package/src/server/middleware.ts +38 -0
- package/src/server/routes/calendar.ts +483 -0
- package/src/server/routes/control.ts +151 -0
- package/src/server/routes/drive.ts +342 -0
- package/src/server/routes/gmail.ts +758 -0
- package/src/server/routes/people.ts +239 -0
- package/src/server/routes/sheets.ts +242 -0
- package/src/server/routes/tasks.ts +191 -0
- package/src/store/index.ts +24 -0
- package/src/store/seed.ts +313 -0
- package/src/store/types.ts +225 -0
- package/src/util/id.ts +9 -0
- package/test/calendar.test.ts +227 -0
- package/test/drive.test.ts +153 -0
- package/test/gmail.test.ts +215 -0
- package/test/gws-validation.test.ts +883 -0
- package/test/helpers/harness.ts +109 -0
- package/test/snapshot.test.ts +80 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +8 -0
|
@@ -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