@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/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
- pluginVersion: text,
41
- hostVersion: text,
42
- hostWarning: text,
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
- ["pluginVersion", "hostVersion", "gws", "services", "accounts", "unguardedAgents"]
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["pluginVersion"]) && isText(value["hostVersion"]) && optional(value["hostWarning"], isText) && 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) && optional(value["nextStep"], isText);
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
- return status2.hostWarning ? `${status2.hostVersion} (host untested: ${status2.hostWarning})` : `${status2.hostVersion} (tested)`;
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.hostWarning ? "warning" : "success");
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 ?? NOTHING_TO_DO;
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.1",
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.1",
54
- "@joe-broadhead/opencode-gws-bin-darwin-x64": "0.0.1",
55
- "@joe-broadhead/opencode-gws-bin-linux-arm64-gnu": "0.0.1",
56
- "@joe-broadhead/opencode-gws-bin-linux-x64-gnu": "0.0.1"
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 `all` or `externalOnly` is a send: commit with `gws_send`. A read with `download.path` is an export: commit with `gws_export`.
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
  {
@@ -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>;