Summary
gogcli's path resolution today follows $XDG_CONFIG_HOME only — every persisted file lands under ${XDG_CONFIG_HOME}/gogcli/ regardless of whether it's config, durable user data (OAuth tokens, keyring, service-account keys), mutable runtime state, regenerable cache, or bulk downloads. This conflates five distinct XDG kinds into one directory. Proposal: honor the freedesktop.org XDG Base Directory Spec per kind. When the relevant XDG env var is set, use it (e.g. $XDG_DATA_HOME/gogcli/ for credentials); when unset, fall back to the spec default for that kind (~/.local/share/gogcli/ for data, ~/.local/state/gogcli/ for state, ~/.cache/gogcli/ for cache, ~/.config/gogcli/ for config — never the current config-dir-for-everything layout). Today's ${XDG_CONFIG_HOME}/gogcli/ and ~/.gog/ paths are read-compat fallbacks for existing files only, per the read-legacy-write-new pattern in "Backwards compatibility" below — new installs go straight to the kind-correct defaults.
Mirrors the resolution sibling project openclaw/mcporter#155 shipped in mcporter v0.10.0.
What's actually written under ${XDG_CONFIG_HOME}/gogcli/ today
Source audit at gogcli tag v0.17.0. Every path below derives from config.Dir() in internal/config/paths.go, which itself resolves only via XDG_CONFIG_HOME (or os.UserConfigDir() as fallback). Categorical XDG kinds (per the spec) determine the right env var for each:
Note on os.UserCacheDir() in backup_gmail.go: Go's stdlib already resolves os.UserCacheDir() via $XDG_CACHE_HOME on Linux, so the four cache sites accidentally honor XDG today — but they bypass any future tool-specific override mechanism (e.g. GOG_HOME — see related feature request), and the path is $XDG_CACHE_HOME/gog/ (note: gog, not gogcli!) because that's whatever the bare os.UserCacheDir() returns joined with whatever cmd code appends. Inconsistent with the AppName = "gogcli" constant used everywhere else.
Note on backup.json at ~/.gog/ (defaultConfigPath() in internal/backup/config.go): a second top-level dotfile root that bypasses XDG entirely and uses gog (not gogcli) as the directory name. Pre-existing inconsistency worth aligning in the same change.
This codebase already has the right patterns — just not applied consistently
Two existing patterns make the proposed change consistent with current code, not foreign to it:
-
gogcli already honors XDG_CONFIG_HOME for the main config dir (config.Dir()) and the tracking config (tracking.ConfigPath()). The proposal is to extend the kind-aware pattern to the other XDG vars, not to bolt on XDG from scratch.
-
AppName = "gogcli" is already centralized (paths.go) and reused across every path-resolution function in internal/config/. Adding DataDir(), StateDir(), CacheDir(), DownloadsDir() siblings to the existing Dir() reuses the same naming and the same AppName constant.
Why XDG over a single bespoke GOG_HOME
A separate (related) feature request is planned for GOG_HOME and per-kind GOG_*_DIR env vars as tool-specific path overrides — a MCPORTER_CONFIG-style escape hatch above the XDG layer. That request and this one are complementary, not alternatives:
- This proposal (XDG kinds) fixes the semantic problem: gogcli today stores OAuth credentials, mutable state, and bulk downloads in the config directory, which is wrong by spec regardless of any override mechanism.
GOG_HOME (the other proposal) adds a tool-specific override above the XDG layer for environments where XDG resolution is unavailable or undesirable (sandboxes that strip XDG vars, CI runners, etc.).
Both are valuable because they fix different problems. This proposal should land first because it's the semantic correctness fix; GOG_HOME is the operator-ergonomics addition on top.
The spec rationale itself:
-
Standardized. Every Linux operator already knows what the XDG vars mean — no new tool-specific env var to discover and document.
-
Categorical separation matters in deployments. Per the spec:
| Spec var |
Default |
What goes there |
gogcli use |
XDG_CONFIG_HOME |
~/.config |
User config files |
OAuth client metadata, tracking config (keyring backend selection if it ever becomes file-overridable) |
XDG_DATA_HOME |
~/.local/share |
Durable user data |
credentials*.json, keyring/, service-account JSON keys |
XDG_STATE_HOME |
~/.local/state |
Logs, history, transient state |
tracking.json, gmail-watch/ cursors |
XDG_CACHE_HOME |
~/.cache |
Regenerable |
Gmail backup intermediate cache |
Operators routinely place cache on tmpfs/local-SSD (regenerable, fast loss OK), data on backed-up volumes (irreplaceable), state on a per-instance volume (instance-scoped), and config under version control (declarative). XDG keeps each on its appropriate medium; collapsing everything under XDG_CONFIG_HOME defeats this entirely.
Concrete pain point from the calling deployment: gogcli's encrypted OAuth refresh-token keyring is data (irreplaceable without a fresh OAuth flow) but lives under what is conventionally treated as config (often version-controlled, never backed up). When the operator configures their container persistence layer to back up $XDG_DATA_HOME but not $XDG_CONFIG_HOME (a reasonable choice — config is in source control, data is not), gogcli's keyring is silently lost on instance replacement.
-
Composes with multi-tool environments. gogcli isn't the only Linux CLI in many container images. If the operator already sets XDG vars at the container/image/systemd-unit level for other tools, gogcli joining the convention means zero additional config — the same env vars cover everything.
-
Better defaults on Linux. Today's ${XDG_CONFIG_HOME}/gogcli/ (often ~/.config/gogcli/) holds credentials and downloads alongside config — non-standard for Linux. Moving credentials to ~/.local/share/gogcli/ and downloads to ~/Downloads/gogcli/ aligns with where Linux users expect to find each kind.
-
Spec-conformant tools compose with system-level integrations. Backup tools, dotfile managers, restic policies, and rsync exclude lists all key off XDG paths. A tool that lumps everything under one XDG kind defeats all of them.
Backwards compatibility (must-have)
The standard pattern (adopted by gh, pnpm, yt-dlp, and many other CLIs — including the sibling mcporter per #155):
- Honor XDG when explicitly set. If
$XDG_DATA_HOME is non-empty, use $XDG_DATA_HOME/gogcli/ for data files; fall back otherwise.
- Auto-detect existing legacy paths. On first run after upgrade, if
credentials.json (or keyring, or tracking.json, etc.) exists at the legacy ${XDG_CONFIG_HOME}/gogcli/ location but not at the new kind-specific path, read from the legacy path. On the next write, write to the new path and emit a one-line stderr notice telling the operator the file moved. No silent breakage on upgrade.
- Consolidate
~/.gog/backup.json. The non-XDG ~/.gog/ root in internal/backup/config.go should migrate to $XDG_CONFIG_HOME/gogcli/backup.json with the same read-legacy-write-new fallback.
Step (1) alone is enough to fix the spec violation; (2) and (3) close the migration cleanly without forcing operators to manually move files.
Minimal implementation sketch
Extend internal/config/paths.go with kind-aware resolvers paralleling the existing Dir():
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const AppName = "gogcli"
type XdgKind int
const (
KindConfig XdgKind = iota
KindData
KindState
KindCache
)
// xdgEnvVar returns the spec env var for a given kind.
func xdgEnvVar(kind XdgKind) string {
switch kind {
case KindConfig: return "XDG_CONFIG_HOME"
case KindData: return "XDG_DATA_HOME"
case KindState: return "XDG_STATE_HOME"
case KindCache: return "XDG_CACHE_HOME"
}
return ""
}
// xdgDefault returns the spec default path for a given kind under $HOME.
func xdgDefault(kind XdgKind) string {
switch kind {
case KindConfig: return ".config"
case KindData: return filepath.Join(".local", "share")
case KindState: return filepath.Join(".local", "state")
case KindCache: return ".cache"
}
return ""
}
// kindDir returns the gogcli directory for a given XDG kind.
//
// Resolution order:
// 1. $XDG_<KIND>_HOME if set (per XDG Base Directory Spec)
// 2. $HOME/<spec-default>/gogcli (also per XDG spec)
//
// Backwards-compat fallback for callers reading existing files:
// pre-XDG-kind-aware files live under Dir() (config kind) regardless of
// their semantic kind. Callers should attempt the kind-specific path first,
// then fall back to Dir() for read-only legacy compatibility — see the
// migration helpers below.
func kindDir(kind XdgKind) (string, error) {
if xdg := strings.TrimSpace(os.Getenv(xdgEnvVar(kind))); xdg != "" {
return filepath.Join(xdg, AppName), nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("resolve user home dir: %w", err)
}
return filepath.Join(home, xdgDefault(kind), AppName), nil
}
// Dir is preserved for backwards-compat and points at the config kind.
func Dir() (string, error) { return kindDir(KindConfig) }
// DataDir returns the gogcli data root ($XDG_DATA_HOME/gogcli).
func DataDir() (string, error) { return kindDir(KindData) }
// StateDir returns the gogcli state root ($XDG_STATE_HOME/gogcli).
func StateDir() (string, error) { return kindDir(KindState) }
// CacheDir returns the gogcli cache root ($XDG_CACHE_HOME/gogcli).
func CacheDir() (string, error) { return kindDir(KindCache) }
Then update each call site to its correct kind:
// internal/config/paths.go: KeyringDir, ClientCredentialsPathFor,
// {Keep,}ServiceAccountPath, ListServiceAccountEmails — switch from Dir() to DataDir()
func KeyringDir() (string, error) { return joinUnder(DataDir, "keyring") }
func ClientCredentialsPathFor(...) (string) { return joinUnder(DataDir, "credentials-*.json") }
func KeepServiceAccountPath(...) (string) { return joinUnder(DataDir, "keep-sa-*.json") }
func ServiceAccountPath(...) (string) { return joinUnder(DataDir, "sa-*.json") }
// (with backwards-compat read fallback through legacyDir() → Dir())
// internal/config/paths.go: GmailWatchDir — switch from Dir() to StateDir()
func GmailWatchDir() (string, error) {
sd, err := StateDir()
if err != nil { return "", err }
return filepath.Join(sd, "gmail-watch"), nil
}
// internal/tracking/config.go: ConfigPath — switch from config.Dir() to config.StateDir()
func ConfigPath() (string, error) {
sd, err := config.StateDir()
if err != nil { return "", err }
return filepath.Join(sd, "tracking.json"), nil
}
// legacyConfigPath() already exists for backwards-compat reads — keep + extend.
// internal/cmd/backup_gmail.go: 4 os.UserCacheDir() sites — switch to config.CacheDir()
// (no more bare os.UserCacheDir(); routes through the kind-aware resolver
// so any future override mechanism applies uniformly)
dir, err := config.CacheDir() // was: os.UserCacheDir()
// internal/config/paths.go: DriveDownloadsDir, GmailAttachmentsDir
// — switch to a new downloadsDir() resolver that honors $XDG_DOWNLOAD_DIR
// (parsed from $XDG_CONFIG_HOME/user-dirs.dirs on Linux, fall back to $HOME/Downloads)
func DriveDownloadsDir() (string, error) { return joinUnder(downloadsDir, "drive") }
func GmailAttachmentsDir() (string, error) { return joinUnder(downloadsDir, "gmail-attachments") }
// internal/backup/config.go: defaultConfigPath — switch from ~/.gog/backup.json to
// config.Dir() + "/backup.json", with read-legacy-write-new fallback.
func defaultConfigPath() (string, error) {
cd, err := config.Dir()
if err != nil { return "", err }
return filepath.Join(cd, "backup.json"), nil
}
Each updated reader should follow the read-legacy-write-new compat pattern:
// Read primary, fall back to legacy, then migrate on write
data, err := os.ReadFile(newPath)
if errors.Is(err, fs.ErrNotExist) {
data, err = os.ReadFile(legacyPath)
if err == nil {
// notify operator + return data; next write goes to newPath
fmt.Fprintf(os.Stderr, "note: read %s from legacy path; will migrate on next write\n", base)
}
}
Use case
Concrete deployment context: gogcli running inside a containerized agent host (Cloudflare Sandbox in this case, but the shape generalizes to any container/sandbox runtime). Per-tenant state — including OAuth refresh tokens for Google services the agent has authenticated against — must persist across container replacement via an external sync mechanism (rclone to object storage in this case). The container entrypoint exports XDG vars pointing into the persisted root:
XDG_CONFIG_HOME=/persist
XDG_DATA_HOME=/persist
XDG_STATE_HOME=/persist
XDG_CACHE_HOME=/persist/cache (or tmpfs, depending on policy)
With today's gogcli (only XDG_CONFIG_HOME is read), the operator must collapse the four kinds into one. With this change, the operator can route each kind to its appropriate storage tier — backed-up volumes for data, tmpfs for cache, etc. — without the XDG-conflation workaround.
This is the same shape of use case behind mcporter#155, and the resolution there (mcporter v0.10.0) closed the gap on mcporter's side. Closing it on gogcli's side completes the spec-conformance pair across the two sibling tools.
Related references
Sibling-project precedent:
openclaw/mcporter#155 ("Honor XDG Base Directory Spec for config, vault, cache, and daemon paths") — closed as completed, shipped in mcporter v0.10.0. Same spec, same framing, same conclusion: split state by XDG kind, with read-legacy-write-new compatibility for existing users. This issue applies the same pattern to gogcli.
Related (complementary, not alternative):
- gogcli#622 — operator-side
GOG_HOME and per-kind GOG_*_DIR env-var overrides above the XDG layer. Fixes the operator-ergonomics gap (XDG-hostile sandboxes); this issue fixes the semantic-kind-mapping gap. Both should land; sequencing this one first preserves backwards-compat the cleanest because the per-kind resolvers exist before the override layer can route them.
Upstream OpenClaw issue this complements:
openclaw/openclaw#84854 — OpenClaw's host-env sanitizer strips XDG_CONFIG_HOME and XDG_CONFIG_DIRS from subprocess env, breaking inheritance for XDG-aware tools. Not blocking this proposal (the gogcli-side fix here is correct regardless), but the two together close the inheritance + producer side of the XDG-conformance story.
XDG Base Directory Specification:
gogcli source code referenced (all at tag v0.17.0):
internal/config/paths.go — central path-resolution: Dir(), KeyringDir(), ClientCredentialsPathFor(), DriveDownloadsDir(), GmailAttachmentsDir(), GmailWatchDir(), KeepServiceAccountPath(), ServiceAccountPath(), ListServiceAccountEmails().
internal/tracking/config.go — ConfigPath(), legacyConfigPath() (legacy-fallback pattern already in use here for read-only — proposal extends it across the rest of the file inventory).
internal/cmd/backup_gmail.go — 4 direct os.UserCacheDir() call sites that bypass the central resolver.
internal/backup/config.go — defaultConfigPath() uses ~/.gog/backup.json (non-XDG ~/.gog/ root) — pre-existing inconsistency to consolidate in this change.
Summary
gogcli's path resolution today follows
$XDG_CONFIG_HOMEonly — every persisted file lands under${XDG_CONFIG_HOME}/gogcli/regardless of whether it's config, durable user data (OAuth tokens, keyring, service-account keys), mutable runtime state, regenerable cache, or bulk downloads. This conflates five distinct XDG kinds into one directory. Proposal: honor the freedesktop.org XDG Base Directory Spec per kind. When the relevant XDG env var is set, use it (e.g.$XDG_DATA_HOME/gogcli/for credentials); when unset, fall back to the spec default for that kind (~/.local/share/gogcli/for data,~/.local/state/gogcli/for state,~/.cache/gogcli/for cache,~/.config/gogcli/for config — never the current config-dir-for-everything layout). Today's${XDG_CONFIG_HOME}/gogcli/and~/.gog/paths are read-compat fallbacks for existing files only, per the read-legacy-write-new pattern in "Backwards compatibility" below — new installs go straight to the kind-correct defaults.Mirrors the resolution sibling project
openclaw/mcporter#155shipped in mcporterv0.10.0.What's actually written under
${XDG_CONFIG_HOME}/gogcli/todaySource audit at gogcli tag
v0.17.0. Every path below derives fromconfig.Dir()ininternal/config/paths.go, which itself resolves only viaXDG_CONFIG_HOME(oros.UserConfigDir()as fallback). Categorical XDG kinds (per the spec) determine the right env var for each:credentials.json,credentials-<client>.json(OAuth client metadata + refresh-token references)ClientCredentialsPathFor()inpaths.go$XDG_DATA_HOME/gogcli/credentials*.jsonkeyring/(encrypted OAuth refresh tokens — file backend)KeyringDir()inpaths.go$XDG_DATA_HOME/gogcli/keyring/keep-sa-<hash>.json,sa-<hash>.json(Google service-account JSON keys)KeepServiceAccountPath(),ServiceAccountPath()inpaths.go$XDG_DATA_HOME/gogcli/{keep-sa,sa}-*.jsontracking.json(mutable per-account tracking config — rotating keys, version counters)ConfigPath()ininternal/tracking/config.go$XDG_STATE_HOME/gogcli/tracking.jsonstate/gmail-watch/(per-account Gmail watch cursors)GmailWatchDir()inpaths.go$XDG_STATE_HOME/gogcli/gmail-watch/drive-downloads/(bulk Google Drive file downloads)DriveDownloadsDir()inpaths.go$XDG_DOWNLOAD_DIR/gogcli/drive/(with fallback)gmail-attachments/(bulk Gmail attachment downloads)GmailAttachmentsDir()inpaths.go$XDG_DOWNLOAD_DIR/gogcli/gmail-attachments/(with fallback)<cache>/gog/gmail-*/...(Gmail backup intermediate cache — 4 sites inbackup_gmail.go)backup_gmail.go(directos.UserCacheDir()calls)$XDG_CACHE_HOME/gogcli/gmail-backup/~/.gog/backup.json(backup config — separate root, bypasses XDG entirely)defaultConfigPath()ininternal/backup/config.go$XDG_CONFIG_HOME/gogcli/backup.jsonNote on
os.UserCacheDir()inbackup_gmail.go: Go's stdlib already resolvesos.UserCacheDir()via$XDG_CACHE_HOMEon Linux, so the four cache sites accidentally honor XDG today — but they bypass any future tool-specific override mechanism (e.g.GOG_HOME— see related feature request), and the path is$XDG_CACHE_HOME/gog/(note:gog, notgogcli!) because that's whatever the bareos.UserCacheDir()returns joined with whatevercmdcode appends. Inconsistent with theAppName = "gogcli"constant used everywhere else.Note on
backup.jsonat~/.gog/(defaultConfigPath()ininternal/backup/config.go): a second top-level dotfile root that bypasses XDG entirely and usesgog(notgogcli) as the directory name. Pre-existing inconsistency worth aligning in the same change.This codebase already has the right patterns — just not applied consistently
Two existing patterns make the proposed change consistent with current code, not foreign to it:
gogcli already honors
XDG_CONFIG_HOMEfor the main config dir (config.Dir()) and the tracking config (tracking.ConfigPath()). The proposal is to extend the kind-aware pattern to the other XDG vars, not to bolt on XDG from scratch.AppName = "gogcli"is already centralized (paths.go) and reused across every path-resolution function ininternal/config/. AddingDataDir(),StateDir(),CacheDir(),DownloadsDir()siblings to the existingDir()reuses the same naming and the sameAppNameconstant.Why XDG over a single bespoke
GOG_HOMEA separate (related) feature request is planned for
GOG_HOMEand per-kindGOG_*_DIRenv vars as tool-specific path overrides — aMCPORTER_CONFIG-style escape hatch above the XDG layer. That request and this one are complementary, not alternatives:GOG_HOME(the other proposal) adds a tool-specific override above the XDG layer for environments where XDG resolution is unavailable or undesirable (sandboxes that strip XDG vars, CI runners, etc.).Both are valuable because they fix different problems. This proposal should land first because it's the semantic correctness fix;
GOG_HOMEis the operator-ergonomics addition on top.The spec rationale itself:
Standardized. Every Linux operator already knows what the XDG vars mean — no new tool-specific env var to discover and document.
Categorical separation matters in deployments. Per the spec:
XDG_CONFIG_HOME~/.configXDG_DATA_HOME~/.local/sharecredentials*.json,keyring/, service-account JSON keysXDG_STATE_HOME~/.local/statetracking.json,gmail-watch/cursorsXDG_CACHE_HOME~/.cacheOperators routinely place cache on tmpfs/local-SSD (regenerable, fast loss OK), data on backed-up volumes (irreplaceable), state on a per-instance volume (instance-scoped), and config under version control (declarative). XDG keeps each on its appropriate medium; collapsing everything under
XDG_CONFIG_HOMEdefeats this entirely.Concrete pain point from the calling deployment: gogcli's encrypted OAuth refresh-token keyring is data (irreplaceable without a fresh OAuth flow) but lives under what is conventionally treated as config (often version-controlled, never backed up). When the operator configures their container persistence layer to back up
$XDG_DATA_HOMEbut not$XDG_CONFIG_HOME(a reasonable choice — config is in source control, data is not), gogcli's keyring is silently lost on instance replacement.Composes with multi-tool environments. gogcli isn't the only Linux CLI in many container images. If the operator already sets XDG vars at the container/image/systemd-unit level for other tools, gogcli joining the convention means zero additional config — the same env vars cover everything.
Better defaults on Linux. Today's
${XDG_CONFIG_HOME}/gogcli/(often~/.config/gogcli/) holds credentials and downloads alongside config — non-standard for Linux. Moving credentials to~/.local/share/gogcli/and downloads to~/Downloads/gogcli/aligns with where Linux users expect to find each kind.Spec-conformant tools compose with system-level integrations. Backup tools, dotfile managers, restic policies, and rsync exclude lists all key off XDG paths. A tool that lumps everything under one XDG kind defeats all of them.
Backwards compatibility (must-have)
The standard pattern (adopted by
gh,pnpm,yt-dlp, and many other CLIs — including the siblingmcporterper#155):$XDG_DATA_HOMEis non-empty, use$XDG_DATA_HOME/gogcli/for data files; fall back otherwise.credentials.json(or keyring, or tracking.json, etc.) exists at the legacy${XDG_CONFIG_HOME}/gogcli/location but not at the new kind-specific path, read from the legacy path. On the next write, write to the new path and emit a one-line stderr notice telling the operator the file moved. No silent breakage on upgrade.~/.gog/backup.json. The non-XDG~/.gog/root ininternal/backup/config.goshould migrate to$XDG_CONFIG_HOME/gogcli/backup.jsonwith the same read-legacy-write-new fallback.Step (1) alone is enough to fix the spec violation; (2) and (3) close the migration cleanly without forcing operators to manually move files.
Minimal implementation sketch
Extend
internal/config/paths.gowith kind-aware resolvers paralleling the existingDir():Then update each call site to its correct kind:
Each updated reader should follow the read-legacy-write-new compat pattern:
Use case
Concrete deployment context: gogcli running inside a containerized agent host (Cloudflare Sandbox in this case, but the shape generalizes to any container/sandbox runtime). Per-tenant state — including OAuth refresh tokens for Google services the agent has authenticated against — must persist across container replacement via an external sync mechanism (rclone to object storage in this case). The container entrypoint exports XDG vars pointing into the persisted root:
XDG_CONFIG_HOME=/persistXDG_DATA_HOME=/persistXDG_STATE_HOME=/persistXDG_CACHE_HOME=/persist/cache(or tmpfs, depending on policy)With today's gogcli (only
XDG_CONFIG_HOMEis read), the operator must collapse the four kinds into one. With this change, the operator can route each kind to its appropriate storage tier — backed-up volumes for data, tmpfs for cache, etc. — without the XDG-conflation workaround.This is the same shape of use case behind mcporter#155, and the resolution there (mcporter
v0.10.0) closed the gap on mcporter's side. Closing it on gogcli's side completes the spec-conformance pair across the two sibling tools.Related references
Sibling-project precedent:
openclaw/mcporter#155("Honor XDG Base Directory Spec for config, vault, cache, and daemon paths") — closed as completed, shipped in mcporterv0.10.0. Same spec, same framing, same conclusion: split state by XDG kind, with read-legacy-write-new compatibility for existing users. This issue applies the same pattern to gogcli.Related (complementary, not alternative):
GOG_HOMEand per-kindGOG_*_DIRenv-var overrides above the XDG layer. Fixes the operator-ergonomics gap (XDG-hostile sandboxes); this issue fixes the semantic-kind-mapping gap. Both should land; sequencing this one first preserves backwards-compat the cleanest because the per-kind resolvers exist before the override layer can route them.Upstream OpenClaw issue this complements:
openclaw/openclaw#84854— OpenClaw's host-env sanitizer stripsXDG_CONFIG_HOMEandXDG_CONFIG_DIRSfrom subprocess env, breaking inheritance for XDG-aware tools. Not blocking this proposal (the gogcli-side fix here is correct regardless), but the two together close the inheritance + producer side of the XDG-conformance story.XDG Base Directory Specification:
user-dirs.dirsandXDG_DOWNLOAD_DIR— the spec-conformant way to discover the user's Downloads dir, relevant for the bulk-download path resolution above.gogcli source code referenced (all at tag
v0.17.0):internal/config/paths.go— central path-resolution:Dir(),KeyringDir(),ClientCredentialsPathFor(),DriveDownloadsDir(),GmailAttachmentsDir(),GmailWatchDir(),KeepServiceAccountPath(),ServiceAccountPath(),ListServiceAccountEmails().internal/tracking/config.go—ConfigPath(),legacyConfigPath()(legacy-fallback pattern already in use here for read-only — proposal extends it across the rest of the file inventory).internal/cmd/backup_gmail.go— 4 directos.UserCacheDir()call sites that bypass the central resolver.internal/backup/config.go—defaultConfigPath()uses~/.gog/backup.json(non-XDG~/.gog/root) — pre-existing inconsistency to consolidate in this change.