@joe-broadhead/opencode-gws 0.0.1 → 0.0.3
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/CHANGELOG.md +36 -0
- package/README.md +28 -9
- package/dist/index.js +1237 -512
- package/dist/tui.js +34 -10
- package/package.json +7 -6
- package/skills/opencode-gws/SKILL.md +5 -2
- package/skills/opencode-gws-admin/SKILL.md +1 -1
- package/skills/opencode-gws-recipes/SKILL.md +3 -1
- package/views/jelly-view.d.ts +0 -73
package/dist/tui.js
CHANGED
|
@@ -19,6 +19,7 @@ var object = (properties, required) => ({ type: "object", properties, required }
|
|
|
19
19
|
var MODES = ["interactive", "credentials_file", "access_token"];
|
|
20
20
|
var ACCESS = ["read", "write"];
|
|
21
21
|
var CLIENT_SOURCES = ["account", "plugin", "pasted"];
|
|
22
|
+
var STATES = ["connected", "sign_in_required", "not_configured", "host_untested"];
|
|
22
23
|
var account = object(
|
|
23
24
|
{
|
|
24
25
|
alias: text,
|
|
@@ -31,15 +32,16 @@ var account = object(
|
|
|
31
32
|
clientSource: { enum: CLIENT_SOURCES },
|
|
32
33
|
grantedScopes: texts,
|
|
33
34
|
missingScopes: texts,
|
|
35
|
+
delegationRequired: texts,
|
|
34
36
|
nextStep: text
|
|
35
37
|
},
|
|
36
38
|
["alias", "mode", "services", "connected"]
|
|
37
39
|
);
|
|
38
40
|
var status = object(
|
|
39
41
|
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
version: text,
|
|
43
|
+
host: object({ version: { type: ["string", "null"] }, range: text, tested: { type: "boolean" } }, ["version", "range", "tested"]),
|
|
44
|
+
state: { enum: STATES },
|
|
43
45
|
gws: object({ path: text, version: text }, ["path", "version"]),
|
|
44
46
|
services: texts,
|
|
45
47
|
accounts: { type: "array", items: account },
|
|
@@ -48,7 +50,7 @@ var status = object(
|
|
|
48
50
|
permissionsBlock: text,
|
|
49
51
|
nextStep: text
|
|
50
52
|
},
|
|
51
|
-
["
|
|
53
|
+
["version", "host", "state", "gws", "services", "accounts", "unguardedAgents", "nextStep"]
|
|
52
54
|
);
|
|
53
55
|
var Gws = Rpc.define({
|
|
54
56
|
id: RPC_ID,
|
|
@@ -65,18 +67,23 @@ var isStrings = (value) => Array.isArray(value) && value.every((entry) => typeof
|
|
|
65
67
|
var optional = (value, check) => value === void 0 || check(value);
|
|
66
68
|
var isText = (value) => typeof value === "string";
|
|
67
69
|
function isAccount(value) {
|
|
68
|
-
return isRecord(value) && isText(value["alias"]) && MODES.includes(value["mode"]) && isStrings(value["services"]) && typeof value["connected"] === "boolean" && optional(value["integrationID"], isText) && optional(value["needsAuth"], (entry) => entry === true) && optional(value["access"], (entry) => ACCESS.includes(entry)) && optional(value["clientSource"], (entry) => CLIENT_SOURCES.includes(entry)) && optional(value["grantedScopes"], isStrings) && optional(value["missingScopes"], isStrings) && optional(value["nextStep"], isText);
|
|
70
|
+
return isRecord(value) && isText(value["alias"]) && MODES.includes(value["mode"]) && isStrings(value["services"]) && typeof value["connected"] === "boolean" && optional(value["integrationID"], isText) && optional(value["needsAuth"], (entry) => entry === true) && optional(value["access"], (entry) => ACCESS.includes(entry)) && optional(value["clientSource"], (entry) => CLIENT_SOURCES.includes(entry)) && optional(value["grantedScopes"], isStrings) && optional(value["missingScopes"], isStrings) && optional(value["delegationRequired"], isStrings) && optional(value["nextStep"], isText);
|
|
69
71
|
}
|
|
70
72
|
function isStatus(value) {
|
|
71
|
-
return isRecord(value) && isText(value["
|
|
73
|
+
return isRecord(value) && isText(value["version"]) && isRecord(value["host"]) && (value["host"]["version"] === null || isText(value["host"]["version"])) && isText(value["host"]["range"]) && typeof value["host"]["tested"] === "boolean" && STATES.includes(value["state"]) && isRecord(value["gws"]) && isText(value["gws"]["path"]) && isText(value["gws"]["version"]) && isStrings(value["services"]) && Array.isArray(value["accounts"]) && value["accounts"].every(isAccount) && optional(value["auditDegraded"], (entry) => typeof entry === "boolean") && isStrings(value["unguardedAgents"]) && optional(value["permissionsBlock"], isText) && isText(value["nextStep"]);
|
|
72
74
|
}
|
|
73
|
-
var NOTHING_TO_DO = "Nothing to do.";
|
|
74
75
|
function connectionLabel(account2) {
|
|
75
76
|
if (account2.connected) return "connected";
|
|
76
77
|
return account2.mode === "interactive" ? "sign-in required" : "not configured";
|
|
77
78
|
}
|
|
78
79
|
function openCodeLabel(status2) {
|
|
79
|
-
|
|
80
|
+
const version = status2.host.version ?? "unknown";
|
|
81
|
+
return status2.host.tested ? `${version} (tested)` : `${version} (host untested; supported range ${status2.host.range})`;
|
|
82
|
+
}
|
|
83
|
+
var DELEGATION_LABEL = "Needs domain-wide delegation";
|
|
84
|
+
function delegationText(account2) {
|
|
85
|
+
const services = account2.delegationRequired ?? [];
|
|
86
|
+
return `${services.join(", ")} (a Workspace admin sets it up; then add the scopes to accounts.${account2.alias}.scopes)`;
|
|
80
87
|
}
|
|
81
88
|
var BLOCK_PLACEMENT = "Put this block first in that agent's own permissions, followed by your top-level rules for gws actions. Never put it in the top-level permissions: that would loosen Explore.";
|
|
82
89
|
|
|
@@ -248,6 +255,23 @@ function AccountStatus(props) {
|
|
|
248
255
|
}
|
|
249
256
|
})
|
|
250
257
|
}), null);
|
|
258
|
+
_$insert(_el$4, _$createComponent(Show, {
|
|
259
|
+
get when() {
|
|
260
|
+
return _$memo(() => !!props.account.delegationRequired?.length)() ? props.account : void 0;
|
|
261
|
+
},
|
|
262
|
+
children: (account2) => _$createComponent(Row, {
|
|
263
|
+
label: DELEGATION_LABEL,
|
|
264
|
+
get value() {
|
|
265
|
+
return delegationText(account2());
|
|
266
|
+
},
|
|
267
|
+
get fg() {
|
|
268
|
+
return muted();
|
|
269
|
+
},
|
|
270
|
+
get muted() {
|
|
271
|
+
return muted();
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
}), null);
|
|
251
275
|
return _el$4;
|
|
252
276
|
})();
|
|
253
277
|
}
|
|
@@ -374,7 +398,7 @@ function StatusBody(props) {
|
|
|
374
398
|
return openCodeLabel(props.status);
|
|
375
399
|
},
|
|
376
400
|
get fg() {
|
|
377
|
-
return tone(props.status.
|
|
401
|
+
return tone(props.status.host.tested ? "success" : "warning");
|
|
378
402
|
},
|
|
379
403
|
get muted() {
|
|
380
404
|
return muted();
|
|
@@ -383,7 +407,7 @@ function StatusBody(props) {
|
|
|
383
407
|
_$insert(_el$6, _$createComponent(Row, {
|
|
384
408
|
label: "Next step",
|
|
385
409
|
get value() {
|
|
386
|
-
return props.status.nextStep
|
|
410
|
+
return props.status.nextStep;
|
|
387
411
|
},
|
|
388
412
|
get fg() {
|
|
389
413
|
return base();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joe-broadhead/opencode-gws",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Unofficial OpenCode plugin for Google Workspace (gws CLI)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"dist",
|
|
29
29
|
"skills",
|
|
30
30
|
"manifest/generated.json",
|
|
31
|
-
"views",
|
|
31
|
+
"views/gmail-thread.html",
|
|
32
|
+
"views/jelly-view.js",
|
|
32
33
|
"README.md",
|
|
33
34
|
"LICENSE",
|
|
34
35
|
"NOTICE",
|
|
@@ -50,10 +51,10 @@
|
|
|
50
51
|
"docs:coverage": "node scripts/generate-docs.mjs"
|
|
51
52
|
},
|
|
52
53
|
"optionalDependencies": {
|
|
53
|
-
"@joe-broadhead/opencode-gws-bin-darwin-arm64": "0.0.
|
|
54
|
-
"@joe-broadhead/opencode-gws-bin-darwin-x64": "0.0.
|
|
55
|
-
"@joe-broadhead/opencode-gws-bin-linux-arm64-gnu": "0.0.
|
|
56
|
-
"@joe-broadhead/opencode-gws-bin-linux-x64-gnu": "0.0.
|
|
54
|
+
"@joe-broadhead/opencode-gws-bin-darwin-arm64": "0.0.3",
|
|
55
|
+
"@joe-broadhead/opencode-gws-bin-darwin-x64": "0.0.3",
|
|
56
|
+
"@joe-broadhead/opencode-gws-bin-linux-arm64-gnu": "0.0.3",
|
|
57
|
+
"@joe-broadhead/opencode-gws-bin-linux-x64-gnu": "0.0.3"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@babel/core": "7.29.7",
|
|
@@ -13,7 +13,7 @@ Call `gws_status` before the first Workspace call in a session. It lists account
|
|
|
13
13
|
|
|
14
14
|
Omit `account` when `gws_status` shows one account. Pass the alias when it shows more than one. A missing alias is `ACCOUNT_REQUIRED`. An unknown alias is `ACCOUNT_UNKNOWN`.
|
|
15
15
|
|
|
16
|
-
An account signs in Read-only or Read and write; `gws_status` shows which. The `default` account defaults to Read and write, which covers every method a Google sign-in can run. An operation whose scopes were not granted fails with `AUTH_SCOPE_MISSING` before anything runs: ask the user to sign in again, approve every scope Google lists, and choose Read and write if the error says so. Do not invent a scope. A method that `gws_catalog` or `gws_schema` marks `appOnly` needs a Chat app's or Classroom add-on's own credentials; no sign-in can run it, so use another method.
|
|
16
|
+
An account signs in Read-only or Read and write; `gws_status` shows which. The `default` account defaults to Read and write, which covers every method a Google sign-in can run. An operation whose scopes were not granted fails with `AUTH_SCOPE_MISSING` before anything runs: ask the user to sign in again, approve every scope Google lists, and choose Read and write if the error says so. Do not invent a scope. A method that `gws_catalog` or `gws_schema` marks `appOnly` (`restriction: "app-only"`) needs a Chat app's or Classroom add-on's own credentials; no sign-in can run it, so use another method. A method marked `restriction: "domain-wide-delegation"` (every Keep method) runs only when a Workspace admin has set up domain-wide delegation and `accounts.<alias>.scopes` adds a Keep scope; otherwise tell the user so.
|
|
17
17
|
|
|
18
18
|
Not signed in: `AUTH_REQUIRED`. The next step is to run `/connect` and choose Google Workspace, or `opencode auth login gws-<alias>`.
|
|
19
19
|
|
|
@@ -47,7 +47,7 @@ A change is two calls.
|
|
|
47
47
|
| export | `gws_export` |
|
|
48
48
|
| admin | `gws_admin` |
|
|
49
49
|
|
|
50
|
-
The plan's category wins over the manifest. A Drive update whose body sets `trashed` to true is a delete: commit with `gws_delete`. A Calendar event write whose `sendUpdates` is `
|
|
50
|
+
The plan's category wins over the manifest. A Drive update whose body sets `trashed` to true is a delete: commit with `gws_delete`. A Calendar event write whose `sendUpdates` is not `none`, or whose `sendNotifications` is true, emails the guests and is a send: commit with `gws_send`. A read with `download.path` is an export: commit with `gws_export`.
|
|
51
51
|
|
|
52
52
|
Plans are single-use and bound to this session, agent, and account. `PLAN_EXPIRED`, `PLAN_ALREADY_USED`, and `PLAN_CALLER_MISMATCH` mean: read the current state, plan again, and show the new preview. `CATEGORY_MISMATCH` means you called the wrong commit tool.
|
|
53
53
|
|
|
@@ -115,10 +115,13 @@ An error is `CODE: What happened. Next step: What to do.` Do the next step once.
|
|
|
115
115
|
| --- | --- |
|
|
116
116
|
| `AUTH_REQUIRED` | Ask the user to sign in as the message says, then retry. |
|
|
117
117
|
| `AUTH_SCOPE_MISSING` | Ask the user to sign in again with the access the message names, or to add the scope. Nothing ran. |
|
|
118
|
+
| `ADMIN_PRIVILEGE_REQUIRED` | The sign-in is valid; the account lacks the Workspace admin privilege the API needs. Do not ask the user to sign in again as the same account. Tell them the message's next step. |
|
|
118
119
|
| `API_DISABLED` | Ask the user to enable the API at the URL in the message, wait about a minute, and retry. |
|
|
119
120
|
| `OPERATION_UNKNOWN` | The id is not in the catalog. Search again with `gws_catalog`. |
|
|
120
121
|
| `HELPER_UNSUPPORTED` | The helper cannot run. Use the method `gws_catalog` names as `alternative`. |
|
|
121
122
|
| `POLICY_DENIED` | Stop. Tell the user. Do not rephrase the call to get around the deny. |
|
|
123
|
+
| `RECIPIENTS_UNKNOWN` | A recipient rule applies and the plugin could not tell who the send or share reaches. Name the recipients in `to`, `cc` and `bcc` (or the `To`, `Cc` and `Bcc` headers of `raw`) once; otherwise tell the user. |
|
|
124
|
+
| `ACCOUNT_DOMAIN_UNKNOWN` | A rule refuses external recipients and the account's own domain is unknown. Tell the user the message's next step. |
|
|
122
125
|
| `GWS_VALIDATION_ERROR` | Fix the request. `gws_schema` shows the parameters. List and search need `fields`. |
|
|
123
126
|
| `CANCELLED` | Do not retry unless the user asks. A commit that was already running may have been applied. Check with `gws_read` before planning again. |
|
|
124
127
|
| `SESSION_UNKNOWN` | A script named a session that does not exist. Have it create a session and keep that id. |
|
|
@@ -26,7 +26,7 @@ Plugin policy cannot allow an admin call. A policy rule can only deny.
|
|
|
26
26
|
|
|
27
27
|
## Admin reports
|
|
28
28
|
|
|
29
|
-
Read-only at both access levels. The sign-in still needs the admin report scopes from the auth doc.
|
|
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
30
|
|
|
31
31
|
Activity list, read. `fields` is required because this is a list:
|
|
32
32
|
|
|
@@ -83,6 +83,8 @@ Reply. Same commit tool, `gws_send`:
|
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
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
|
+
|
|
86
88
|
## Drive
|
|
87
89
|
|
|
88
90
|
Recent files, read:
|
|
@@ -169,7 +171,7 @@ Create an event that does not email anyone. Plan category is write. Commit with
|
|
|
169
171
|
}
|
|
170
172
|
```
|
|
171
173
|
|
|
172
|
-
Create or move an event that emails attendees (`sendUpdates` of `all` or `externalOnly`). The plan category is **send**. Commit with `gws_send`.
|
|
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`.
|
|
173
175
|
|
|
174
176
|
```json
|
|
175
177
|
{
|
package/views/jelly-view.d.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The types of `jelly-view.js`, for a page written or checked with TypeScript:
|
|
3
|
-
* `/// <reference path="./jelly-view.d.ts" />`. They mirror the bridge envelopes
|
|
4
|
-
* in Jelly's contracts (`protocol/contracts/src/view-bridge.ts`); the page itself
|
|
5
|
-
* carries only the JavaScript.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
type JellyViewPlace = "tool-result" | "home-widget" | "settings-section";
|
|
9
|
-
type JellyViewGrant = "data" | "state" | "rpc" | "open-agent";
|
|
10
|
-
|
|
11
|
-
/** The fixed theme token set; the helper sets each as `--jelly-<name>` on the page. */
|
|
12
|
-
interface JellyViewTheme {
|
|
13
|
-
text: string;
|
|
14
|
-
muted: string;
|
|
15
|
-
surface: string;
|
|
16
|
-
raised: string;
|
|
17
|
-
border: string;
|
|
18
|
-
accent: string;
|
|
19
|
-
"on-accent": string;
|
|
20
|
-
danger: string;
|
|
21
|
-
font: string;
|
|
22
|
-
radius: string;
|
|
23
|
-
scheme: "light" | "dark";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface JellyViewHello {
|
|
27
|
-
v: 1;
|
|
28
|
-
kind: "hello";
|
|
29
|
-
/** `package` is null for a Home app an agent made. */
|
|
30
|
-
view: { package: string | null; id: string; title: string; place: JellyViewPlace };
|
|
31
|
-
grants: JellyViewGrant[];
|
|
32
|
-
/** The methods `call` may name. */
|
|
33
|
-
rpc: string[];
|
|
34
|
-
theme: JellyViewTheme;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** A view's state: JSON of at most 64 KB, and the revision a write must name. */
|
|
38
|
-
interface JellyViewState {
|
|
39
|
-
revision: number;
|
|
40
|
-
data: unknown;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
type JellyViewPush =
|
|
44
|
-
| { v: 1; kind: "data.changed"; data: unknown }
|
|
45
|
-
| ({ v: 1; kind: "state.changed" } & JellyViewState)
|
|
46
|
-
| { v: 1; kind: "theme.changed"; theme: JellyViewTheme }
|
|
47
|
-
| { v: 1; kind: "visibility"; visible: boolean }
|
|
48
|
-
/** The call with request id `re` waits for the person, at most until `until` (ISO time). */
|
|
49
|
-
| { v: 1; kind: "rpc.waiting"; re: string; until: string };
|
|
50
|
-
|
|
51
|
-
interface JellyView {
|
|
52
|
-
hello: JellyViewHello;
|
|
53
|
-
/** Hear pushes; returns the way to stop. */
|
|
54
|
-
on(listener: (push: JellyViewPush) => void): () => void;
|
|
55
|
-
/** Any request by its op, for one the helper has no name for. */
|
|
56
|
-
request(op: string, fields?: Record<string, unknown>): Promise<unknown>;
|
|
57
|
-
/** The place's data (the `data` grant). */
|
|
58
|
-
data(): Promise<unknown>;
|
|
59
|
-
/** The view's state (the `state` grant). */
|
|
60
|
-
state(): Promise<JellyViewState>;
|
|
61
|
-
/** Replace the state if `revision` is still the stored one; rejects on a conflict. */
|
|
62
|
-
setState(revision: number, data: unknown): Promise<JellyViewState>;
|
|
63
|
-
/** One of the methods the hello lists (the `rpc` grant). */
|
|
64
|
-
call(method: string, input?: unknown): Promise<unknown>;
|
|
65
|
-
/** Start a message for the person to send; it is never sent (the `open-agent` grant). */
|
|
66
|
-
openAgent(prefill?: string): Promise<null>;
|
|
67
|
-
/** The height the page needs, in CSS pixels, up to 1200: a hint the app may draw shorter. */
|
|
68
|
-
resize(height: number): Promise<null>;
|
|
69
|
-
/** Not mine: the app closes the frame and keeps its ordinary look. */
|
|
70
|
-
decline(): Promise<null>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
declare function jellyView(): Promise<JellyView>;
|