gws --help.
Licenses: The upstream project uses
Apache-2.0; Mirage’s
independent implementation uses
Apache-2.0.
Install
clis: section; see the CLI overview.
Verbs
The syntax mirrors the upstream Google Workspace CLI: one passthrough leaf per Discovery method, plus hand-written helpers under each service.drive, sheets, docs, slides, gmail. The whole
surface is discoverable from the shell:
API passthrough
Passthrough commands call the corresponding API method directly:--params fills URL path and query parameters, --json is the request
body, and the output is the compact API response JSON. List methods
follow nextPageToken to the end by default (pages print as NDJSON);
--page-limit N stops early.
Folder scope
A config carryingfolder_id scopes what the CLI creates, so a file
it makes lands in the same folder a GDriveVFS sharing that config
mounts, and the agent’s own ls shows what it just made:
- Two divergences from the official CLI’s passthrough.
drive files createandcopygetparentsdefaulted into the request body, and the Docs/Sheets/Slidescreatemethods have noparentsfield at all, so mirage issues a second Drive call to move the new file. Both also sendsupportsAllDrives, which is what lets a scope name a Shared Drive folder. - An explicit
parentsarray always wins, and then nothing is injected into the query either: you typed the call, you own it. The key being present is what counts, so"parents": []is honored too rather than read as absent. - Reads are not scoped.
gws drive files liststill sees the whole account. The scope is about where new files go, not a fence. - Helpers are spelled bare, not
+-prefixed. Upstream marks its hand-written commands with a+(gws docs +write); mirage’s tree does not need the marker, because a helper and a Discovery method cannot collide inside a typed spec. So an upstream line carrying+has to drop it here. The flag names are upstream’s own and match it (--document --text,gmail send’s--to --subject --body,sheets read’s--spreadsheet --range);--tabis the single addition, because upstream’s+writetakes no tab argument and upstream has no tab support at all.
Document tabs
A Google Doc holds one or more tabs, and the Docs API reports them only when asked.documents.get fills the legacy top-level body from the
first tab and leaves tabs empty unless includeTabsContent is set;
with it, the content moves under tabs[].documentTab and body is left
empty. The two shapes are exclusive, so it is a shape switch rather than a
verbosity knob, and tabs nest through childTabs.
A .gdoc.json on a mount is always the tab-aware shape, because that is
the only one that can represent a document with more than one tab. Through
the passthrough you choose:
gws docs write without --tab appends to
the first tab, which is the API’s own default for a request that names no
tab; read the ids out of a mounted file with
jq '[.. | .tabProperties? // empty | .tabId]'. Note that
replaceAllText is one of three requests that instead default to every
tab, so a bare batchUpdate replacement is document-wide.