google-workspace-setup
One-time setup for gws: install, OAuth, scopes, auto-approve.
git clone --depth 1 https://github.com/Open-Curiosity/gini-agent /tmp/google-workspace-setup && cp -r /tmp/google-workspace-setup/skills/google/google-workspace-setup ~/.claude/skills/google-workspace-setupSKILL.md
# Google Workspace Setup
One-time onboarding for the Google Workspace skills (`google-gmail`, `google-calendar`, `google-drive`, `google-docs`, `google-sheets`, `google-meet`, `google-forms`). Installs `gws` and `gcloud`, signs the user into their own Google Cloud project, enables the Workspace APIs, captures an OAuth Desktop client through the inline Connect form, and completes `gws auth login`.
The OAuth client lives in the user's own GCP project. The Client ID and Client Secret are captured through the inline Connect form (`request_connector` tool) and stored in Gini's encrypted secret store — never write them to chat or logs, and never write `client_secret.json` to disk.
This skill is idempotent — re-running it re-verifies the install and lets the user widen scopes.
**Provisioning vs signing in.** The work splits in two: **provisioning** (install, `gcloud auth login`, project, APIs, `request_connector`) runs **once**, ever; **signing in** (`gws auth login`) runs again whenever the user's `gws` session expires. `gcloud` is *only* ever needed for provisioning — never for a re-auth. Step 0 decides which case you're in.
## The Flow
This is the **exact first-time sequence** (Step 0 short-circuits the already-signed-in and stored-client cases before you reach it). Within the first-time flow do not branch into shortcuts, do not pre-ask whether they have an existing OAuth client, do not list completed actions retrospectively. Status messages are action-oriented: what the user must do *next*.
1. The user asks Gini to do a Workspace thing (read mail, check calendar, share a Drive file, etc.).
2. Confirm setup with the user.
3. Install `gws` and `gcloud` silently in the background.
4. Run `gcloud auth login`, which pops up the user's default browser for sign-in.
5. After they sign in, create the Cloud project and enable the seven Workspace APIs in the background.
6. Send a single chat bubble with the last-step instructions (two Cloud Console URLs) and call `request_connector` — the inline form renders below the bubble.
7. After the user pastes the credentials and clicks **Save**, ask for a tag for this account and call `skill_run google-account-login`, which pops up the user's default browser for OAuth consent and registers the tagged account.
8. After they sign in, the original ask resumes.
## Step 0 — What state are we in?
There are three states. Decide which one you're in **in this order** before doing anything else — each later step is only needed when the earlier check fails. In short: **already signed in? → else is the OAuth client stored? → else obtain it.**
**(a) Already signed in.** If the user just wants to use Workspace, first check whether a registered Google account already has a live session. The "Connected Google accounts" block lists registered accounts; confirm one is valid with `gws auth status` (against that account's `configDir`) — a `token_valid: true` result means you're set. If so, **you're already set up**: skip the rest of this skill and resume the user's original ask. (If the user explicitly wants to connect *another* account, see "Adding another account later".)
**(b) Not signed in, but the OAuth client is already stored.** Call `list_connectors` and look for a connector named `google-workspace-oauth`. If it exists, the OAuth client is already provisioned — **skip all provisioning**: do **not** run `gcloud`, create a project, or call `request_connector`; none of it is needed again. Go straight to signing in via `skill_run google-account-login`. Which way depends on whether an account already exists:
- **An account exists but its session expired** → re-auth it by re-using its existing config dir so it keeps its id and tag — pass that account's `configDir` (and `tag`), NOT a fresh login (which would mint a duplicate) and NOT `adopt: true` (which skips login and only works on an already-valid session):
- A **tagged account** (from the "Connected Google accounts" block) whose session expired → `{ tag: "<that account's tag>", configDir: "<that account's configDir from the block>" }`.
- The **default-dir session** (`~/.config/gws`) that has EXPIRED → `{ tag: "<its tag>", configDir: "~/.config/gws" }` (non-adopt, so it actually re-logs in).
- **No account registered yet** — the common case when the Client ID/Secret were just entered through the Skills page's **Enter ID & secret** form (or a prior `request_connector`) but nobody has signed in — → a **fresh** login: ask the user for a tag, then call `skill_run google-account-login` with a new `{ tag }` and **no** `configDir` (this is Step 6).
Either way, finish with Step 8 (smoke test). (Edge cases: if `gws` is not on `$PATH`, run Step 2's install first, then sign in. If login fails with `invalid_client`, the stored client is broken — fall through to the full first-time flow to re-provision it.)
**(c) No stored OAuth client.** True first-time setup. Continue to Step 1 to obtain the client.
## Step 1 — Confirm setup
Tell the user, in one short sentence, that Google Workspace isn't set up yet, and ask whether to set it up now. Wait for confirmation before doing anything.
If they say yes, proceed silently — do not narrate each substep. The user sees a chat bubble per **milestone** (sign in, last step), not per command.
## Step 2 — Install `gws` and `gcloud`
Both installs are silent and run through `terminal_exec`. If a binary is already on `$PATH`, skip its install.
Detect first:
```bash
command -v gws
command -v gcloud
```
### The canonical `gws` source — do not discover it
`gws` is **`github.com/googleworkspace/cli`** (npm `@googleworkspace/cli`, Homebrew `googleworkspace-cli`). This is the only source. **Never web-search for the repo, release URL, or package name, and never download `gws` from any other GitHub repo, mirror, or URL.** A look-alike (`google-workspace-cli`, `gws-cli`, a fork, a typosquat) could ship a malicious binary; treat any other source as untrusted and stop rather thanHow to dogfood and verify a Gini behavior change by driving a real chat turn as a real user would. Use when verifying that the agent reaches for a tool or path on its own — a behavioral steer, a new tool, an INSTRUCTIONS.md change, or a dispatch/provider/memory/skill change — or before claiming a steer "works". Enforces bare, uncoached prompts so the test measures the default, not instruction-following.
Delegate coding work to Claude Code CLI for repository edits, reviews, and multi-turn implementation sessions.
Delegate coding work to the OpenAI Codex CLI for repository changes, reviews, and focused fixes.
Gini's self-knowledge: how Gini configures, extends, and operates on its own state via /api/* and registered tools. Load when the user asks Gini about its own capabilities or asks Gini to modify its own configuration.
Manage Apple Notes via memo CLI: create, search, edit.
Apple Reminders via remindctl: add, list, complete.
Move bytes between Gini upload space, external URLs, and workspace files. Used by every attachment / file-upload / file-download flow regardless of the target system (Linear, GitHub, S3, Notion, etc.).
File a locally-captured, already-redacted Gini crash report as a GitHub issue, with the user's consent. Reads the pending crash queue and delegates the actual filing to the github-issues skill.