Skip to content

Commit 3eb6eda

Browse files
author
Peter Steinberger
committed
Merge branch 'origin/main' into fix/issue-126-iso-2022-jpr
2 parents ae0b226 + 04f6ff2 commit 3eb6eda

252 files changed

Lines changed: 26010 additions & 3077 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
- Formatting: `make fmt` (`goimports` local prefix `github.com/steipete/gogcli` + `gofumpt`).
2121
- Output: keep stdout parseable (`--json` / `--plain`); send human hints/progress to stderr.
22+
- Gmail labels: treat label IDs as case-sensitive opaque tokens; only case-fold label names for name lookup.
2223

2324
## Testing Guidelines
2425

README.md

Lines changed: 133 additions & 10 deletions
Large diffs are not rendered by default.

docs/RELEASING.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,4 @@ gog --help
106106
```
107107

108108
## Notes
109-
- `gog` currently does not print a version string; use tags + changelog as the source of truth.
110-
- If you later add `gog version`, update this doc to validate `gog version` post-install.
109+
- `gog --version` / `gog version` should report the release version post-install.

docs/contacts-json-update.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Update Contacts From JSON
2+
3+
`gog contacts update` supports JSON input via `--from-file`, so you can update People API fields without adding new CLI flags.
4+
5+
## Usage
6+
7+
Update from a file:
8+
9+
```bash
10+
gog contacts get people/c123456 --json > contact.json
11+
12+
# Edit contact.json (see notes below)
13+
gog contacts update people/c123456 --from-file contact.json
14+
```
15+
16+
Update from stdin:
17+
18+
```bash
19+
gog contacts get people/c123456 --json | \
20+
jq '(.contact.urls //= []) | (.contact.urls += [{"value":"https://example.com","type":"profile"}])' | \
21+
gog contacts update people/c123456 --from-file -
22+
```
23+
24+
## Input Formats
25+
26+
The command accepts:
27+
28+
- Wrapped (from `gog contacts get --json`): `{"contact": { ...person... }}`
29+
- Direct Person object: `{ ...person... }`
30+
31+
## What Can Be Updated
32+
33+
`--from-file` updates only fields that the People API allows via `people.updateContact` `updatePersonFields`.
34+
35+
Practical rule: include only fields you want to change, at the top level of the JSON object (for example `urls`, `biographies`, `names`, `emailAddresses`, `phoneNumbers`, `addresses`, `organizations`, ...).
36+
37+
If the JSON contains unsupported fields (for `updateContact`), gog errors instead of silently ignoring them.
38+
39+
Notes:
40+
41+
- Some fields are “singleton” for contact sources. Don’t include more than one value for `biographies`, `birthdays`, `genders`, or `names`.
42+
- If you update `memberships`, the Person must include contact group memberships or the API will error.
43+
44+
## Clearing Fields
45+
46+
Clearing list fields is supported by including the key with an empty value:
47+
48+
- Use `[]` to clear a list field (example: `"urls": []`)
49+
- Use `null` to clear a list field (example: `"biographies": null`)
50+
51+
## Concurrency (ETags)
52+
53+
To avoid overwriting concurrent contact edits, gog compares the JSON etag with the current contact etag:
54+
55+
- If they mismatch, update fails with an etag error.
56+
- Use `--ignore-etag` to apply your JSON changes to the latest version anyway.

docs/dates.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Date and Time Input Formats
2+
3+
Use one parsing contract across commands.
4+
5+
## Canonical choices
6+
7+
- Prefer `RFC3339` in automation: `2026-02-13T15:04:05Z`
8+
- Use `YYYY-MM-DD` for date-only fields (birthdays, date-only due values)
9+
- Keep timezone explicit when time matters
10+
11+
## Accepted input formats
12+
13+
- Date-only: `YYYY-MM-DD`
14+
- Datetime: `RFC3339` / `RFC3339Nano`
15+
- ISO offset without colon: `YYYY-MM-DDTHH:MM:SS-0800`
16+
- Local datetime (no timezone):
17+
- `YYYY-MM-DDTHH:MM[:SS]`
18+
- `YYYY-MM-DD HH:MM[:SS]`
19+
20+
## Relative forms
21+
22+
Calendar range flags (`--from` / `--to`) also accept:
23+
24+
- `now`, `today`, `tomorrow`, `yesterday`
25+
- Weekday names: `monday`, `next friday`
26+
27+
## Duration forms
28+
29+
Tracking `--since` also accepts `time.ParseDuration` values such as:
30+
31+
- `24h`
32+
- `15m`
33+
34+
## Agent guidance
35+
36+
- Generate RFC3339 for all datetime fields by default.
37+
- Use date-only for fields explicitly documented as dates.
38+
- For local scheduling, pass an explicit offset (or timezone-aware RFC3339) to avoid ambiguity.

docs/refactor/output.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,13 @@ Use `internal/cmd/output_helpers.go:printNextPageHint(u, token)`:
2727

2828
- prints to stderr
2929
- exact format (tests depend on it): `# Next page: --page <token>`
30+
31+
## Result (Key-Value) Output
32+
33+
Use `internal/cmd/output_helpers.go:writeResult(ctx, u, ...)` for simple command results (especially destructive operations).
34+
35+
Rules:
36+
37+
- Same keys in `--plain` and `--json` (no snake_case vs camelCase split).
38+
- Prefer explicit booleans for flags (e.g. `deleted`, `removed`, `trashed`).
39+
- Include primary identifiers (`id`, `fileId`, etc) as separate keys.

