gws-multi-account
Multi-account management for gws, the Google Workspace CLI. A monorepo shipping plugins for three coding agents out of one shared core:
- Claude Code — a PreToolUse hook that blocks bare
gws calls, installed via the bundled marketplace (git clone).
- opencode — a
tool.execute.before hook and auto-registered skill, published to npm as opencode-gws-multi-account.
- TypeClaw — a
tool.before hook and a contributed skill, published to npm as typeclaw-gws-multi-account.
All three plugins share the same enforcement logic (packages/core) and the same SKILL.md. One source tree, three targets.
Why
The gws CLI reads GOOGLE_WORKSPACE_CLI_CONFIG_DIR to pick which account's credentials to use. The simplest way to juggle multiple accounts is to put each account's credentials in its own subdirectory and point the env var at the right one:
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=~/.config/gws/[email protected] gws auth login
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=~/.config/gws/[email protected] gws auth login
That gives you a per-account layout:
~/.config/gws/
├── [email protected]/
│ ├── client_secret.json
│ ├── credentials.enc
│ └── token_cache.json
└── [email protected]/
├── client_secret.json
├── credentials.enc
└── token_cache.json
And from then on, every call names its account explicitly:
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=~/.config/gws/[email protected] gws gmail ...
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=~/.config/gws/[email protected] gws gmail ...
This works — but it's easy for an agent to forget the prefix, and when it does, gws silently falls back to the default account and writes to the wrong inbox / calendar / drive. This package is the guardrail: a hook that inspects every gws invocation and blocks any call missing the env var with an explanatory message the agent can act on. The layout above becomes a contract; the agent picks the account from ~/.config/gws/accounts.json and the hook makes sure it actually did.
It also blocks a second footgun: foreground gws auth login. That command starts an interactive OAuth callback server and blocks until a browser redirect completes; the agent shell's ~60s command timeout kills it mid-flow and leaves the user with a dead URL. The hook catches this and points the agent at the background-spawn flow in references/auth-login.md. Legitimate background spawns (... gws auth login ... & or nohup gws auth login ... &) pass through unchanged.
See skills/gws-multi-account/SKILL.md for the full layout contract.
Install
Claude Code
/plugin marketplace add indentcorp/gws-multi-account
/plugin install gws-multi-account@gws-multi-account
opencode
Add to your opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-gws-multi-account"]
}
On first run the plugin registers its bundled skill by appending the path to skills.paths in your config (idempotent, JSONC-safe). Restart opencode once after the first install to pick up the skill.
TypeClaw
Add the package to plugins[] in your typeclaw.json:
{
"plugins": ["typeclaw-gws-multi-account"]
}
The plugin's plugin factory contributes a tool.before hook (same enforcement as the other two hosts) and the bundled gws-multi-account skill via skillsDirs. Restart the agent to load it.
Platform support
- macOS, Linux — both plugins work out of the box. The Claude hook runs under Node (bundled with Claude Code), the opencode plugin runs under Bun (bundled with opencode). No shell dependencies.
- Windows — the opencode plugin works. The Claude Code plugin is currently blocked by an upstream Claude Code bug (anthropics/claude-code#32486):
${CLAUDE_PLUGIN_ROOT} in hooks.json is not expanded on Windows, so the hook path never resolves. The plugin logic (parser, hook entry, skill registration) is cross-platform — the blocker is variable interpolation inside Claude Code itself, not in this package. The opencode plugin uses a different hook mechanism and is unaffected.
CI runs the full pipeline on Ubuntu, macOS, and Windows.
How it works
All three plugins funnel every Bash-style command through the same enforcement function (packages/core) before the agent is allowed to run it.