@joe-broadhead/opencode-gws 0.0.4 → 0.0.6

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.
@@ -1,263 +1,92 @@
1
1
  ---
2
2
  name: opencode-gws-recipes
3
- description: Use when building a Gmail, Drive, Calendar, Docs, or Sheets request for gws_read or gws_plan.
3
+ description: Load before the first gws_read or gws_plan for an everyday Google Workspace task; it has the ready request: unread or recent mail and who sent it, mail in a time window, replies and drafts, the next meetings or a week's schedule, finding, summarising, sharing or trashing Drive files and Google Docs, reading or appending Sheets rows, and the standup, meeting-prep and weekly-digest reports. Prefer it to any gws-* CLI skill.
4
4
  ---
5
5
 
6
6
  # Recipes
7
7
 
8
- These are exact requests for the everyday Gmail, Drive, Calendar, Docs, and Sheets tasks. Copy the shape. Replace the placeholder ids and text. Confirm the parameters with `gws_schema` when the task is not one of these.
9
-
10
- Pass `account` only when `gws_status` shows more than one account. `fields` is required on list and search. A change is `gws_plan`, then the commit tool of the **returned** category, with only `{ "planId": "<planId>" }`. Show the preview first.
11
-
12
- If `gws_catalog` says `executable` is false, do not call that id. Use `alternative`.
8
+ Copy the shape and replace the ids. `opencode-gws` covers accounts, plan then commit, and errors. Each list example asks only for what the task needs; add a field when you need one.
13
9
 
14
10
  ## Gmail
15
11
 
16
- Unread inbox, read:
17
-
18
- ```json
19
- {
20
- "operation": "gmail.users.messages.list",
21
- "params": { "userId": "me", "q": "is:unread", "maxResults": 10 },
22
- "fields": "messages(id,threadId),nextPageToken"
23
- }
24
- ```
25
-
26
- Mail between two times, read. `after:` and `before:` take Unix seconds; `newer_than:` and `older_than:` take only days, months, or years:
27
-
28
- ```json
29
- {
30
- "operation": "gmail.users.messages.list",
31
- "params": { "userId": "me", "q": "after:1767225600 before:1767312000", "maxResults": 100 },
32
- "fields": "messages(id,threadId),nextPageToken"
33
- }
34
- ```
35
-
36
- Sender, subject, and date of one message, read, without the body:
37
-
38
- ```json
39
- {
40
- "operation": "gmail.users.messages.get",
41
- "params": { "userId": "me", "id": "MESSAGE_ID", "format": "metadata", "metadataHeaders": ["From", "Subject"] },
42
- "fields": "id,threadId,snippet,internalDate,payload/headers"
43
- }
44
- ```
45
-
46
- One message, read. Prefer the helper when you want the text, not the raw payload:
12
+ - **A time window.** `after:` and `before:` take Unix seconds, so compute them for the user's zone. 09:00 to 12:00 on 2026-11-03 at UTC+1 is `after:1793692800 before:1793703600`. `after:2026/11/03` means a whole day, and `newer_than:` takes only `d`, `m` or `y`.
13
+ - **Who sent it.** Get each message with `format: "metadata"`, which skips the body. For the text, use `gmail.read` with `{"id": "...", "format": "text"}`. For a whole conversation, use `gmail.users.threads.get` with `format: "metadata"`.
14
+ - **Exact unread count.** Read `gmail.users.labels.get`, not a list's `resultSizeEstimate`.
47
15
 
48
16
  ```json
49
- {
50
- "operation": "gmail.read",
51
- "input": { "id": "MESSAGE_ID", "format": "text" }
52
- }
17
+ { "operation": "gmail.users.messages.list", "params": { "userId": "me", "q": "after:1793692800 before:1793703600", "maxResults": 100 }, "fields": "messages(id),nextPageToken" }
53
18
  ```
54
19
 
55
- Triage, read:
56
-
57
20
  ```json
58
- {
59
- "operation": "gmail.triage",
60
- "input": { "max": 10, "query": "is:unread" }
61
- }
21
+ { "operation": "gmail.users.messages.get", "params": { "userId": "me", "id": "MESSAGE_ID", "format": "metadata", "metadataHeaders": ["From", "Subject", "Date"] }, "fields": "id,payload/headers" }
62
22
  ```