docs/spec.md

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ Build a single, clean, modern Go CLI that talks to:
66

77
- Gmail API
88
- Google Calendar API
9+
- Google Chat API
910
- Google Classroom API
1011
- Google Drive API
12+
- Google Docs API
13+
- Google Sheets API
14+
- Google Forms API
15+
- Apps Script API
16+
- Google Tasks API
17+
- Cloud Identity API (Groups)
1118
- Google People API (Contacts + directory)
19+
- Google Keep API (Workspace-only, service account)
1220

1321
This replaces the existing separate CLIs (`gmcli`, `gccli`, `gdcli`) and the Python contacts server conceptually, but:
1422

@@ -99,6 +107,9 @@ Implementation: `internal/secrets/store.go`.
99107

100108
- Desktop OAuth 2.0 flow using local HTTP redirect on an ephemeral port.
101109
- Supports a browserless/manual flow (paste redirect URL) for headless environments.
110+
- Supports a remote/server-friendly 2-step manual flow:
111+
- Step 1 prints an auth URL (`gog auth add ... --remote --step 1`)
112+
- Step 2 exchanges the pasted redirect URL and requires `state` validation (`--remote --step 2 --auth-url ...`)
102113
- Refresh token issuance:
103114
- requests `access_type=offline`
104115
- supports `--force-consent` to force the consent prompt when Google doesn't return a refresh token
@@ -119,6 +130,7 @@ Scope selection note:
119130
- `credentials-<client>.json` (OAuth client id/secret; named clients)
120131
- State:
121132
- `state/gmail-watch/<account>.json` (Gmail watch state)
133+
- `oauth-manual-state-<state>.json` (temporary manual OAuth state cache; expires quickly; no tokens)
122134
- Secrets:
123135
- refresh tokens in keyring
124136

@@ -148,7 +160,7 @@ Flag aliases:
148160
- `gog auth credentials <credentials.json|->`
149161
- `gog auth credentials list`
150162
- `gog --client <name> auth credentials <credentials.json|->`
151-
- `gog auth add <email> [--services user|all|gmail,calendar,classroom,drive,docs,contacts,tasks,sheets,people,groups] [--readonly] [--drive-scope full|readonly|file] [--manual] [--force-consent]`
163+
- `gog auth add <email> [--services user|all|gmail,calendar,classroom,drive,docs,contacts,tasks,sheets,people,groups] [--readonly] [--drive-scope full|readonly|file] [--manual] [--remote] [--step 1|2] [--auth-url URL] [--timeout DURATION] [--force-consent]`
152164
- `gog auth services [--markdown]`
153165
- `gog auth keep <email> --key <service-account.json>` (Google Keep; Workspace only)
154166
- `gog auth list`
@@ -165,16 +177,17 @@ Flag aliases:
165177
- `gog config path`
166178
- `gog config set <key> <value>`
167179
- `gog config unset <key>`
168-
- `gog drive ls [--parent ID] [--max N] [--page TOKEN] [--query Q]`
169-
- `gog drive search <text> [--max N] [--page TOKEN]`
180+
- `gog version`
181+
- `gog drive ls [--parent ID] [--max N] [--page TOKEN] [--query Q] [--[no-]all-drives]`
182+
- `gog drive search <text> [--raw-query] [--max N] [--page TOKEN] [--[no-]all-drives]`
170183
- `gog drive get <fileId>`
171-
- `gog drive download <fileId> [--out PATH]`
172-
- `gog drive upload <localPath> [--name N] [--parent ID]`
184+
- `gog drive download <fileId> [--out PATH] [--format F]` (`--format` only applies to Google Workspace files)
185+
- `gog drive upload <localPath> [--name N] [--parent ID] [--convert] [--convert-to doc|sheet|slides]`
173186
- `gog drive mkdir <name> [--parent ID]`
174-
- `gog drive delete <fileId>`
187+
- `gog drive delete <fileId> [--permanent]`
175188
- `gog drive move <fileId> --parent ID`
176189
- `gog drive rename <fileId> <newName>`
177-
- `gog drive share <fileId> [--anyone | --email addr] [--role reader|writer] [--discoverable]`
190+
- `gog drive share <fileId> --to anyone|user|domain [--email addr] [--domain example.com] [--role reader|writer] [--discoverable]`
178191
- `gog drive permissions <fileId> [--max N] [--page TOKEN]`
179192
- `gog drive unshare <fileId> <permissionId>`
180193
- `gog drive url <fileIds...>`
@@ -291,7 +304,7 @@ Flag aliases:
291304
- `gog contacts list [--max N] [--page TOKEN]`
292305
- `gog contacts get <people/...|email>`
293306
- `gog contacts create --given NAME [--family NAME] [--email addr] [--phone num]`
294-
- `gog contacts update <people/...> [--given NAME] [--family NAME] [--email addr] [--phone num]`
307+
- `gog contacts update <people/...> [--given NAME] [--family NAME] [--email addr] [--phone num] [--birthday YYYY-MM-DD] [--notes TEXT] [--from-file PATH|-] [--ignore-etag]`
295308
- `gog contacts delete <people/...>`
296309
- `gog contacts directory list [--max N] [--page TOKEN]`
297310
- `gog contacts directory search <query> [--max N] [--page TOKEN]`
@@ -302,6 +315,14 @@ Flag aliases:
302315
- `gog people search <query> [--max N] [--page TOKEN]`
303316
- `gog people relations [<people/...|userId>] [--type TYPE]`
304317

