| name | gws-multi-account |
| description | Run the gws CLI (Google Workspace CLI) against multiple accounts stored under ~/.config/gws/<email>/, with metadata in ~/.config/gws/accounts.json. Always load this skill when executing any gws command so the right account is selected. Covers account selection, adding new accounts, migrating legacy flat ~/.config/gws/ setups, and the non-blocking `gws auth login` OAuth flow (background-spawn + poll; never run `gws auth login` in the foreground from an agent). |
gws — Multi-Account
The gws CLI ships with a single-config-dir model: all credentials live under one directory. This skill layers a multi-account convention on top:
~/.config/gws/accounts.json — account registry (email + description).
~/.config/gws/<email>/ — one config dir per account, holding client_secret.json, credentials.enc, token_cache.json, and cache/.
gws v0.4.2+ uses ~/.config/gws on all platforms (macOS, Linux, Windows) — on Windows this resolves to %USERPROFILE%\.config\gws (e.g., C:\Users\alice\.config\gws). If you're on a pre-v0.4.2 install and your data is still under %APPDATA%\gws, migrate it or let gws continue using the legacy path (it auto-detects).
Every gws invocation must set GOOGLE_WORKSPACE_CLI_CONFIG_DIR to the per-account directory explicitly. There is no implicit default — pick an account every time.
Shell syntax note. Examples below use POSIX shell (bash/zsh) — they work on macOS, Linux, and in any POSIX-compatible shell on Windows (Git Bash, WSL). For PowerShell and cmd.exe, see the Selecting an account section for syntax equivalents.
Layout
~/.config/gws/ ← %USERPROFILE%\.config\gws on Windows
├── accounts.json ← registry (this skill's contract)
├── alice@example.com/ ← one dir per account, named by email
│ ├── client_secret.json
│ ├── credentials.enc
│ ├── token_cache.json
│ └── cache/
└── bob@work.com/
└── …
accounts.json schema
Flat object keyed by email. Each value is metadata for that account.
{
"alice@example.com": {
"description": "Personal Gmail"
},
"bob@work.com": {
"description": "Work (Acme Corp) — billing, calendar"
}
}
- Keys are the account's email address and MUST match the directory name under
~/.config/gws/.
description is a free-form human-readable hint. Use it when the user refers to an account by nickname ("work", "personal", "the client one") to pick the right email.
- No
default field. Callers always specify an account.
Selecting an account
- Read
~/.config/gws/accounts.json.
- Map the user's phrasing → email using
description + email local-part.
- User says
"personal" → match the entry whose description contains "personal" (or @gmail.com local email).
- User says
"work" / company name → match by description.
- User gives a full email → use it directly if present in
accounts.json.
- If ambiguous, ask the user to pick.
- Set
GOOGLE_WORKSPACE_CLI_CONFIG_DIR="$HOME/.config/gws/<email>" for the command. Use $HOME, never a literal ~ — ~ does not expand inside quoted values or when the command isn't run through a shell, and gws will then create a stray ~/.config/gws/... directory under $PWD.
macOS / Linux / Git Bash / WSL
cat ~/.config/gws/accounts.json
GOOGLE_WORKSPACE_CLI_CONFIG_DIR="$HOME/.config/gws/alice@example.com" \
gws gmail users messages list --params '{"userId":"me"}'
Windows (PowerShell)
Get-Content "$env:USERPROFILE\.config\gws\accounts.json"
$env:GOOGLE_WORKSPACE_CLI_CONFIG_DIR = "$env:USERPROFILE\.config\gws\alice@example.com"
gws gmail users messages list --params '{"userId":"me"}'
Windows (cmd)
type "%USERPROFILE%\.config\gws\accounts.json"
set "GOOGLE_WORKSPACE_CLI_CONFIG_DIR=%USERPROFILE%\.config\gws\alice@example.com" && gws gmail users messages list --params "{\"userId\":\"me\"}"
Works identically across every gws service (gmail, calendar, drive, sheets, docs, slides, tasks, people, chat, forms, etc.).
Logging in (OAuth flow)
gws auth login is an interactive OAuth2 flow that starts a localhost callback server and blocks until the browser redirects back. Agent shells typically time out around 60 seconds; a foreground gws auth login will be killed mid-flow, the callback server dies with it, and the URL you already showed the user redirects to a dead port.
Rule: never run gws auth login in the foreground from an agent. Background-spawn it, extract the OAuth URL from its log file, share the URL verbatim, and poll gws auth status for completion.
The full six-step flow (with macOS / Linux + Windows PowerShell commands, troubleshooting table, and scope-flag reference) lives in references/auth-login.md. Load it whenever the user asks to log in, re-authenticate, or when gws auth status shows token_valid: false.
Triggers that should make you read the reference: "log into gws", "gws re-auth", "gmail login expired", "구글 로그인", "gws 재인증", gws auth login.
Rules for agents
- Always set
GOOGLE_WORKSPACE_CLI_CONFIG_DIR explicitly. Never rely on a shell default or an exported variable — the invocation must be self-contained.
- Never pass a literal
~ in the env var value. Use "$HOME/.config/gws/<email>" on POSIX, "$env:USERPROFILE\.config\gws\<email>" on PowerShell, "%USERPROFILE%\.config\gws\<email>" on cmd. A literal ~ does not expand when quoted or when the command is run outside an interactive shell — gws will silently create a stray ~/.config/gws/... directory under $PWD instead of writing to the real home directory. The gws-multi-account hook blocks bare ~ values with an explanatory error.
- Never invent an account. If the user hasn't specified one and you can't confidently pick from
accounts.json, ask.
- Never write to
accounts.json silently. Only modify it during the migration / add-account flows below, and only with the user's confirmation.
- Never log or echo credential file contents (
credentials.enc, token_cache.json, .encryption_key).
Adding a new account
When the user wants to register a new account:
- Ask for the account's email (this becomes the directory name and
accounts.json key).
- Ask for a short description (1 line, how the user will refer to it).
- Create the account directory:
- bash / zsh / Git Bash / WSL:
mkdir -p ~/.config/gws/<email>
- PowerShell:
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\gws\<email>"
- Run the
gws auth login flow against that directory. If the user already has a client_secret.json, drop it into ~/.config/gws/<email>/client_secret.json first. Do not run gws auth login in the foreground — it starts a local OAuth callback server that dies when the agent's command timeout fires, stranding the user with a dead URL. Follow the background-spawn flow in references/auth-login.md instead.
- Append the account to
accounts.json (preserve existing entries). Use Node for cross-platform JSON merging — no jq dependency. Node is guaranteed present on any machine running Claude Code or opencode.
GWS_ACCOUNTS_JSON="$HOME/.config/gws/accounts.json" EMAIL=<email> DESC=<description> \
node -e "const fs=require('fs'),p=process.env.GWS_ACCOUNTS_JSON;const d=fs.existsSync(p)?JSON.parse(fs.readFileSync(p,'utf8')):{};d[process.env.EMAIL]={description:process.env.DESC};fs.writeFileSync(p,JSON.stringify(d,null,2)+'\n');"
- Verify:
GOOGLE_WORKSPACE_CLI_CONFIG_DIR="$HOME/.config/gws/<email>" \
gws gmail users getProfile --params '{"userId":"me"}'
Migration from legacy flat layout
If ~/.config/gws/ contains credential files at the root (not inside an <email>/ subdirectory), it's a legacy single-account setup. Migrate it before using this skill.
On Windows, pre-v0.4.2 gws installs stored the config at %APPDATA%\gws instead of %USERPROFILE%\.config\gws. If you find credentials there, treat it as the legacy flat layout too — migrate into ~/.config/gws/<email>/ (which resolves to %USERPROFILE%\.config\gws\<email>) so newer gws versions pick it up.
Detection
Legacy layout is present when any of these exist directly under ~/.config/gws/ (or %APPDATA%\gws\ on older Windows installs):
client_secret.json
credentials.enc
token_cache.json
.encryption_key
Check with:
ls ~/.config/gws/client_secret.json ~/.config/gws/credentials.enc \
~/.config/gws/token_cache.json ~/.config/gws/.encryption_key 2>/dev/null
Get-ChildItem "$env:USERPROFILE\.config\gws\client_secret.json","$env:USERPROFILE\.config\gws\credentials.enc",`
"$env:USERPROFILE\.config\gws\token_cache.json","$env:USERPROFILE\.config\gws\.encryption_key",`
"$env:APPDATA\gws\client_secret.json","$env:APPDATA\gws\credentials.enc",`
"$env:APPDATA\gws\token_cache.json","$env:APPDATA\gws\.encryption_key" -ErrorAction SilentlyContinue
Interactive migration flow
Walk the user through this step by step. Do not skip the confirmation.
-
Detect and report. Tell the user: "I found a legacy gws config at ~/.config/gws/. I'd like to move it into the per-account layout."
-
Identify the account. Run a cheap call against the existing config to learn which email it belongs to:
gws gmail users getProfile --params '{"userId":"me"}'
Confirm the returned emailAddress with the user: "This config belongs to <email> — correct?"
-
Ask for a description. "How should I describe this account in accounts.json? (e.g., 'Personal Gmail', 'Work — Acme')."
-
Show the plan and confirm. Print what will happen before doing it:
mkdir -p ~/.config/gws/<email>
- Move
client_secret.json, credentials.enc, token_cache.json, .encryption_key, and cache/ into ~/.config/gws/<email>/.
- Create / update
accounts.json with { "<email>": { "description": "<description>" } }.
- Leave
~/.config/gws/.gitignore (if present) untouched at the root.
-
Execute after the user confirms.
macOS / Linux:
EMAIL="<email>"
DESC="<description>"
mkdir -p ~/.config/gws/"$EMAIL"
for f in client_secret.json credentials.enc token_cache.json .encryption_key cache; do
if [ -e ~/.config/gws/"$f" ]; then
mv ~/.config/gws/"$f" ~/.config/gws/"$EMAIL"/
GWS_ACCOUNTS_JSON= EMAIL= DESC= \
node -e
Partial / broken migrations
- If both root files and
<email>/ directories exist, ask the user which source is authoritative before doing anything. Do not merge blindly.
- If
accounts.json exists but an account directory is missing its credentials.enc, the account is unauthorized — re-run gws auth login against that config dir.
- If a directory exists under
~/.config/gws/ that is not listed in accounts.json, offer to add it (ask for a description) rather than deleting it.
Security
~/.config/gws/<email>/ contains live OAuth credentials. Never commit, copy to shared locations, or include in logs.
- The skill assumes a
.gitignore at ~/.config/gws/.gitignore keeps credentials.enc, token_cache.json, .encryption_key, and cache/ untracked. Preserve it during migration.
accounts.json contains only emails and descriptions — safe to back up, but avoid committing it if descriptions reveal sensitive context.