63
23
 
64
- Send. Plan, show the preview, then `gws_send`. Send does not offer Always.
65
-
66
- ```json
67
- {
68
- "operation": "gmail.send",
69
- "input": {
70
- "to": "person@example.com",
71
- "subject": "Hello",
72
- "body": "Hello"
73
- }
74
- }
75
- ```
76
-
77
- Reply. Same commit tool, `gws_send`:
78
-
79
- ```json
80
- {
81
- "operation": "gmail.reply",
82
- "input": { "messageId": "MESSAGE_ID", "body": "Thanks" }
83
- }
84
- ```
85
-
86
- The preview lists who the reply reaches: the sender, and for `gmail.reply-all` everyone else on the message. `to`, `cc` and `bcc` add recipients; `remove` leaves people out of a reply-all. `gmail.forward` takes `to`, `cc`, `bcc`, `body`, `attachments` and `noOriginalAttachments`. `gws_schema` lists every helper flag.
87
-
88
- ## Drive
89
-
90
- Recent files, read:
91
-
92
24
  ```json
93
- {
94
- "operation": "drive.files.list",
95
- "params": { "q": "trashed = false", "pageSize": 20, "orderBy": "modifiedTime desc" },
96
- "fields": "files(id,name,mimeType,modifiedTime),nextPageToken"
97
- }
25
+ { "operation": "gmail.users.labels.get", "params": { "userId": "me", "id": "INBOX" }, "fields": "messagesUnread,threadsUnread" }
98
26
  ```
99
27
 
100
- One file's metadata, read:
28
+ - **Send, reply, forward.** Use `gmail.send` (`to`, `subject`, `body`, `cc`, `bcc`, `attachments`), or `gmail.reply`, `gmail.reply-all` or `gmail.forward` with `messageId`. Each plans as send. The preview lists everyone it reaches, including the sender a reply answers. Commit with `gws_send`.
29
+ - **Draft.** Add `"draft": true`. It plans as write and reaches no one, so commit with `gws_write`. Sending the draft later is `gmail.users.drafts.send`, which is a send.
101
30
 
102
31
  ```json
103
- {
104
- "operation": "drive.files.get",
105
- "params": { "fileId": "FILE_ID" },
106
- "fields": "id,name,mimeType,modifiedTime"
107
- }
108
- ```
109
-
110
- Create a file, write. Plan, then `gws_write`. An upload path must be absolute and inside an allowed read root. `gws_schema` shows the upload field.
111
-
112
- ```json
113
- {
114
- "operation": "drive.files.create",
115
- "body": { "name": "Notes.txt", "mimeType": "text/plain" }
116
- }
117
- ```
118
-
119
- Move to trash. The plan category is **delete**, even if the manifest says write. Commit with `gws_delete`, not `gws_write`.
120
-
121
- ```json
122
- {
123
- "operation": "drive.files.update",
124
- "params": { "fileId": "FILE_ID" },
125
- "body": { "trashed": true }
126
- }
127
- ```
128
-
129
- Share with one person. Plan category is share. Commit with `gws_share`. Share does not offer Always.
130
-
131
- ```json
132
- {
133
- "operation": "drive.permissions.create",
134
- "params": { "fileId": "FILE_ID" },
135
- "body": { "type": "user", "role": "reader", "emailAddress": "person@example.com" }
136
- }
32
+ { "operation": "gmail.reply", "input": { "messageId": "MESSAGE_ID", "body": "Thanks, Thursday works.", "draft": true } }
137
33
  ```
138
34
 
139
35
  ## Calendar
140
36
 
141
- Today's agenda, read:
37
+ - **Next meetings.** Always set `timeMin` to now. Without it, `orderBy: "startTime"` returns the calendar's oldest events. `timeMin` also returns events still running, such as all-day ones (`start.date`), so ask for more than you need (`maxResults: 10`) and skip those when the user means meetings. `singleEvents: true` expands recurring meetings.
38
+ - **A range, such as next week.** Set `timeMin` to Monday 00:00 and `timeMax` to the next Monday 00:00, both with the user's offset. Put `timeZone` in `fields` to learn the calendar's zone without another call. Count each event on its local start date. All-day events have `start.date`, not `start.dateTime`.
39
+ - **The helper.** `calendar.agenda` takes `today`, `tomorrow`, `week` or `days`, plus `timezone`. It is smaller than a full events list.
142
40
 