318+
Date/time input conventions (shared parser):
319+
320+
- Date-only: `YYYY-MM-DD`
321+
- Datetime: `RFC3339` / `RFC3339Nano` / `YYYY-MM-DDTHH:MM[:SS]` / `YYYY-MM-DD HH:MM[:SS]`
322+
- Numeric timezone offset accepted: `YYYY-MM-DDTHH:MM:SS-0800`
323+
- Calendar range flags also accept relatives: `now`, `today`, `tomorrow`, `yesterday`, weekday names (`monday`, `next friday`)
324+
- Tracking `--since` also accepts durations like `24h`
325+
305326
### Planned high-level command tree
306327

307328
- `gog auth …`

docs/watch.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ gog gmail watch serve \
4848
[--verify-oidc] [--oidc-email <svc@...>] [--oidc-audience <aud>] \
4949
[--token <shared>] \
5050
[--hook-url <url>] [--hook-token <token>] \
51-
[--include-body] [--max-bytes <n>] [--save-hook]
51+
[--include-body] [--max-bytes <n>] [--exclude-labels <id,id,...>] \
52+
[--history-types <type>...] [--save-hook]
5253
5354
gog gmail history --since <historyId> [--max <n>] [--page <token>]
5455
```
@@ -58,6 +59,10 @@ Notes:
5859
- `watch renew` reuses stored topic/labels.
5960
- `watch stop` calls Gmail stop + clears state.
6061
- `watch serve` uses stored hook if `--hook-url` not provided.
62+
- `watch serve --exclude-labels` defaults to `SPAM,TRASH`; set to an empty string to disable.
63+
- Exclude label IDs are matched exactly (case-sensitive opaque IDs).
64+
- `watch serve --history-types` accepts `messageAdded`, `messageDeleted`, `labelAdded`, `labelRemoved` (repeatable or comma-separated). Default: `messageAdded` (for backward compatibility).
65+
- `watch serve --history-types` must include at least one non-empty type.
6166

6267
## State
6368

@@ -95,6 +100,7 @@ Schema (v1):
95100
"source": "gmail",
96101
"account": "you@gmail.com",
97102
"historyId": "...",
103+
"deletedMessageIds": ["..."],
98104
"messages": [
99105
{
100106
"id": "...",

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module github.com/steipete/gogcli
22

3-
go 1.25
3+
go 1.24.0
44

55
require (
66
github.com/99designs/keyring v1.2.2
77
github.com/alecthomas/kong v1.13.0
88
github.com/muesli/termenv v0.16.0
99
github.com/yosuke-furukawa/json5 v0.1.1
10+
golang.org/x/net v0.49.0
1011
golang.org/x/oauth2 v0.34.0
1112
golang.org/x/term v0.39.0
1213
google.golang.org/api v0.260.0
@@ -40,7 +41,6 @@ require (
4041
go.opentelemetry.io/otel/metric v1.39.0 // indirect
4142
go.opentelemetry.io/otel/trace v1.39.0 // indirect
4243
golang.org/x/crypto v0.47.0 // indirect
43-
golang.org/x/net v0.49.0 // indirect
4444
golang.org/x/sys v0.40.0 // indirect
4545
golang.org/x/text v0.33.0 // indirect
4646
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect

internal/cmd/agent.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package cmd
2+
3+
// AgentCmd contains helper commands intended to make gog easier to consume from LLM agents.
4+
type AgentCmd struct {
5+
ExitCodes AgentExitCodesCmd `cmd:"" name:"exit-codes" aliases:"exitcodes,exit-code" help:"Print stable exit codes for automation"`
6+
}

0 commit comments

Comments
 (0)