143
41
  ```json
144
- {
145
- "operation": "calendar.agenda",
146
- "input": { "today": true }
147
- }
42
+ { "operation": "calendar.events.list", "params": { "calendarId": "primary", "timeMin": "2026-11-05T14:30:00+01:00", "singleEvents": true, "orderBy": "startTime", "maxResults": 10 }, "fields": "timeZone,items(summary,start,end,htmlLink),nextPageToken" }
148
43
  ```
149
44
 
150
- Events on the primary calendar, read:
151
-
152
45
  ```json
153
- {
154
- "operation": "calendar.events.list",
155
- "params": { "calendarId": "primary", "maxResults": 10, "singleEvents": true, "orderBy": "startTime" },
156
- "fields": "items(id,summary,start,end),nextPageToken"
157
- }
46
+ { "operation": "calendar.agenda", "input": { "week": true, "timezone": "Europe/Amsterdam" } }
158
47
  ```
159
48
 
160
- Create an event that does not email anyone. Plan category is write. Commit with `gws_write`.
49
+ - **An event.** `calendar.events.insert` with `calendarId: "primary"` and a body with `summary`, `start` and `end`. It plans as write. With `sendUpdates: "all"` it emails the guests and plans as send.
161
50
 
162
- ```json
163
- {
164
- "operation": "calendar.events.insert",
165
- "params": { "calendarId": "primary" },
166
- "body": {
167
- "summary": "Standup",
168
- "start": { "dateTime": "2026-11-02T09:00:00+01:00" },
169
- "end": { "dateTime": "2026-11-02T09:15:00+01:00" }
170
- }
171
- }
172
- ```
51
+ ## Drive and Docs
173
52
 
174
- Create or move an event that emails attendees (`sendUpdates` of `all` or `externalOnly`; prefer it to the deprecated `sendNotifications`, which counts as a send too). The plan category is **send**. Commit with `gws_send`.
53
+ - **Search.** Drive's `q` uses clauses joined by `and`: `name contains 'budget'`, `fullText contains 'offsite'`, `'me' in owners` and `trashed = false`. A Doc is `mimeType = 'application/vnd.google-apps.document'`, and a Sheet is `mimeType = 'application/vnd.google-apps.spreadsheet'`. For "the one I edited", order by `modifiedByMeTime desc`; otherwise use `modifiedTime desc`.
175
54
 
176
55
  ```json
177
- {
178
- "operation": "calendar.events.insert",
179
- "params": { "calendarId": "primary", "sendUpdates": "all" },
180
- "body": {
181
- "summary": "Standup",
182
- "start": { "dateTime": "2026-11-02T09:00:00+01:00" },
183
- "end": { "dateTime": "2026-11-02T09:15:00+01:00" }
184
- }
185
- }
56
+ { "operation": "drive.files.list", "params": { "q": "mimeType = 'application/vnd.google-apps.document' and trashed = false", "orderBy": "modifiedByMeTime desc", "pageSize": 1 }, "fields": "files(id,name,modifiedTime,webViewLink,ownedByMe,capabilities/canTrash)" }
186
57
  ```
187
58
 
188
- ## Docs
189
-
190
- Read a document:
59
+ - **A Doc's text.** Read it with this mask; the `table` branch keeps text inside tables. The full document is several times larger. `drive.files.export` is not a read: it writes a file to disk after a prompt, so don't use it to read a Doc.
191
60
 
192
61
  ```json
193
- {
194
- "operation": "docs.documents.get",
195
- "params": { "documentId": "DOCUMENT_ID" }
196
- }
62
+ { "operation": "docs.documents.get", "params": { "documentId": "DOCUMENT_ID" }, "fields": "title,body/content(paragraph/elements/textRun/content,table/tableRows/tableCells/content/paragraph/elements/textRun/content)" }
197
63
  ```
198
64
 
199
- Create a document, write. Plan, then `gws_write`.
65
+ - **Edit a Doc.** `docs.documents.batchUpdate` with `{"requests": [{"insertText": {"location": {"index": 1}, "text": "..."}}]}`. Index 1 is the start of the body. It plans as write.
66
+ - **Trash (the bin).** Use `drive.files.update` with `"body": {"trashed": true}`. It plans as delete, so commit with `gws_delete`. The owner can restore it from the trash for 30 days. People it is shared with keep access until it is permanently deleted. Only the owner can trash a file (on a shared drive, an organizer or file organizer, with `supportsAllDrives: true`). Ask for `ownedByMe` and `capabilities/canTrash` in the list, as above, so you need no extra call. `drive.files.delete` is a permanent delete that removes everyone's access, so never use it for "bin" or "trash".
200
67
 
201
68
  ```json
202
- {
203
- "operation": "docs.documents.create",
204
- "body": { "title": "Notes" }
205
- }
69
+ { "operation": "drive.files.update", "params": { "fileId": "FILE_ID" }, "body": { "trashed": true } }
206
70
  ```
207
71
 
208
- Insert text, write. Plan, then `gws_write`.
209
-
210
- ```json
211
- {
212
- "operation": "docs.documents.batchUpdate",
213
- "params": { "documentId": "DOCUMENT_ID" },
214
- "body": {
215
- "requests": [
216
- { "insertText": { "location": { "index": 1 }, "text": "Hello" } }
217
- ]
218
- }
219
- }
220
- ```
72
+ - **Share.** `drive.permissions.create` with `fileId` and a body such as `{"type": "user", "role": "reader", "emailAddress": "..."}`. It plans as share.
73
+ - **Upload.** `drive.files.create` with a `body` (`name`, `mimeType`) and the plugin's own top-level `"upload": {"path": "/absolute/file.pdf", "mime": "application/pdf"}`. `gws_schema` doesn't show `upload`.
221
74
 
222
75
  ## Sheets
223
76
 
224
- Read a range. The helper is enough for a rectangular range:
77
+ - **Read.** `sheets.read` with `{"spreadsheet": "ID", "range": "A1:D20"}`. A range without a sheet name reads the first sheet, so don't guess a name such as `Sheet1`.
78
+ - **Append rows.** `sheets.spreadsheets.values.append` with `range: "A1"`, `valueInputOption: "USER_ENTERED"` and `{"values": [[...]]}`. It plans as write.
225
79
 
226
- ```json
227
- {
228
- "operation": "sheets.read",
229
- "input": { "spreadsheet": "SPREADSHEET_ID", "range": "Sheet1!A1:D20" }
230
- }
231
- ```
232
-
233
- The method, when you need the raw values resource:
234
-
235
- ```json
236
- {
237
- "operation": "sheets.spreadsheets.values.get",
238
- "params": { "spreadsheetId": "SPREADSHEET_ID", "range": "Sheet1!A1:D20" }
239
- }
240
- ```
80
+ ## Reports
241
81
 
242
- Append rows, write. Plan, then `gws_write`.
82
+ `workflow.standup-report`, `workflow.meeting-prep` and `workflow.weekly-digest` are reads. Run them with `gws_read`, and `input: {"format": "json"}` for structured data.
243
83
 
244
84
  ```json
245
- {
246
- "operation": "sheets.spreadsheets.values.append",
247
- "params": {
248
- "spreadsheetId": "SPREADSHEET_ID",
249
- "range": "Sheet1!A1",
250
- "valueInputOption": "USER_ENTERED"
251
- },
252
- "body": { "values": [["a", "b"]] }
253
- }
85
+ { "operation": "workflow.standup-report", "input": { "format": "json" } }
254
86
  ```
255
87
 
256
- Create a spreadsheet, write. Plan, then `gws_write`.
88
+ Login activity is Admin SDK Reports. It needs an account for a Workspace admin with the Reports privilege. Any other account gets `ADMIN_PRIVILEGE_REQUIRED`, which signing in again as the same account won't fix.
257
89
 
258
90
  ```json
259
- {
260
- "operation": "sheets.spreadsheets.create",
261
- "body": { "properties": { "title": "Notes" } }
262
- }
91
+ { "operation": "admin-reports.activities.list", "params": { "userKey": "all", "applicationName": "login", "startTime": "2026-11-04T14:30:00Z" }, "fields": "items(actor/email,id/time,events/name),nextPageToken" }
263
92
  ```
@@ -1,97 +0,0 @@
1
- ---
2
- name: opencode-gws-admin
3
- description: Load only when the user asks for admin reports, Apps Script, Workspace Events, Model Armor, or a workflow helper.
4
- opencode/autoinvoke: false
5
- ---
6
-
7
- # High-risk Workspace services
8
-
9
- This skill is not loaded automatically. Use it only when the user asked for admin reports, Apps Script, Workspace Events, Model Armor, or a workflow helper.
10
-
11
- Search with `gws_catalog` and check `gws_schema` before you call. If `executable` is false, do not call that id. Deploy and watch never run. A helper with no adapter cannot run; use `alternative` when the catalog names one.
12
-
13
- ## Admin gate
14
-
15
- An operation whose category is `admin` needs all of these before `gws_plan` will accept it:
16
-
17
- 1. `admin.enabled` is true, which is the default.
18
- 2. The service is in `admin.allowedServices` (the default list is the services that have an admin-class entry).
19
- 3. The plan includes a non-empty `reason` when `admin.requireReason` is true, which is the default.
20
-
21
- Then commit with `gws_admin` and only `{ "planId": "<planId>" }`. `gws_admin` asks every time. It does not offer Always. A user deny hides the tool.
22
-
23
- A read on one of these services is still a read: `gws_read`, no admin gate. A write that is not category `admin` still uses `gws_write` and the normal ask.
24
-
25
- Plugin policy cannot allow an admin call. A policy rule can only deny.
26
-
27
- ## Admin reports
28
-
29
- Read-only at both access levels. The sign-in still needs the admin report scopes from the auth doc, and the account must be a Workspace admin with the Reports privilege: otherwise every call fails with `ADMIN_PRIVILEGE_REQUIRED`, which signing in again as the same account does not fix.
30
-
31
- Activity list, read. `fields` is required because this is a list:
32
-
33
- ```json
34
- {
35
- "operation": "admin-reports.activities.list",
36
- "params": { "userKey": "all", "applicationName": "login" },
37
- "fields": "items(id,actor,events),nextPageToken"
38
- }
39
- ```
40
-
41
- Watching an activity feed is category `admin`, not a read. Plan it only when the user asked, with a reason, then commit with `gws_admin`:
42
-
43
- ```json
44
- {
45
- "operation": "admin-reports.activities.watch",
46
- "params": { "userKey": "all", "applicationName": "login" },
47
- "reason": "The user asked to watch login activity."
48
- }
49
- ```
50
-
51
- `gws_schema` shows the watch body. Do not invent a channel.
52
-
53
- ## Apps Script
54
-
55
- Read a project:
56
-
57
- ```json
58
- {
59
- "operation": "script.projects.get",
60
- "params": { "scriptId": "SCRIPT_ID" }
61
- }
62
- ```
63
-
64
- Creating a project is a write. Plan, then `gws_write`. Deployments and running a script never run. If catalog marks an id not executable, stop and tell the user.
65
-
66
- ## Workspace Events
67
-
68
- Most subscription methods never run. A status read can:
69
-
70
- ```json
71
- {
72
- "operation": "events.operations.get",
73
- "params": { "name": "operations/OPERATION_ID" }
74
- }
75
- ```
76
-
77
- ## Model Armor
78
-
79
- Read and write requests `https://www.googleapis.com/auth/cloud-platform` for Model Armor; Read-only requests nothing for it. The Model Armor helpers cannot run. Do not invent a call. Tell the user, and use `gws_catalog` if they still want a Discovery method.
80
-
81
- ## Workflow
82
-
83
- These three helpers are reads. They need the account to enable `workflow`, which uses the Gmail, Calendar, Tasks, and Drive scopes.
84
-
85
- ```json
86
- { "operation": "workflow.meeting-prep", "input": {} }
87
- ```
88
-
89
- ```json
90
- { "operation": "workflow.standup-report", "input": {} }
91
- ```
92
-
93
- ```json
94
- { "operation": "workflow.weekly-digest", "input": {} }
95
- ```
96
-
97
- Other workflow helpers cannot run. Use the alternative `gws_catalog` names, and commit with that method's category tool.