From c5b0586dd4c15b167c9e8ed9c74fbb9e5ede0017 Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sat, 14 Mar 2026 17:11:27 +0530 Subject: [PATCH 1/7] fix: readonly scope enforcement (#168) and add admin-directory (#430) --- Cargo.lock | 823 +++++++------------------------------ Cargo.toml | 3 +- all_issues.json | 1 + all_prs.json | 1 + analysis_results.json | 256 ++++++++++++ deep_analysis_results.json | 151 +++++++ issues.json | 1 + merged_prs.json | 1 + open_prs.json | 1 + src/auth_commands.rs | 21 +- src/fs_util.rs | 46 ++- src/services.rs | 6 + src/setup_tui.rs | 1 + 13 files changed, 624 insertions(+), 688 deletions(-) create mode 100644 all_issues.json create mode 100644 all_prs.json create mode 100644 analysis_results.json create mode 100644 deep_analysis_results.json create mode 100644 issues.json create mode 100644 merged_prs.json create mode 100644 open_prs.json diff --git a/Cargo.lock b/Cargo.lock index 0d55ea8d..91051795 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,16 +125,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "atomic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" -dependencies = [ - "bytemuck", + "syn", ] [[package]] @@ -155,27 +146,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.11.0" @@ -197,12 +167,6 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - [[package]] name = "byteorder" version = "1.5.0" @@ -215,6 +179,12 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "castaway" version = "0.2.4" @@ -226,9 +196,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -266,7 +236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", - "phf 0.12.1", + "phf", ] [[package]] @@ -310,7 +280,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -327,9 +297,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" -version = "0.9.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -398,19 +368,35 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", - "rustix", + "rustix 1.1.4", "signal-hook", "signal-hook-mio", "winapi", @@ -436,16 +422,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "csscolorparser" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" -dependencies = [ - "lab", - "phf 0.11.3", -] - [[package]] name = "ctr" version = "0.9.2" @@ -461,18 +437,8 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "darling_core", + "darling_macro", ] [[package]] @@ -486,20 +452,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "syn", ] [[package]] @@ -508,36 +461,19 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.11", + "darling_core", "quote", - "syn 2.0.117", + "syn", ] -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "deltae" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" - [[package]] name = "deranged" -version = "0.5.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", - "serde_core", + "serde", ] [[package]] @@ -555,10 +491,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.20.11", + "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -568,7 +504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", + "syn", ] [[package]] @@ -590,7 +526,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn", ] [[package]] @@ -632,7 +568,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -672,60 +608,18 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "euclid" -version = "0.22.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set", - "regex", -] - [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "filedescriptor" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "finl_unicode" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fnv" version = "1.0.7" @@ -738,12 +632,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -793,7 +681,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -896,7 +784,7 @@ dependencies = [ "chrono", "chrono-tz", "clap", - "crossterm", + "crossterm 0.29.0", "derive_builder", "dirs", "dotenvy", @@ -949,7 +837,9 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash 0.1.5", + "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -957,11 +847,6 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] [[package]] name = "heck" @@ -969,12 +854,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hostname" version = "0.4.2" @@ -1264,15 +1143,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.11" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" dependencies = [ - "darling 0.23.0", + "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1299,9 +1178,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.14.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1322,17 +1201,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kasuari" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" -dependencies = [ - "hashbrown 0.16.1", - "portable-atomic", - "thiserror 2.0.18", -] - [[package]] name = "keyring" version = "3.6.3" @@ -1347,12 +1215,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "lab" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" - [[package]] name = "lazy_static" version = "1.5.0" @@ -1381,13 +1243,10 @@ dependencies = [ ] [[package]] -name = "line-clipping" -version = "0.3.5" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" -dependencies = [ - "bitflags 2.11.0", -] +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" @@ -1424,11 +1283,11 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.15.5", ] [[package]] @@ -1437,16 +1296,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "mac_address" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" -dependencies = [ - "nix", - "winapi", -] - [[package]] name = "matchers" version = "0.2.0" @@ -1462,27 +1311,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "memmem" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "mio" version = "1.1.1" @@ -1495,29 +1323,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1529,20 +1334,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[package]] -name = "num-derive" -version = "0.4.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" @@ -1592,15 +1386,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -1625,63 +1410,16 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.6" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pest_meta" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" -dependencies = [ - "pest", - "sha2", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "phf" -version = "0.11.3" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared 0.11.3", -] +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -1689,49 +1427,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ - "phf_shared 0.12.1", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", + "phf_shared", ] [[package]] @@ -1767,12 +1463,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - [[package]] name = "potential_utf" version = "0.1.4" @@ -1804,7 +1494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -1953,87 +1643,23 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" -dependencies = [ - "instability", - "ratatui-core", - "ratatui-crossterm", - "ratatui-macros", - "ratatui-termwiz", - "ratatui-widgets", -] - -[[package]] -name = "ratatui-core" -version = "0.1.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.11.0", + "bitflags", + "cassowary", "compact_str", - "hashbrown 0.16.1", + "crossterm 0.28.1", "indoc", + "instability", "itertools", - "kasuari", "lru", + "paste", "strum", - "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "ratatui-crossterm" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" -dependencies = [ - "cfg-if", - "crossterm", - "instability", - "ratatui-core", -] - -[[package]] -name = "ratatui-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" -dependencies = [ - "ratatui-core", - "ratatui-widgets", -] - -[[package]] -name = "ratatui-termwiz" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" -dependencies = [ - "ratatui-core", - "termwiz", -] - -[[package]] -name = "ratatui-widgets" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.16.1", - "indoc", - "instability", - "itertools", - "line-clipping", - "ratatui-core", - "strum", - "time", - "unicode-segmentation", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -2042,7 +1668,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -2056,18 +1682,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.4.14" @@ -2155,16 +1769,29 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -2269,7 +1896,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.11.0", + "bitflags", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -2282,7 +1909,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2332,7 +1959,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2396,7 +2023,7 @@ checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2504,23 +2131,24 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "rustversion", + "syn", ] [[package]] @@ -2529,17 +2157,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -2568,7 +2185,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2580,73 +2197,10 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] -[[package]] -name = "terminfo" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" -dependencies = [ - "fnv", - "nom", - "phf 0.11.3", - "phf_codegen", -] - -[[package]] -name = "termios" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" -dependencies = [ - "libc", -] - -[[package]] -name = "termwiz" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" -dependencies = [ - "anyhow", - "base64", - "bitflags 2.11.0", - "fancy-regex", - "filedescriptor", - "finl_unicode", - "fixedbitset", - "hex", - "lazy_static", - "libc", - "log", - "memmem", - "nix", - "num-derive", - "num-traits", - "ordered-float", - "pest", - "pest_derive", - "phf 0.11.3", - "sha2", - "signal-hook", - "siphasher", - "terminfo", - "termios", - "thiserror 1.0.69", - "ucd-trie", - "unicode-segmentation", - "vtparse", - "wezterm-bidi", - "wezterm-blob-leases", - "wezterm-color-types", - "wezterm-dynamic", - "wezterm-input-types", - "winapi", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -2673,7 +2227,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2684,7 +2238,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2698,9 +2252,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -2708,22 +2262,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde_core", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -2779,7 +2333,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2826,7 +2380,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags", "bytes", "futures-util", "http", @@ -2881,7 +2435,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2948,12 +2502,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -2968,20 +2516,26 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" -version = "2.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "unicode-width" -version = "0.2.2" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" @@ -3035,18 +2589,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" -dependencies = [ - "atomic", - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" @@ -3059,15 +2601,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vtparse" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" -dependencies = [ - "utf8parse", -] - [[package]] name = "want" version = "0.3.1" @@ -3147,7 +2680,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -3201,7 +2734,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -3227,78 +2760,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "wezterm-bidi" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" -dependencies = [ - "log", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-blob-leases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" -dependencies = [ - "getrandom 0.3.4", - "mac_address", - "sha2", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "wezterm-color-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" -dependencies = [ - "csscolorparser", - "deltae", - "lazy_static", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-dynamic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" -dependencies = [ - "log", - "ordered-float", - "strsim", - "thiserror 1.0.69", - "wezterm-dynamic-derive", -] - -[[package]] -name = "wezterm-dynamic-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "wezterm-input-types" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" -dependencies = [ - "bitflags 1.3.2", - "euclid", - "lazy_static", - "serde", - "wezterm-dynamic", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3342,7 +2803,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3353,7 +2814,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3632,7 +3093,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3648,7 +3109,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3660,7 +3121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags", "indexmap", "log", "serde", @@ -3715,7 +3176,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3761,7 +3222,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3781,7 +3242,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3802,7 +3263,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3835,7 +3296,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 24bc253b..b61b9694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ name = "gws" version = "0.16.0" edition = "2021" +rust-version = "1.87.0" description = "Google Workspace CLI — dynamic command surface from Discovery Service" license = "Apache-2.0" repository = "https://github.com/googleworkspace/cli" @@ -51,7 +52,7 @@ tokio-util = { version = "0.7", features = ["io"] } bytes = "1" base64 = "0.22.1" derive_builder = "0.20.2" -ratatui = "0.30.0" +ratatui = "0.29.0" crossterm = "0.29.0" chrono = "0.4.44" chrono-tz = "0.10" diff --git a/all_issues.json b/all_issues.json new file mode 100644 index 00000000..9d817f30 --- /dev/null +++ b/all_issues.json @@ -0,0 +1 @@ +[{"assignees":[],"body":"## Summary\n\nThe Admin SDK Directory API (`admin:directory_v1`) is one of the most commonly used Google Workspace APIs for managing users, groups, and organizational units. Currently, accessing it through `gws` requires using the `admin-reports` alias with a version override:\n\n```bash\ngws admin-reports --api-version directory_v1 groups list --params '{\"domain\":\"example.com\"}'\n```\n\nThis works but is unintuitive — `admin-reports` implies audit/usage reporting, not user/group management. A built-in `admin-directory` alias would make the CLI more discoverable and self-documenting.\n\n## Proposed Change\n\nAdd a new `ServiceEntry` to `src/services.rs`:\n\n```rust\nServiceEntry {\n aliases: &[\"admin-directory\"],\n api_name: \"admin\",\n version: \"directory_v1\",\n description: \"Manage users, groups, and organizational units\",\n},\n```\n\nThis would enable:\n\n```bash\ngws admin-directory groups list --params '{\"domain\":\"example.com\"}'\ngws admin-directory members list --params '{\"groupKey\":\"group@example.com\"}'\ngws admin-directory users get --params '{\"userKey\":\"user@example.com\"}'\n```\n\n## Context\n\n- The `admin-reports` alias already maps to `admin:reports_v1` — same API name, different version\n- The `--api-version directory_v1` workaround works correctly today\n- Adding this alias is backward-compatible (no existing aliases change)\n- The Directory API is core to Workspace administration and used heavily for RBAC, onboarding/offboarding, and compliance workflows","labels":[],"number":473,"title":"Add admin-directory as a built-in service alias for Admin SDK Directory API","updatedAt":"2026-03-13T16:17:37Z"},{"assignees":[],"body":"Hi I have defined to auto-add hangout in the ui, yet when creating a calendar event with the CLI it doesn't add the hangout. \nIs it possible to add support for respecting the parameter in the UI? Or at least provide the option in the CLI to add a Google Meet hangout. \nThanks! ","labels":[],"number":461,"title":"Calendar event is created without Hangout","updatedAt":"2026-03-13T09:20:24Z"},{"assignees":[],"body":"I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back.\n\nRelease 0.7.0 says that multiple accounts were removed. With no reason why.\n\nMultiple accounts can be handy but ESPECIALLY in the age of AI agents. I need to be able to assign my agent account (created in Google) to be able to take actions. And by using the agent account credentials, it becomes auditable. \n\nAnd access can be switched off at a moments notice.\n\nI would say that being able to have multiple agents is a security concern, because it enables proper separation of permissions.\n\nPlease bring it back","labels":[],"number":439,"title":"Please restore multiple accounts being able to be used","updatedAt":"2026-03-13T00:31:55Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent needs to read the content of Gmail messages (e.g., to triage an inbox, summarize emails, or decide on actions), it must use the raw API:\n\n```bash\ngws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"...\", \"format\": \"full\"}'\n```\n\nThis returns the entire message payload including:\n- All email headers (Received, DKIM-Signature, ARC chains, SPF, etc.)\n- Base64-encoded MIME parts\n- Multipart boundaries and nested structure\n- Inline image attachments\n\nFor a typical email, this produces 10–60KB of JSON, most of which is noise. The agent then has to locate the correct MIME part, base64-decode it, and extract readable text — all of which is error-prone and wastes context window.\n\nBy contrast, `+triage` cleanly abstracts listing messages, but there is no equivalent for *reading* a single message body.\n\n## Proposal\n\nAdd a `gws gmail +read` helper that extracts the human-readable body from a message:\n\n```bash\n# Read a message body as plain text\ngws gmail +read --id \n\n# Read with metadata (from, to, subject, date)\ngws gmail +read --id --headers\n\n# JSON output for agent consumption\ngws gmail +read --id --format json\n```\n\nThe helper would:\n1. Fetch the message via `users.messages.get`\n2. Walk the MIME tree to find the `text/plain` part (or fall back to `text/html` → stripped text)\n3. Base64-decode and return clean, readable content\n4. Optionally include key headers (From, To, Subject, Date, Cc)\n\n### Example JSON output\n\n```json\n{\n \"id\": \"19ce2251577a09ea\",\n \"from\": \"Jira automation \",\n \"to\": \"gvilches@squarespace.com\",\n \"cc\": \"bkimmel@squarespace.com, tlong@squarespace.com\",\n \"subject\": \"[ACTION REQUIRED] Your COE violates 14-Day Incident SLA\",\n \"date\": \"Thu, 12 Mar 2026 13:03:25 +0000\",\n \"body\": \"George Vilches,\\n\\nAs the current Assignee for the Incident ticket COE-5367...\"\n}\n```\n\n## Why this matters for agents\n\n`+triage` answers \"what's in my inbox?\" but agents immediately need to follow up with \"what does this email say?\" to make decisions (e.g., does this need a reply? is this actionable?). Today that requires a raw API call that returns massive payloads full of transport headers and encoded content. This is exactly the kind of complexity that helpers are designed to abstract — similar to how `+send` abstracts RFC 2822 encoding.\n\nThe helpers README states helpers should be added when they \"abstract away significant complexity\" — MIME tree walking and base64 decoding for readable text extraction qualifies.","labels":[],"number":438,"title":"Feature request: Gmail +read helper for extracting message body as plain text","updatedAt":"2026-03-12T18:15:41Z"},{"assignees":[],"body":"Hi, \nAfter upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory API on my GCP project and on my auth scopes\n\n\"Image\"","labels":[],"number":430,"title":"Missing admin directory SDK on the newest version?","updatedAt":"2026-03-14T11:13:10Z"},{"assignees":[],"body":"Please consider adding a Gmail draft-only mode/policy to `gws`.\n\nDesired behavior:\n- allow reading Gmail as configured\n- allow creating/updating drafts\n- block all send paths\n\nIn particular, this mode should prevent:\n- `users.messages.send`\n- `users.drafts.send`\n- reply / reply-all / forward actions that result in sending\n- any equivalent helper/tool aliases\n\nA common agent workflow is: let the agent prepare Gmail drafts for human review, but guarantee that it cannot actually send mail.\n\nThat “draft yes, send no” boundary would be very useful for agent integrations.\n\nNotes:\n\nI understand this may depend on Gmail/OAuth/API limitations. If true Google-enforced draft-only permission is not currently possible, a `gws`-side policy/tool filter would still be very valuable.\n\nIf this request belongs in another repo/team, please feel free to redirect it.","labels":[],"number":424,"title":"Feature request: Gmail draft-only mode for CLI+agent workflows","updatedAt":"2026-03-12T06:28:52Z"},{"assignees":[],"body":"## Description\n\n`gws` commands fail in environments requiring HTTP proxy (e.g., users in China who need proxy to access Google APIs).\n\n## Problem\n\nThe underlying `yup-oauth2` and `hyper` HTTP clients don't respect standard proxy environment variables (`http_proxy`, `https_proxy`, `ALL_PROXY`).\n\n**Affected operations:**\n1. `gws auth login` - OAuth token exchange fails\n2. All API calls - token refresh fails\n\n**Error message:**\n```\n{\n \"error\": {\n \"code\": 401,\n \"message\": \"Authentication failed: Failed to get token: Connection failure: Hyper error: client error (Connect): tcp connect error: Operation timed out (os error 60)\",\n \"reason\": \"authError\"\n }\n}\n```\n\n## Environment\n- macOS 14.x (Sonoma)\n- gws 0.11.1\n- Proxy: http://127.0.0.1:7890\n\n## Current Workaround\n\n1. Manually exchange OAuth code using curl with proxy\n2. Save credentials to `~/.config/gws/credentials.json`\n3. Use `GOOGLE_WORKSPACE_CLI_TOKEN` env var with manually refreshed access token\n\nThis is cumbersome as tokens expire every hour.\n\n## Suggested Fix\n\n**Option 1:** Use `reqwest` with default features (includes proxy support):\n```toml\n# Cargo.toml - current\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\"], default-features = false }\n\n# Suggested - enable proxy support\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\", \"socks\"] }\n```\n\n**Option 2:** Pass a custom `hyper` client with proxy support to `yup-oauth2` using `.with_hyper_client()`.\n\n**Option 3:** Add explicit `--proxy` flag or `GWS_PROXY` / `GWS_HTTPS_PROXY` environment variable.\n\n## References\n- reqwest proxy support: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html\n- yup-oauth2 custom client support","labels":[],"number":422,"title":"Support HTTP proxy environment variables (http_proxy/https_proxy)","updatedAt":"2026-03-12T04:31:47Z"},{"assignees":[],"body":"## Problem\n\n`SKILL.md` files are auto-generated by `gws generate-skills` and contain API method listings. However, some services have non-obvious usage patterns that require hand-written guidance — particularly around:\n\n- **Google Docs tabs**: reading specific tabs (`includeTabsContent`), creating tabs (`addDocumentTab` vs the invalid `createTab`), writing to specific tabs (`tabId` in `location`/`range`)\n- **Formatted content insertion**: the insert-then-style pattern with `insertText` + `updateParagraphStyle`\n- **`+write` limitations**: no tab targeting, plain text only\n\nThese patterns are hard to discover from `gws schema` output alone and typically require trial-and-error.\n\n## Proposal\n\nAdd a `COOKBOOK.md` file alongside `SKILL.md` in skill directories that need hand-written recipes:\n\n\\`\\`\\`\nskills/gws-docs/\n SKILL.md ← auto-generated (unchanged)\n COOKBOOK.md ← hand-written recipes (new)\n\\`\\`\\`\n\nBenefits:\n- **Safe from regeneration** — `gws generate-skills` won't overwrite it\n- **Composable** — other skills can adopt the same pattern as needed\n- **Discoverable** — agents can check for COOKBOOK.md alongside SKILL.md\n\nOptional follow-up: teach `generate_skills.rs` to append a \"See also: COOKBOOK.md\" link when one exists.\n\n## Scope\n\nStarting with `gws-docs` as a proof of concept. The cookbook covers:\n1. Tab CRUD operations (read, create, write, rename, delete)\n2. Formatted content insertion (headings, bold/italic, paragraph styles)\n3. Valid `batchUpdate` request types reference table\n4. `+write` helper limitations and workarounds","labels":[],"number":420,"title":"docs(skills): add COOKBOOK.md pattern for hand-written recipes alongside generated SKILL.md","updatedAt":"2026-03-12T02:00:15Z"},{"assignees":[],"body":"Implement the ability to send meeting invites with video conferencing details.","labels":[],"number":419,"title":"Feature: Add video conferencing to meeting insert","updatedAt":"2026-03-11T22:49:33Z"},{"assignees":[],"body":"Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: \nsrc/fs_util.rs\n, \nsrc/credential_store.rs\n, \nsrc/oauth_config.rs\n\nDetails: The \ngws\n CLI uses \natomic_write\n and \natomic_write_async\n in \nsrc/fs_util.rs\n to persist sensitive information like client_secret.json and encrypted OAuth tokens (credentials.enc).\n\nCurrently, these functions write the data to a temporary file (e.g., client_secret.json.tmp) using the default system umask (often 0644 or 0666), and then rename it into place. The permissions are only tightened to 0600 after the file has been renamed (e.g., in oauth_config.rs:88).\n\nImpact: A local attacker running a script that watches the ~/.config/gws/ directory via inotify can open the temporary file or the renamed file before the chmod 0o600 is applied. This allows a low-privileged user on a shared system to exfiltrate the victim's Google Workspace plaintext OAuth client secrets or encrypted credential material.\n\nProposed Fix: The temporary file must be created with secure 0o600 permissions atomically at creation time using std::os::unix::fs::OpenOptionsExt, completely eliminating the TOCTOU window. I have a patch ready to submit","labels":[],"number":401,"title":"TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage","updatedAt":"2026-03-11T11:04:41Z"},{"assignees":[],"body":"## Summary\n\nThe repository currently has no issue templates (only a PR template). As the project grows, issue templates would help contributors file consistent, actionable reports for bugs, feature requests, and docs improvements — reducing back-and-forth for maintainers.\n\n## Suggested templates\n\n- **Bug report** — reproduction steps, expected vs actual behavior, OS/version info\n- **Feature request** — problem description, proposed solution\n- **Docs improvement** — which doc, what's missing or wrong\n\nThese could live in `.github/ISSUE_TEMPLATE/` as standard GitHub issue forms (YAML) or plain markdown templates.\n\n## Bonus: structured input for AI agents\n\nGiven that this project explicitly targets AI agents as users, consistent issue structure also creates a reliable input format for automation. Skills could be built around issue triage, reproduction, or even implementation — but only if issues follow a predictable schema. Issue templates are a low-effort way to enable that.","labels":[],"number":400,"title":"meta: add GitHub issue templates","updatedAt":"2026-03-11T10:15:47Z"},{"assignees":[],"body":"## Summary\n\nExpose a focused lib.rs so gws can be used as a Rust library dependency for programmatic access to Google Workspace APIs. This enables tools like CI integrations and custom automation to reuse the core logic without shelling out to the binary.\n\nRelated: #211, #376\n\n## Design Principles\n\n1. **Minimal surface area** — only expose modules that are stable and useful outside the CLI\n2. **No CLI coupling** — library consumers should not need clap, ratatui, or interactive OAuth flows\n3. **Facade over internals** — provide a clean execute() function rather than exposing executor.rs internals\n\n## Proposed Public API\n\n### Modules to expose (pub mod)\n\n| Module | Purpose |\n|---|---|\n| discovery | RestDescription, RestMethod, RestResource — introspect Google APIs |\n| error | GwsError — unified error type |\n| config | config_dir() — resolve config directory |\n| services | resolve_service(), SERVICES, select_scope() — service name to API mapping |\n| validate | validate_api_identifier(), validate_gcp_location() — input safety |\n| client | build_client(), send_with_retry() — HTTP with retry |\n\n### Modules to keep internal (pub(crate))\n\n| Module | Why |\n|---|---|\n| auth, auth_commands | CLI-specific OAuth flow (browser redirect, token caching) |\n| executor | Takes ArgMatches — CLI orchestration |\n| helpers/* | Each helper parses ArgMatches directly |\n| formatter | Presentation concern (JSON/CSV output) |\n| setup, setup_tui | Interactive TUI |\n| credential_store | Internal key storage detail |\n\n### Programmatic execution facade (future)\n\nFor users who need to execute API methods programmatically:\n\n```rust\npub async fn execute(\n service: &str,\n resource_path: &[&str], // [\"files\", \"list\"]\n method: &str, // \"list\", \"get\", \"create\"\n params: &HashMap,\n body: Option,\n auth: AuthToken,\n) -> Result\n```\n\nThis keeps CLI internals hidden while providing a clean interface. The AuthToken wrapper would accept a pre-obtained token string, decoupling from the CLI's OAuth flow.\n\n## Non-goals\n\n- Exposing the TUI or interactive setup flows\n- Guaranteeing API stability for internal modules\n- Replacing the binary — the library is a complement, not a fork\n\n## Prior art\n\n- PR #376 by @geofflittle — good first attempt but exposes too many CLI-specific modules. The SSRF validation (validate_gcp_location) from that PR is independently valuable and should be extracted.\n- Issue #211 — original feature request","labels":[],"number":386,"title":"feat: expose minimal library crate for programmatic API access","updatedAt":"2026-03-10T20:11:26Z"},{"assignees":[],"body":"This package contains a lot of skills. \n\nConsider implementing support for a skill.json file to make them more manageable and discoverable.\n\nsee https://github.com/automaton-labs/skill.json","labels":[],"number":356,"title":"Consider making skills more discoverable with skill.json.","updatedAt":"2026-03-10T00:21:46Z"},{"assignees":[],"body":"I see the agents flail around trying to find a document in a shared drive and I often have to stop it and say \"the data is in a shared drive\". Not sure if that can be a note in the skills document to mention \"if file is not found, try `supportsAllDrives: true`\"","labels":[],"number":327,"title":"update skills document to mention shared drives (supportsAllDrives: true)","updatedAt":"2026-03-09T14:58:51Z"},{"assignees":[],"body":"## Why\n\n`gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering available services/resources/methods without referring to `--help` each time.\n\n## What\n\nAdd shell completion generation for major shells:\n- bash\n- zsh\n- fish\n\nIdeally via a subcommand like `gws completion ` that outputs the completion script to stdout, following the common CLI pattern (e.g., `gh completion`, `kubectl completion`).\n\n## How\n\n- Top-level services (`drive`, `sheets`, `gmail`, `calendar`, etc.)\n- Resources per service (`files`, `spreadsheets`, `messages`, etc.)\n- Methods per resource (`list`, `get`, `create`, `update`, `delete`, etc.)\n- Global flags (`--params`, `--json`, `--format`, `--page-all`, etc.)","labels":[],"number":323,"title":"feat: Add shell completion support (bash, zsh, fish)","updatedAt":"2026-03-09T13:24:20Z"},{"assignees":[],"body":"The OAuth scopes are not properly ordered in the list. \n\nIt looks like it is ordered by risk levels. Maybe we neeed to provide options to reorder this - product-wise, access-wise (read / write / modify / delete), and then risk-wise. \n\n\"Image\"\n\n","labels":[],"number":319,"title":"OAuth scopes are not properly ordered","updatedAt":"2026-03-09T05:42:41Z"},{"assignees":[],"body":"## Problem\n\nCurrently all error output from `gws` is raw JSON, which is hard to read in an interactive terminal session:\n\n```json\n{\"error\":{\"code\":404,\"message\":\"Not Found\",\"reason\":\"notFound\"}}\n```\n\n## Proposal\n\nDetect whether stdout/stderr is a TTY and display human-friendly colored messages when running interactively:\n\n```\n✗ notFound (404): Not Found ← red bold\n⚠️ Model Armor: injection detected ← yellow bold\n💡 API not enabled for your project ← yellow bold\n```\n\nWhen piped or redirected, keep the existing JSON output for backward compatibility.\n\n## Implementation notes\n\n- Use `std::io::IsTerminal` (stable since Rust 1.70) for TTY detection\n- `colored` crate (or alternatively `anstream`/`anstyle` which are already indirect deps via clap) for ANSI colors\n- Error output goes to stderr; JSON pipe output stays on stdout\n- Zero behavior change for scripted/piped usage","labels":[],"number":317,"title":"feat: Display colored error/warning messages in TTY terminals","updatedAt":"2026-03-09T04:43:30Z"},{"assignees":[],"body":"gws auth login \r\n \"message\": \"Authentication successful. Encrypted credentials saved.\",\r\n \"scopes\": [\r\n \"https://www.googleapis.com/auth/drive\",\r\n \"https://www.googleapis.com/auth/spreadsheets\",\r\n \"https://www.googleapis.com/auth/gmail.modify\",\r\n \"https://www.googleapis.com/auth/calendar\",\r\n \"https://www.googleapis.com/auth/documents\",\r\n \"https://www.googleapis.com/auth/presentations\",\r\n \"https://www.googleapis.com/auth/tasks\",\r\n \"https://www.googleapis.com/auth/pubsub\",\r\n \"https://www.googleapis.com/auth/cloud-platform\",\r\n \"openid\",\r\n \"https://www.googleapis.com/auth/userinfo.email\"\r\n ],\r\n \"status\": \"success\"\r\n\r\nbut \r\ngws drive files list --params '{\"pageSize\": 5}'\r\n{\r\n \"error\": {\r\n \"code\": 500,\r\n \"message\": \"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found (tried both standard and $discovery URLs)\",\r\n \"reason\": \"discoveryError\"\r\n }\r\n}\r\n\r\n_Originally posted by @kaijung in https://github.com/googleworkspace/cli/discussions/257_","labels":[],"number":314,"title":"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found","updatedAt":"2026-03-09T18:29:01Z"},{"assignees":[],"body":"I installed the cli and then ran the `gws auth setup` and it returned\n\n\"Image\"\n\nis it expected?","labels":[],"number":309,"title":"bug: Error on doing the steps in readme","updatedAt":"2026-03-08T10:39:06Z"},{"assignees":[],"body":"**Environment**\n- OS: Windows\n- Node.js: compatible with the package requirement (>=18)\n- npm: global install\n\n**What I ran**\n```\nnpm uninstall -g @googleworkspace/cli\nnpm cache clean --force\nnpm install -g @googleworkspace/cli\ngws auth setup\n```\n\n**What happened**\nThe command fails with:\n\n```\nError: spawnSync C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe ENOENT\n at Object.spawnSync (node:internal/child_process:1119:20)\n at spawnSync (node:child_process:911:24)\n at C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\binary-install.js:197:24\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {\n errno: -4058,\n code: 'ENOENT',\n syscall: 'spawnSync C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n path: 'C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n spawnargs: [ 'auth', 'setup' ]\n}\n```\n\n**Verification**\nI checked whether the expected binary exists:\n\n```\nTest-Path \"C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe\"\n```\n\nResult:\n```\nFalse\n```\n\nSo the package appears to install successfully via npm, but the actual Windows binary is missing from the location the wrapper expects.\n\n**Expected behavior**\n`npm install -g @googleworkspace/cli` should install or place `gws.exe` in the expected path so `gws auth setup` can run.\n\n**Notes**\nThis looks like a packaging/install-script issue specific to Windows, rather than a Node version issue.","labels":[],"number":306,"title":"Windows install is missing `gws.exe`, causing `gws auth setup` to fail with ENOENT","updatedAt":"2026-03-08T07:27:47Z"},{"assignees":[],"body":"```\nnpm install @googleworkspace/cli@0.7.0\nnpm error code 1\nnpm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli\nnpm error command failed\nnpm error command sh -c node ./install.js\nnpm error Your glibc isn't compatible; trying static musl binary instead\nnpm error Downloading release from https://github.com/googleworkspace/cli/releases/download/v0.7.0/gws-x86_64-unknown-linux-musl.tar.gz\nnpm error Error fetching release: spawnSync tar ENOENT\nnpm error A complete log of this run can be found in: /tmp/cache/_logs/2026-03-06T22_08_26_142Z-debug-0.log\n```\n\nThis appears to be using musl when it should be using glibc.\n\nThis was running on Amazon Linux 2023.","labels":[],"number":284,"title":"npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT","updatedAt":"2026-03-09T21:31:38Z"},{"assignees":[],"body":"Hi team — I found a UX bug while trying the CLI:\n\n## Repro\n\n```bash\ngws auth setup --help\n```\n\nOn machines without `gcloud`, this executes setup and fails with:\n\n```json\n{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n}\n```\n\nExpected behavior is to print setup help/usage and exit successfully.\n\n## Proposed fix\n\nEarly-return from `run_setup()` when `-h/--help` is present, before any setup checks.\n\nI prepared a patch in my fork/branch:\n\n- Branch: `stablegenius49/cli:fix-auth-setup-help-flag`\n- Compare URL: https://github.com/googleworkspace/cli/compare/main...stablegenius49:cli:fix-auth-setup-help-flag\n\nI also added unit tests for the help flag detection.\n\nI couldn't open a PR directly because this repo currently blocks PR creation for non-collaborators (`CreatePullRequest` permission error), so sharing via issue + compare link.\n","labels":[],"number":280,"title":"{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n} executes setup instead of showing help","updatedAt":"2026-03-06T20:16:56Z"},{"assignees":[],"body":"Hi,\n\nThanks for this project!\n\nI just tried to auth with all scopes and got\n\nSome requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of XXXXXXXXXX, see error details. Error 400: invalid_scope\n\nI'm not sure if the scope is invalid or it's because my gcloud project is missing this API or because I'm on enterprise account and do not have super admin permission.\n\n","labels":[],"number":258,"title":"Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope","updatedAt":"2026-03-13T17:42:39Z"},{"assignees":[],"body":"## Problem\n\nThere's no way to send a Gmail message with an attachment using `gws`. Two approaches fail:\n\n### 1. `--json` with base64url-encoded raw MIME — hits OS argument length limit\n\nThe documented approach for sending raw MIME (with `threadId`, `In-Reply-To`, `References` headers, and attachments) is to base64url-encode the entire message and pass it via `--json '{\"raw\": \"...\"}'`. But even a modest ~577KB PDF attachment produces a ~1MB base64 payload, which exceeds macOS's ~256KB `ARG_MAX` for command-line arguments:\n\n```\ngws-behav gmail users messages send --params '{\"userId\": \"me\"}' --json \"{\\\"threadId\\\": \\\"...\\\", \\\"raw\\\": \\\"$RAW\\\"}\"\n# => zsh: argument list too long: gws\n```\n\n### 2. `--upload` with raw MIME file — wrong content type\n\nGmail's `/upload/gmail/v1/users/me/messages/send` endpoint accepts `message/rfc822` for media uploads. But `--upload` wraps the file in `multipart/related` with an auto-detected content type, which Gmail rejects:\n\n```\ngws gmail users messages send --params '{\"userId\": \"me\", \"uploadType\": \"media\"}' --upload message.eml\n# => Media type 'multipart/related; boundary=gws_boundary_...' is not supported.\n```\n\n## Suggested fixes\n\nAny of these would unblock the use case:\n\n1. **`--json-file `** or **`--json @`** — read JSON body from a file (like `curl -d @file.json`)\n2. **`--json -`** — read JSON body from stdin\n3. **`--upload-type `** — let users override the content type for `--upload` (e.g. `--upload-type message/rfc822`)\n4. **Auto-detect `.eml` → `message/rfc822`** in `--upload` content type detection\n\nOptions 1 or 2 are the most general and would help any API method with large request bodies, not just Gmail.\n\n## Environment\n\n- gws 0.4.4\n- macOS Darwin 25.3.0 (arm64)\n\n## Related\n\n- #88 — first-class reply/forward support would also solve this at a higher level\n- #244 — OOM on large file uploads (related large-payload issue)","labels":[],"number":247,"title":"Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)","updatedAt":"2026-03-12T01:03:30Z"},{"assignees":[],"body":"gws version: 0.4.4\nOS: macOS (Apple Silicon, MacBook Pro)\nAccount type: Personal @gmail.com\nDescription\nAfter authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects, all Google Drive API calls work correctly but all Apps Script API calls fail with 401 \"invalid authentication credentials.\"\nSteps to Reproduce\n\nInstall gws via npm: npm install -g @googleworkspace/cli\nPlace custom client_secret.json in ~/.config/gws/\nEnable both Google Drive API and Apps Script API in GCP project\nEnable Apps Script API at https://script.google.com/home/usersettings\nAuthenticate:\n\nbashgws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects\n\nExport unmasked credentials (required due to separate Keychain issue — see below):\n\nbashgws auth export --unmasked > ~/.config/gws/credentials.json\n\nVerify auth status shows correct scopes:\n\nbashgws auth status\n# Shows scope_count: 2, token_valid: true, both drive and script.projects scopes listed\n\nTest Drive API — works:\n\nbashgws drive files list --params '{\"pageSize\": 5, \"fields\": \"files(id,name,mimeType)\"}' --format table\n# Returns file listing successfully\n\nTest Apps Script API — fails:\n\nbashgws apps-script projects getContent --params '{\"scriptId\": \"VALID_SCRIPT_ID\"}'\n# Returns 401 error\nExpected Behavior\ngws apps-script projects getContent should return the script project content, since the credentials include the script.projects scope and the Apps Script API is enabled.\nActual Behavior\njson{\n \"error\": {\n \"code\": 401,\n \"message\": \"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\",\n \"reason\": \"unknown\"\n }\n}\nAdditional Context\nScope issue required revoking app access\nInitially, after adding script.projects to an existing auth session, the API returned 403 \"insufficient authentication scopes\" even though gws auth status showed the scope was present. This was resolved by revoking app access at https://myaccount.google.com/permissions and re-authenticating from scratch. After revoking, the error changed from 403 to 401.\nSeparate issue: encrypted credentials not usable for API calls\nOn macOS, gws auth login saves encrypted credentials via OS Keyring, but API calls (e.g., gws drive files list) fail with 401 \"No credentials provided\" unless you first run gws auth export --unmasked > ~/.config/gws/credentials.json to create a plaintext credentials file. The gws auth status command can read the encrypted credentials fine, but the API call code path cannot. This workaround resolves Drive API calls but does not resolve the Apps Script 401.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":205,"title":"Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials","updatedAt":"2026-03-06T01:20:33Z"},{"assignees":[],"body":"This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication:\n\n\"Image\"\n\nUnderstandably, this may just be a limitation of Advanced Protection, though it's always a bit of a confusing threat model that I can grant Claude connectors extensive access to my emails and Gdrive, but not read-only scopes for my own app.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":197,"title":"Doesn't support Advanced Protection Program users","updatedAt":"2026-03-14T00:53:12Z"},{"assignees":[],"body":"## Description\n\nWhen creating a comment on a Google Docs file using `gws drive comments create` with an `anchor` field, the comment is posted successfully (HTTP 200) but appears in the Google Docs UI as **\"Original content deleted\"** instead of being anchored to the specified text.\n\n## Steps to Reproduce\n\n```bash\ngws drive comments create \\\n --params '{\"fileId\": \"\", \"fields\": \"id,content,anchor,quotedFileContent\"}' \\\n --json '{\n \"content\": \"Hello\",\n \"quotedFileContent\": {\n \"mimeType\": \"text/plain\",\n \"value\": \"Overview\"\n },\n \"anchor\": \"{\\\"r\\\":\\\"head\\\",\\\"a\\\":[{\\\"lt\\\":\\\"b\\\",\\\"lv\\\":\\\"Overview\\\"}]}\"\n }'\n```\n\n## Expected Behavior\n\nThe comment should be anchored to the matching text in the document, or the CLI/docs should clearly indicate that anchored comments are not supported for Google Docs files via the API.\n\n## Actual Behavior\n\nThe API returns a 200 with the anchor saved, but the comment appears in the Google Docs UI as **\"Original content deleted\"** — no text is highlighted and the anchor has no visible effect.\n\n## Additional Context\n\n- This appears to be a known Google Drive API limitation: for Google Workspace editor files (Docs, Sheets, Slides), the `anchor` field is saved but ignored by the editor UI. See the [official docs](https://developers.google.com/workspace/drive/api/v3/manage-comments#add_an_anchored_comment_to_the_latest_revision_of_a_document): *\"The anchor is saved and returned when retrieving the comment, however Google Workspace editor apps treat these comments as un-anchored comments.\"*\n- It would be helpful if `gws` surfaced a warning when using `anchor` on a `application/vnd.google-apps.kix` (Google Docs) file, since the result is misleading.\n- `gws` version: `0.4.4`","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":169,"title":"drive comments create: anchor field results in 'Original content deleted' in Google Docs","updatedAt":"2026-03-05T20:38:24Z"},{"assignees":[],"body":"## Summary\nWhen authenticating with read-only scopes using `gws auth login --readonly`, exporting credentials with:\n\n```bash\ngws auth export --unmasked > credentials.json\n```\n\nand then using those credentials on another machine (OpenClaw), the resulting access appears to still allow full/write operations.\n\n## Reproduction\n1. Run:\n ```bash\n gws auth login --readonly\n ```\n2. Export credentials:\n ```bash\n gws auth export --unmasked > credentials.json\n ```\n3. Move `credentials.json` to an external machine/environment (OpenClaw).\n4. Use the exported credentials with `gws` there.\n5. Attempt a write operation (for example, Gmail send).\n\n## Actual behavior\nWrite/full-access behavior is still possible on the external machine, even though login was done with `--readonly`.\n\n## Expected behavior\nRead-only scope enforcement should persist with exported credentials and be enforced by Google-issued token scopes, not only by client-side command restrictions.\n\n## Questions\n- Are `--readonly` scopes currently enforced only in the local client flow, rather than in the OAuth token/scopes themselves?\n- If so, what is the recommended way to ensure exported credentials are truly read-only on external machines?\n- Can `--readonly` become the default behavior in the installer/setup flow (or at least a strongly guided default), not just a local/client-side option?\n\n## Why this matters\nUsers exporting credentials for remote execution expect least-privilege guarantees to carry over across machines/environments.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":168,"title":"gws auth login --readonly + auth export --unmasked appears to allow full access on external machine","updatedAt":"2026-03-12T12:31:16Z"},{"assignees":[],"body":"Does this project plan to support browsing Google Photos on the CLI? It would be nice to do analyses on photos using LLMs.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":163,"title":"Any plan to support Google Photos?","updatedAt":"2026-03-05T20:48:15Z"},{"assignees":[],"body":"Add an option to make HITL / requests gateway so those of us that are using agents and want to gatekeep them for oauth and write actions can do it","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPT1Q","name":"area: mcp","description":"","color":"c46210"}],"number":158,"title":"Add built in HITL that the agents can't edit in code","updatedAt":"2026-03-05T20:38:09Z"},{"assignees":[],"body":"I can't get `npx -y @googleworkspace/cli@latest auth login` to work properly. I triple checked the settings of my auth:\n* Status: testing\n* User type: external\n* Test user: includes myself\n* Client ID: Desktop\n\nThe only \"strange\" thing is that I can't set a redirect URI, which according to `auth setup` is necessary.\n\n`auth login` shows an URL, after browsing to it and approving all scopes on the \"... wants to access your Google Account\" page, I get this on a page https://accounts.google.com/info/unknownerror?...:\n\n\"Image\"","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":157,"title":"\"Something went wrong\" in oauth screen","updatedAt":"2026-03-05T20:37:48Z"},{"assignees":[],"body":"## Description\n\nOn Windows, the `.encryption_key` fallback path in `credential_store.rs` writes the AES-256-GCM encryption key as a plaintext file with no access controls applied.\n\nOn Unix, the key file is explicitly created with mode `0o600` (user-read/write only):\n\n```rust\n#[cfg(unix)]\n{\n use std::os::unix::fs::OpenOptionsExt;\n let mut options = std::fs::OpenOptions::new();\n options.write(true).create(true).truncate(true).mode(0o600);\n ...\n}\n#[cfg(not(unix))]\n{\n let _ = std::fs::write(&key_file, &b64_key); // no permission hardening\n}\n```\n\nOn Windows, `std::fs::write` is used with no equivalent ACL restrictions, meaning the key file inherits default directory permissions and is readable by other users or processes on the same machine.\n\nThis key decrypts `credentials..enc`, which contains the OAuth refresh token. An attacker with local read access can decrypt the token and gain long-lived Google account access.\n\n## Affected Code\n\nThree locations in `src/credential_store.rs` write sensitive files without Windows ACLs:\n\n1. **Line 112-115** — `.encryption_key` file (keyring `NoEntry` fallback)\n2. **Line 172-175** — `.encryption_key` file (keyring failure fallback)\n3. **Lines 246, 305** — `credentials.*.enc` files (via `atomic_write`)\n\nAdditionally, the config **directory** itself (`~/.config/gws/`) gets `0o700` on Unix (lines 89-99, 150-159) but no ACL on Windows.\n\n## Fix Plan\n\n### Option A: `windows-acl` crate (recommended)\n```toml\n[target.'cfg(windows)'.dependencies]\nwindows-acl = \"0.3\"\n```\n\nCreate a helper function:\n```rust\n#[cfg(windows)]\nfn set_owner_only_acl(path: &Path) -> std::io::Result<()> {\n use windows_acl::acl::ACL;\n // Get current user SID, set DACL to owner-only\n}\n```\n\nCall it after every file write on Windows (same locations where `#[cfg(unix)]` sets `0o600`).\n\n### Option B: Raw Win32 via `windows` crate\nUse `SetNamedSecurityInfoW` directly. More control but more boilerplate.\n\n### Option C: Interim warning\nPrint a warning on Windows when falling back to file storage:\n```\n⚠️ Warning: Encryption key stored without access controls. \n Run 'icacls %USERPROFILE%\\.config\\gws /inheritance:r /grant:r %USERNAME%:F' to restrict access.\n```\n\n## Files to Modify\n\n- `Cargo.toml` — add Windows-only dependency\n- `src/credential_store.rs` — add `#[cfg(windows)]` ACL blocks at all 3 write locations + directory creation\n- Optionally: extract a `set_secure_permissions(path)` helper that dispatches to `chmod 0o600` on Unix and ACL on Windows\n\n## Verification\n\n- Windows: `icacls ~/.config/gws/.encryption_key` should show only current user\n- Windows: `icacls ~/.config/gws/credentials.*.enc` should show only current user\n- Unix: behavior unchanged (`ls -la` confirms `0o600`)\n- CI: Windows test runner should exercise the keyring fallback path\n\n## Workaround\n\nUse Windows Credential Manager (the primary keyring path). There is currently no way to force this from the CLI. If keyring works, the file fallback is never used.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":153,"title":"[Security] Windows: encryption key written to plaintext file with no access controls","updatedAt":"2026-03-07T16:08:16Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent tries to help a user unsubscribe from mailing lists using `gws`, there is no guided path. The agent has to:\n\n1. Search for emails containing \"unsubscribe\"\n2. Fetch each message with `format: full` to extract `List-Unsubscribe` headers\n3. Guess whether to use the HTTPS URL or mailto address\n4. Attempt `curl` GET/POST requests to unsubscribe URLs (which mostly load confirmation pages requiring a browser)\n5. Fall back to sending emails to `mailto:` unsubscribe addresses via `gws gmail users messages send`\n\nThis entire process is trial-and-error, fragile, and often fails.\n\nMeanwhile, **Gmail natively supports one-click unsubscribe via RFC 8058**. Since June 2024, Google requires bulk senders to include `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers. The correct unsubscribe mechanism is a simple `POST` to the `List-Unsubscribe` HTTPS URL with the body `List-Unsubscribe=One-Click` — no browser needed, no mailto, no guessing.\n\n## Proposal\n\nAdd a `gws gmail +unsubscribe` helper skill that:\n\n1. **Lists candidates** — Scans recent emails for `List-Unsubscribe` and `List-Unsubscribe-Post` headers, groups by sender, and shows frequency/volume\n2. **Executes RFC 8058 one-click unsubscribe** — Sends the correct `POST` request with `List-Unsubscribe=One-Click` body to the HTTPS unsubscribe URL\n3. **Falls back gracefully** — For senders without RFC 8058 support, shows the mailto address or URL so the agent/user can decide\n\n### Example UX\n\n```bash\n# List subscription candidates with volume\ngws gmail +unsubscribe --list --max 200\n\n# Unsubscribe from a specific sender\ngws gmail +unsubscribe --from \"noreply@mail.michaelpage.com.sg\"\n\n# Dry run\ngws gmail +unsubscribe --from \"team@mail.cerebralvalley.ai\" --dry-run\n```\n\n## Why this matters for agents\n\nThe existing `gws-gmail-triage` skill is great for reading, but there's no skill for the common \"clean up my inbox subscriptions\" workflow. Without a dedicated skill, agents waste many API calls fumbling through message formats, header extraction, and URL guessing — exactly the kind of boilerplate `gws` is designed to eliminate.\n\n## References\n\n- [RFC 8058 — Signaling One-Click Functionality for List Email Headers](https://datatracker.ietf.org/doc/html/rfc8058)\n- [Gmail bulk sender requirements (June 2024)](https://support.google.com/a/answer/81126)","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":114,"title":"Add a gmail +unsubscribe skill for one-click list unsubscribe (RFC 8058)","updatedAt":"2026-03-06T07:16:56Z"},{"assignees":[],"body":"It would be convenient to be able to install this tool using Homebrew 😄 ","labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"}],"number":90,"title":"Feature request: Homebrew Formula","updatedAt":"2026-03-05T20:38:12Z"},{"assignees":[],"body":"Hi! I love skills. But I also hate how they create issues for agents.\n\nContext pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem with this is that the top level skills all get loaded into , and 40 of them, added to any other skills that people use, will make for a really bad experience.\n\nObviously, one might say that not all of them need to be installed at once, but with how heavy the cross-references are, basically none of them are skippable.\n\nProposal:\n\nEven if it costs some duplication, bundle the granular skills in a top level structure, with /references/ folder.\n\nI believe this will save both the users and the devs a lot of headaches, and it will be easier for users to manage a situation where they might only need parts of the included skills.\n\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":82,"title":"Optimisation: 40 skills => less skills + references","updatedAt":"2026-03-06T01:20:35Z"}] diff --git a/all_prs.json b/all_prs.json new file mode 100644 index 00000000..32ae37b0 --- /dev/null +++ b/all_prs.json @@ -0,0 +1 @@ +[{"closingIssuesReferences":[],"number":488,"state":"OPEN","title":"feat(credential_store): default keyring backend to OS-only, add keyring-with-file option"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wHXwu","number":169,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/169"}],"number":487,"state":"OPEN","title":"fix(drive): warn when anchor field targets Workspace editor files"},{"closingIssuesReferences":[],"number":486,"state":"OPEN","title":"feat(http): add --verbose / -v flag for request/response diagnostics"},{"closingIssuesReferences":[],"number":485,"state":"OPEN","title":"fix(http): route error and empty-result messages to stderr"},{"closingIssuesReferences":[],"number":484,"state":"OPEN","title":"fix(validate): reject dangerous Unicode characters in input validation"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":482,"state":"MERGED","title":"fix(gmail): RFC 2047 encode non-ASCII display names in address headers"},{"closingIssuesReferences":[],"number":481,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":480,"state":"MERGED","title":"feat(timezone): use Google account timezone for day-boundary calculations"},{"closingIssuesReferences":[],"number":479,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":478,"state":"MERGED","title":"feat: add opt-in structured HTTP request logging via tracing"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wUIxh","number":244,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/244"}],"number":477,"state":"MERGED","title":"fix: stream multipart uploads to avoid OOM on large files"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7ytu2a","number":473,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/473"}],"number":476,"state":"OPEN","title":"Add admin-directory as built-in service alias for Admin SDK Directory API"},{"closingIssuesReferences":[],"number":475,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xKrfZ","number":327,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/327"}],"number":471,"state":"CLOSED","title":"docs(skills): add supportsAllDrives guidance for Shared Drives"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xl-XV","number":380,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/380"}],"number":470,"state":"CLOSED","title":"feat(drive): auto-detect markdown uploads and convert to Google Docs"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":469,"state":"CLOSED","title":"fix(gmail): RFC 2047 encode non-ASCII display names in address headers"},{"closingIssuesReferences":[],"number":468,"state":"OPEN","title":"Revise OAuth login fix and consent screen instructions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1K7_","number":399,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/399"}],"number":467,"state":"MERGED","title":"docs: add Homebrew as an installation option in README"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xABA3","number":317,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/317"}],"number":466,"state":"OPEN","title":"feat(error): display colored error labels on TTY stderr"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yYHMc","number":438,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/438"}],"number":465,"state":"OPEN","title":"feat(gmail): add +read helper for extracting message body as plain text"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yD8x1","number":419,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/419"},{"id":"I_kwDORcxyRs7yliQV","number":461,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/461"}],"number":464,"state":"OPEN","title":"feat(calendar): add --conference flag to +insert for Google Meet links"},{"closingIssuesReferences":[],"number":463,"state":"OPEN","title":"fix(sheets): report error for invalid --json-values instead of silent empty append"},{"closingIssuesReferences":[],"number":462,"state":"CLOSED","title":"fix(workflow): use local timezone for standup-report and weekly-digest boundaries"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yJP70","number":424,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/424"}],"number":460,"state":"OPEN","title":"feat: add `--draft-only` mode for agent workflows"},{"closingIssuesReferences":[],"number":459,"state":"CLOSED","title":"chore: add package lockfile"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":458,"state":"CLOSED","title":"fix(gmail): RFC 2047 encode draft address headers"},{"closingIssuesReferences":[],"number":457,"state":"OPEN","title":"feat: add --dry-run support to events helper commands"},{"closingIssuesReferences":[],"number":456,"state":"CLOSED","title":"feature/add-dry-run-to-helpers"},{"closingIssuesReferences":[],"number":455,"state":"CLOSED","title":"feat: add --dry-run support to events helper commands"},{"closingIssuesReferences":[],"number":454,"state":"MERGED","title":"chore: sync skills with Discovery API"},{"closingIssuesReferences":[],"number":453,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":451,"state":"MERGED","title":"fix(docs): correct broken flag names in recipes"},{"closingIssuesReferences":[],"number":450,"state":"CLOSED","title":"chore: replace deprecated serde_yaml with serde_yml"},{"closingIssuesReferences":[],"number":449,"state":"MERGED","title":"chore: fix maintainer email typo in flake.nix and harden coverage.sh"},{"closingIssuesReferences":[],"number":448,"state":"OPEN","title":"fix(security): cap Retry-After sleep and sanitize mimeType in uploads"},{"closingIssuesReferences":[],"number":447,"state":"OPEN","title":"fix(security): validate --upload and --output paths against traversal"},{"closingIssuesReferences":[],"number":446,"state":"OPEN","title":"fix: report auth errors instead of silently proceeding unauthenticated"},{"closingIssuesReferences":[],"number":445,"state":"MERGED","title":"test(gmail): add unit tests for +triage argument parsing"},{"closingIssuesReferences":[],"number":444,"state":"MERGED","title":"fix(security): validate space name in chat +send"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wVZBB","number":259,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/259"}],"number":443,"state":"MERGED","title":"fix(calendar): use local timezone for agenda day boundaries"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wkDt1","number":280,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/280"}],"number":442,"state":"OPEN","title":"fix: `gws auth setup --help` prints usage instead of running wizard"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x8C7Y","number":408,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/408"}],"number":441,"state":"MERGED","title":"fix(auth): validate --subscription in gmail +watch and deduplicate PUBSUB_API_BASE"},{"closingIssuesReferences":[],"number":440,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":437,"state":"MERGED","title":"ci: avoid scope creep in gemini code assist reviews"},{"closingIssuesReferences":[],"number":436,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xprae","number":389,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/389"}],"number":435,"state":"MERGED","title":"fix(auth): auto-recover from stale encrypted credentials after upgrade"},{"closingIssuesReferences":[],"number":434,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":433,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":431,"state":"OPEN","title":"feat: support GOOGLE_WORKSPACE_CLI_API_BASE to override Google API base URL"},{"closingIssuesReferences":[],"number":429,"state":"MERGED","title":"feat: add --upload-content-type flag and smart MIME inference for uploads"},{"closingIssuesReferences":[],"number":428,"state":"MERGED","title":"feat(error): add structured exit codes for scriptable error handling"},{"closingIssuesReferences":[],"number":427,"state":"MERGED","title":"docs: document helper commands and the + prefix convention"},{"closingIssuesReferences":[],"number":426,"state":"OPEN","title":"feat(formatter): add --format tsv output format"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yHkZX","number":422,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/422"}],"number":423,"state":"OPEN","title":"feat: add HTTP proxy support via environment variables"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yFvFe","number":420,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/420"}],"number":421,"state":"OPEN","title":"docs(skills): add COOKBOOK.md for gws-docs with tab and formatting recipes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wUIxh","number":244,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/244"}],"number":418,"state":"CLOSED","title":"fix: stream multipart uploads to avoid OOM on large files"},{"closingIssuesReferences":[],"number":417,"state":"MERGED","title":"feat(gmail): add --html flag for HTML email composition"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w0s3N","number":300,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/300"}],"number":415,"state":"MERGED","title":"fix(schema): expose repeated field and expand array query params"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w38zb","number":310,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/310"},{"id":"I_kwDORcxyRs7w_cbk","number":316,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/316"}],"number":414,"state":"MERGED","title":"fix(auth): map People service to contacts/directory scope prefixes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xAq2T","number":319,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/319"}],"number":413,"state":"OPEN","title":"feat(auth): sort OAuth scopes by service, then access level"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wYcZh","number":268,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/268"}],"number":412,"state":"MERGED","title":"docs: warn about zsh ! history expansion in sheet range examples"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1Mwr","number":400,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/400"}],"number":411,"state":"CLOSED","title":"meta: add GitHub issue templates for bugs, features, and docs"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w49i3","number":311,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/311"}],"number":410,"state":"MERGED","title":"fix(sheets): preserve multi-row structure in +append --json-values"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x8C7Y","number":408,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/408"}],"number":409,"state":"CLOSED","title":"fix(security): validate resource names in watch and subscribe helpers"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xq8wj","number":392,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/392"}],"number":407,"state":"MERGED","title":"fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1K7_","number":399,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/399"}],"number":406,"state":"CLOSED","title":"docs: add Homebrew installation option"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":405,"state":"CLOSED","title":"fix: RFC 2047 encode non-ASCII display names in address headers"},{"closingIssuesReferences":[],"number":402,"state":"CLOSED","title":"fix(security): resolve TOCTOU race condition in atomic writes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xprae","number":389,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/389"}],"number":398,"state":"CLOSED","title":"fix(auth): auto-recover from credential decryption failures after upgrade"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xourR","number":386,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/386"}],"number":397,"state":"OPEN","title":"feat: expose minimal library crate for programmatic API access"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xq8wj","number":392,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/392"}],"number":396,"state":"CLOSED","title":"fix: refresh OAuth2 tokens in long-running watch/subscribe loops"},{"closingIssuesReferences":[],"number":395,"state":"OPEN","title":"feat(gmail): add --attachment flag to +send, +reply, +reply-all, +forward"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xABA3","number":317,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/317"}],"number":394,"state":"CLOSED","title":"fix: colorize stderr warnings and errors on TTY terminals"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xKrfZ","number":327,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/327"}],"number":393,"state":"CLOSED","title":"docs(drive): add supportsAllDrives note for shared drive access"},{"closingIssuesReferences":[],"number":391,"state":"CLOSED","title":"🔒 [security] Secure atomic file creation with restrictive permissions"},{"closingIssuesReferences":[],"number":390,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":388,"state":"MERGED","title":"chore: Update workflow to use `GOOGLEWORKSPACE_BOT_TOKEN` and `googleworkspace-bot` identity for operations."},{"closingIssuesReferences":[],"number":387,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":385,"state":"MERGED","title":"fix(ci): check remote tags in tag-release.sh"},{"closingIssuesReferences":[],"number":384,"state":"MERGED","title":"ci: use bot token for /gemini review comment"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xl-XV","number":380,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/380"}],"number":383,"state":"CLOSED","title":"feat(docs): add --content-format markdown to +write helper"},{"closingIssuesReferences":[],"number":382,"state":"CLOSED","title":"feat(vertex): add Vertex AI inference helper for Gemini models"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xVg7o","number":360,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/360"}],"number":379,"state":"CLOSED","title":"fix(auth): verify keyring roundtrip before deleting .encryption_key"},{"closingIssuesReferences":[],"number":377,"state":"CLOSED","title":"Ashwin/codex/pr2 retry consistency"},{"closingIssuesReferences":[],"number":376,"state":"CLOSED","title":"feat: expose library crate for programmatic API access"},{"closingIssuesReferences":[],"number":374,"state":"OPEN","title":"fix(security): validate --upload and --output file paths"},{"closingIssuesReferences":[],"number":373,"state":"MERGED","title":"fix(auth): enable native keyring backends on top of #359"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xWmWr","number":364,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/364"}],"number":371,"state":"CLOSED","title":"fix(auth): preserve Linux encryption key fallback"},{"closingIssuesReferences":[],"number":370,"state":"CLOSED","title":"fix(auth): verify keyring round-trip to prevent phantom key loss"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wVZBB","number":259,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/259"}],"number":369,"state":"CLOSED","title":"fix(calendar): use local day boundaries for agenda date filters"},{"closingIssuesReferences":[],"number":366,"state":"CLOSED","title":"docs(auth export): documment --unmasked flag"},{"closingIssuesReferences":[],"number":365,"state":"CLOSED","title":"feat: add MCP server for Google Workspace"},{"closingIssuesReferences":[],"number":363,"state":"CLOSED","title":"fix(security): harden --upload/--output file I/O against traversal, symlink escape, and TOCTOU races"},{"closingIssuesReferences":[],"number":362,"state":"MERGED","title":"feat(gmail): add recipient management flags and extract shared message builder"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":359,"state":"MERGED","title":"feat(credential_store): add GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND env var"},{"closingIssuesReferences":[],"number":358,"state":"CLOSED","title":"feat: Implement multi-account Configuration Profiles"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":357,"state":"CLOSED","title":"fix(credential_store): preserve .encryption_key file for keyring-less environments"},{"closingIssuesReferences":[],"number":355,"state":"CLOSED","title":"chore: sync skills with Discovery API"},{"closingIssuesReferences":[],"number":354,"state":"CLOSED","title":"Claude/rebase onto parent 2 z rr z"},{"closingIssuesReferences":[],"number":353,"state":"MERGED","title":"fix(gmail): bring +forward behavior in line with Gmail web UI"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTXZE","number":236,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/236"}],"number":352,"state":"MERGED","title":"fix(auth): format and deduplicate dynamic scope fallback"},{"closingIssuesReferences":[],"number":351,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":350,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":349,"state":"MERGED","title":"ci: run smoketest immediately after linux build, gate other builds on it"},{"closingIssuesReferences":[],"number":348,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":347,"state":"MERGED","title":"fix(ci): add missing tool input to install-action for cargo-llvm-cov"},{"closingIssuesReferences":[],"number":346,"state":"MERGED","title":"ci: make sccache setup resilient to cache API outages"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":345,"state":"MERGED","title":"fix(credential_store): stop persisting encryption key file when keyring is available"},{"closingIssuesReferences":[],"number":341,"state":"MERGED","title":"ci: pin all actions to a sha"},{"closingIssuesReferences":[],"number":339,"state":"MERGED","title":"ci: ignore sccache errors when GitHub down"},{"closingIssuesReferences":[],"number":331,"state":"MERGED","title":"build(ci): optimize nix and windows cache layers"},{"closingIssuesReferences":[],"number":330,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":329,"state":"MERGED","title":"fix(ci): use default GITHUB_TOKEN for PR reviews"},{"closingIssuesReferences":[],"number":326,"state":"MERGED","title":"chore: Synced file(s) with googleworkspace/.github"},{"closingIssuesReferences":[],"number":325,"state":"CLOSED","title":"fix: allow api:version escape hatch for unlisted APIs"},{"closingIssuesReferences":[],"number":324,"state":"CLOSED","title":"feat: add Google Search Console as a known service"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXIUW","number":266,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/266"}],"number":322,"state":"MERGED","title":"fix: RFC 2047 encode non-ASCII email subjects in +send helper"},{"closingIssuesReferences":[],"number":320,"state":"CLOSED","title":"feat: improve 88 skills descriptions, examples, and validation"},{"closingIssuesReferences":[],"number":318,"state":"OPEN","title":"[Night Shift] Fix +append --json-values multi-row bug"},{"closingIssuesReferences":[],"number":313,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":307,"state":"CLOSED","title":"feat: add gws cache clear and --format tsv"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wdDt-","number":271,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/271"}],"number":305,"state":"CLOSED","title":"fix: handle null libc version on Android/Termux"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXH30","number":265,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/265"}],"number":304,"state":"MERGED","title":"fix: use gmail.readonly scope in +triage to avoid metadata scope 403"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wkDt1","number":280,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/280"}],"number":303,"state":"CLOSED","title":"fix: handle --help flag in gws auth setup"},{"closingIssuesReferences":[],"number":302,"state":"MERGED","title":"fix: use native OS certificate store for TLS"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w0s3N","number":300,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/300"}],"number":301,"state":"CLOSED","title":"fix(schema): keep repeated query metadata and honor array params"},{"closingIssuesReferences":[],"number":297,"state":"CLOSED","title":"feat: semantic exit codes and actionable error hints"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXGI1","number":264,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/264"}],"number":296,"state":"CLOSED","title":"docs: add asdf installation instructions"},{"closingIssuesReferences":[],"number":295,"state":"MERGED","title":"fix(auth): prioritize local project configuration over global ADC for quota attribution"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wZBDL","number":269,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/269"}],"number":294,"state":"CLOSED","title":"skills: remove openclaw vendor metadata from SKILL frontmatter"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXIUW","number":266,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/266"}],"number":292,"state":"CLOSED","title":"docs: clarify fields usage and UTF-8 subjects for gmail raw drafts"},{"closingIssuesReferences":[],"number":290,"state":"CLOSED","title":"docs: quote sheet ranges and JSON examples to avoid zsh ! expansion"},{"closingIssuesReferences":[],"number":289,"state":"CLOSED","title":"feat(auth): add configuration profiles for account switching"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wlYBe","number":283,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/283"}],"number":288,"state":"MERGED","title":"fix: handle array-of-arrays in CSV formatter"},{"closingIssuesReferences":[],"number":277,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":275,"state":"MERGED","title":"BREAKING CHANGE: Remove MCP server mode"},{"closingIssuesReferences":[],"number":254,"state":"MERGED","title":"chore: Remove a subset of skills and recipes along with their service entries and registry references"},{"closingIssuesReferences":[],"number":253,"state":"MERGED","title":"feat!: remove multi-account, DWD, and impersonation support"},{"closingIssuesReferences":[],"number":250,"state":"MERGED","title":"chore: Remove dwd support"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTXZE","number":236,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/236"}],"number":246,"state":"CLOSED","title":"fix(auth): fall back to Discovery docs when `-s` specifies services not in static scope lists"},{"closingIssuesReferences":[],"number":243,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":242,"state":"MERGED","title":"fix(client): Move x-goog-user-project header to API requests only"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTRyl","number":234,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/234"}],"number":241,"state":"CLOSED","title":"feat(auth): redesign scope picker to show services instead of raw scopes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wCYOn","number":152,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/152"}],"number":240,"state":"MERGED","title":"fix: use .zip for Windows npm installer to fix Git Bash install failure"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTPKr","number":232,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/232"}],"number":239,"state":"CLOSED","title":"feat(auth): auto-migrate legacy credentials.enc to per-account format"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9tfD","number":116,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/116"}],"number":238,"state":"CLOSED","title":"refactor: extract timeout constant and DRY project input logic (closes #116)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wFU83","number":162,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/162"},{"id":"I_kwDORcxyRs7wTVgl","number":235,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/235"}],"number":237,"state":"CLOSED","title":"fix(mcp): switch tool names from underscore to hyphen separator"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9tfD","number":116,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/116"}],"number":233,"state":"CLOSED","title":"fix: increase project timeout and add manual entry in gws auth setup (closes #116)"},{"closingIssuesReferences":[],"number":231,"state":"CLOSED","title":"feat(auth): add `--no-localhost` flag to `gws auth login`"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wKUCH","number":179,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/179"}],"number":230,"state":"CLOSED","title":"fix(auth): resolve per-account credentials in `auth export`"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9ppc","number":114,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/114"}],"number":229,"state":"CLOSED","title":"feat: add gmail +unsubscribe skill for RFC 8058 one-click unsubscribe"},{"closingIssuesReferences":[],"number":228,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7vE9","number":88,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/88"}],"number":227,"state":"CLOSED","title":"feat: add gmail +reply and +forward skills"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wQJzz","number":210,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/210"}],"number":226,"state":"CLOSED","title":"feat: add --port and --no-browser flags for headless auth"},{"closingIssuesReferences":[],"number":225,"state":"CLOSED","title":"docs: add usage examples for Docs and Drive commands"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wFU83","number":162,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/162"}],"number":224,"state":"CLOSED","title":"fix: use service alias in MCP tool names for aliased services"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wKfRa","number":181,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/181"},{"id":"I_kwDORcxyRs7wR6BO","number":221,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/221"}],"number":223,"state":"MERGED","title":"fix: respect account selection in MCP server and CLI --account flag"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wINp6","number":171,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/171"}],"number":222,"state":"MERGED","title":"docs: document all environment variables and enable CONFIG_DIR override"},{"closingIssuesReferences":[],"number":218,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":217,"state":"MERGED","title":"fix: clean up nits from PR #175 auth fix"},{"closingIssuesReferences":[],"number":216,"state":"MERGED","title":"ci: add path filtering to skip expensive jobs on non-Rust changes"},{"closingIssuesReferences":[],"number":215,"state":"MERGED","title":"fix(client): For ADC, send x-goog-user-project header"},{"closingIssuesReferences":[],"number":214,"state":"CLOSED","title":"feat: expose library crate for programmatic API access"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wQkNd","number":212,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/212"}],"number":213,"state":"MERGED","title":"fix(mcp): conditionally include body/upload in tool schemas, drop empty body on execution"},{"closingIssuesReferences":[],"number":209,"state":"MERGED","title":"ci: auto-trigger Gemini Code Assist review on PR push"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wPmsr","number":206,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/206"}],"number":208,"state":"MERGED","title":"fix: isolate flaky auth tests from host ADC credentials"},{"closingIssuesReferences":[],"number":207,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":204,"state":"MERGED","title":"chore: auto triage and fmt"},{"closingIssuesReferences":[],"number":203,"state":"MERGED","title":"chore: use labels"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wHTys","number":168,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/168"}],"number":202,"state":"CLOSED","title":"fix: store granted scopes in credentials, warn on scope escalation via export"},{"closingIssuesReferences":[],"number":201,"state":"CLOSED","title":"feat: add native Factory Droid integration"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wJPN0","number":174,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/174"}],"number":200,"state":"MERGED","title":"fix: prevent gmail.metadata scope from blocking query parameters"},{"closingIssuesReferences":[],"number":199,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAG6G","number":137,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/137"}],"number":196,"state":"CLOSED","title":"fix: surface auth errors instead of sending unauthenticated requests"},{"closingIssuesReferences":[],"number":193,"state":"MERGED","title":"fix: log token cache errors instead of silently swallowing them"},{"closingIssuesReferences":[],"number":192,"state":"MERGED","title":"fix: replace strip_suffix(\".readonly\").unwrap() with unwrap_or"},{"closingIssuesReferences":[],"number":191,"state":"CLOSED","title":"fix: warn on stderr when discovery cache write fails"},{"closingIssuesReferences":[],"number":190,"state":"CLOSED","title":"fix: log auth errors to stderr instead of silently discarding"},{"closingIssuesReferences":[],"number":189,"state":"CLOSED","title":"fix: replace unwrap() with proper error handling in MCP server"},{"closingIssuesReferences":[],"number":186,"state":"MERGED","title":"fix(setup): drain stale keypresses between TUI screen transitions"},{"closingIssuesReferences":[],"number":185,"state":"MERGED","title":"chore: steps to reduce contributor friction"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wK1fc","number":182,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/182"}],"number":183,"state":"MERGED","title":"fix(executor): add Content-Length: 0 for body-less POST requests"},{"closingIssuesReferences":[],"number":180,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v-Ciy","number":119,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/119"},{"id":"I_kwDORcxyRs7wAXev","number":138,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/138"}],"number":177,"state":"MERGED","title":"fix: allow services flag for login and improve default list for consumer accounts"},{"closingIssuesReferences":[],"number":176,"state":"MERGED","title":"chore(docs): easier cmd to install from source"},{"closingIssuesReferences":[],"number":175,"state":"MERGED","title":"Fix auth failures when accounts.json registry is missing"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wDc8I","number":156,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/156"}],"number":173,"state":"CLOSED","title":"fix: gracefully fallback on legacy credentials instead of bailing"},{"closingIssuesReferences":[],"number":172,"state":"MERGED","title":"feat(mcp): add compact tool mode to reduce context window usage"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wFU83","number":162,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/162"}],"number":165,"state":"CLOSED","title":"fix(mcp): align tools/list namespace with aliased services"},{"closingIssuesReferences":[],"number":164,"state":"CLOSED","title":"fix: remove legacy darwin.apple_sdk reference in flake.nix"},{"closingIssuesReferences":[],"number":161,"state":"CLOSED","title":"Fix typo in gws-reseller/SKILL.md"},{"closingIssuesReferences":[],"number":154,"state":"MERGED","title":"fix: migrate to new Darwin SDK pattern in nix flake"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAXev","number":138,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/138"}],"number":148,"state":"CLOSED","title":"fix: add -s / --services flag to `gws auth login` to limit OAuth scope picker"},{"closingIssuesReferences":[],"number":147,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAisd","number":139,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/139"}],"number":146,"state":"MERGED","title":"fix: use reverse video for TUI highlight style"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAisd","number":139,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/139"}],"number":145,"state":"CLOSED","title":"fix: use terminal-default colors for TUI text to fix readability on light themes"},{"closingIssuesReferences":[],"number":143,"state":"CLOSED","title":"Remote mcp gateway"},{"closingIssuesReferences":[],"number":142,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAG6G","number":137,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/137"}],"number":141,"state":"CLOSED","title":"fix: enhance account resolution and export command handling"},{"closingIssuesReferences":[],"number":140,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":136,"state":"MERGED","title":"docs: improve readme especially auth which is complex no matter what"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v-Ciy","number":119,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/119"}],"number":135,"state":"CLOSED","title":"fix: fix gws auth login for personal @gmail.com accounts"},{"closingIssuesReferences":[],"number":134,"state":"MERGED","title":"fix: use ~/.config/gws on all platforms for consistent config path"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7nJ6","number":87,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/87"}],"number":133,"state":"CLOSED","title":"fix: harden URL and path construction across helper modules"},{"closingIssuesReferences":[],"number":132,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":131,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":129,"state":"MERGED","title":"Fix URL template substitution for media upload endpoints in executor"},{"closingIssuesReferences":[],"number":128,"state":"MERGED","title":"feat: add Linux ARM64 build targets (gnu + musl)"},{"closingIssuesReferences":[],"number":127,"state":"MERGED","title":"fix: exclude Workspace-admin-only scopes from Recommended preset"},{"closingIssuesReferences":[],"number":126,"state":"MERGED","title":"fix: use gcloud.cmd on Windows and show platform-correct config paths"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9DYA","number":103,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/103"}],"number":125,"state":"MERGED","title":"feat: add Application Default Credentials (ADC) support"},{"closingIssuesReferences":[],"number":124,"state":"MERGED","title":"Fix/warn credential permission failures"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9tfD","number":116,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/116"}],"number":123,"state":"MERGED","title":"fix: add manual project ID entry to setup project picker"},{"closingIssuesReferences":[],"number":122,"state":"MERGED","title":"fix: reject DEL character (0x7F) in input validation"},{"closingIssuesReferences":[],"number":121,"state":"CLOSED","title":"feat: add [MANUAL] notation for human-interaction steps (closes #66)"},{"closingIssuesReferences":[],"number":120,"state":"MERGED","title":"fix: build_url now falls back to method.path when flatPath placeholders do not match parameter names, resolving Slides API issues."},{"closingIssuesReferences":[],"number":117,"state":"CLOSED","title":"fix: clean up per-account token caches on logout"},{"closingIssuesReferences":[],"number":115,"state":"MERGED","title":"docs: add note about not official product more prominently"},{"closingIssuesReferences":[],"number":113,"state":"CLOSED","title":"feat: add Homebrew installer via cargo-dist"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7kdn","number":86,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/86"}],"number":112,"state":"MERGED","title":"fix: add x86_64-unknown-linux-musl build target"},{"closingIssuesReferences":[],"number":111,"state":"CLOSED","title":"test: add coverage for helper dispatch and service alias resolution"},{"closingIssuesReferences":[],"number":110,"state":"CLOSED","title":"feat: add [MANUAL] notation for human interaction steps"},{"closingIssuesReferences":[],"number":109,"state":"MERGED","title":"fix: replace unwrap() calls with proper error handling in MCP server"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v5T0c","number":73,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/73"}],"number":108,"state":"MERGED","title":"fix: filter alertcenter scopes from user OAuth login flow"},{"closingIssuesReferences":[],"number":107,"state":"CLOSED","title":"feat: Add Agoragentic marketplace skill — agent-to-agent commerce for Workspace automations"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v8Yqt","number":96,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/96"}],"number":106,"state":"MERGED","title":"fix: drain stdout pipe to prevent project listing deadlock"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7vE9","number":88,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/88"}],"number":105,"state":"MERGED","title":"feat(gmail): add +reply, +reply-all, and +forward helpers"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7nJ6","number":87,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/87"}],"number":102,"state":"MERGED","title":"fix: harden URL and path construction across helper modules"},{"closingIssuesReferences":[],"number":100,"state":"CLOSED","title":"feat: add GOOGLE_WORKSPACE_CLI_API_BASE_URL for custom/mock endpoint support"},{"closingIssuesReferences":[],"number":99,"state":"CLOSED","title":"feat(auth): add gws auth use-adc command "},{"closingIssuesReferences":[],"number":98,"state":"CLOSED","title":"fix: resolve Clippy warnings"},{"closingIssuesReferences":[],"number":97,"state":"CLOSED","title":"feat: add GWS_API_BASE_URL for custom/mock endpoint support"},{"closingIssuesReferences":[],"number":95,"state":"MERGED","title":"Improve auth setup project-creation error handling and retry UX"},{"closingIssuesReferences":[],"number":94,"state":"MERGED","title":"feat: Add nix flake"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v8Evj","number":92,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/92"}],"number":93,"state":"CLOSED","title":"Fix typo: 'descrived' to 'described' in SKILL.md"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9-9P","number":118,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/118"}],"number":91,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":89,"state":"MERGED","title":"Fix: correct author email typo in package.json"},{"closingIssuesReferences":[],"number":85,"state":"MERGED","title":"feat: multi-account support"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7cam","number":83,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/83"}],"number":84,"state":"MERGED","title":"fix: encode URL template path params in build_url"},{"closingIssuesReferences":[],"number":81,"state":"MERGED","title":"fix: credential masking panic and silent token write errors"},{"closingIssuesReferences":[],"number":80,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":79,"state":"MERGED","title":"fix: add github.com instead of just github in readme"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v6b3_","number":75,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/75"}],"number":77,"state":"MERGED","title":"fix: enable APIs individually and surface gcloud errors"},{"closingIssuesReferences":[],"number":72,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":71,"state":"MERGED","title":"feat: add `gws version` bare subcommand"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v2zmI","number":64,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/64"}],"number":70,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v2zmI","number":64,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/64"}],"number":68,"state":"MERGED","title":"feat: smarter truncation of method/resource descriptions"},{"closingIssuesReferences":[],"number":67,"state":"CLOSED","title":"feat: implement clasp"},{"closingIssuesReferences":[],"number":65,"state":"MERGED","title":"chore: sync skills with Discovery API"},{"closingIssuesReferences":[],"number":63,"state":"MERGED","title":"feat: Implement hourly cron and manual trigger for the generate-skills workflow to auto-sync skills via pull requests and downgrade CI drift check to a warning."},{"closingIssuesReferences":[],"number":62,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vwMum","number":56,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/56"}],"number":61,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vwMum","number":56,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/56"}],"number":60,"state":"MERGED","title":"docs: fix gws setup → gws auth setup"},{"closingIssuesReferences":[],"number":59,"state":"MERGED","title":"fix: use gl-rust/ prefix in x-goog-api-client header"},{"closingIssuesReferences":[],"number":58,"state":"MERGED","title":"feat: add gws mcp server"},{"closingIssuesReferences":[],"number":55,"state":"MERGED","title":"ci: add workflow to publish OpenClaw skills to ClawHub"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhe3","number":24,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/24"},{"id":"I_kwDORcxyRs7vmhjU","number":25,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/25"},{"id":"I_kwDORcxyRs7vm-Tz","number":31,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/31"}],"number":54,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":53,"state":"MERGED","title":"ci: auto-generate and commit skills on branch push"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vo3kB","number":42,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/42"}],"number":52,"state":"MERGED","title":"fix: atomic credential file writes to prevent corruption on crash (fixes #42)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vohTs","number":40,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/40"},{"id":"I_kwDORcxyRs7vo3k6","number":43,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/43"}],"number":51,"state":"MERGED","title":"fix(table): flatten nested objects to dot-notation, safe multi-byte truncation (fixes #40 #43)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vohKf","number":39,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/39"}],"number":50,"state":"MERGED","title":"fix: add YAML document separators when paginating (fixes #39)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vohAX","number":38,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/38"}],"number":49,"state":"MERGED","title":"fix: warn to stderr when unknown --format value is provided (fixes #38)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhp-","number":26,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/26"}],"number":48,"state":"MERGED","title":"fix(auth): support --help / -h flag on auth subcommand (fixes #26)"},{"closingIssuesReferences":[],"number":47,"state":"MERGED","title":"ci: skip smoketest on fork pull requests"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmtp7","number":27,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/27"}],"number":46,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":41,"state":"MERGED","title":"docs(gws-shared): add community guidance for stars + issue hygiene"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vogNm","number":35,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/35"},{"id":"I_kwDORcxyRs7vogX7","number":36,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/36"}],"number":37,"state":"MERGED","title":"fix: YAML block scalar for single-line strings, and repeated CSV/table headers with --page-all"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vm-UW","number":32,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/32"}],"number":34,"state":"CLOSED","title":"feat: support kebab-case aliases for camelCase subcommands (fixes #32)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vm-Tz","number":31,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/31"}],"number":33,"state":"MERGED","title":"feat(error): detect accessNotConfigured and guide users to enable APIs"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhe3","number":24,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/24"},{"id":"I_kwDORcxyRs7vmhjU","number":25,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/25"}],"number":30,"state":"MERGED","title":"fix: narrow default OAuth scopes to avoid restricted_client, improve non-interactive setup UX"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhe3","number":24,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/24"},{"id":"I_kwDORcxyRs7vmhjU","number":25,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/25"}],"number":29,"state":"MERGED","title":"docs(auth): add manual OAuth client and browser consent guidance"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmtp7","number":27,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/27"}],"number":28,"state":"MERGED","title":"fix(auth): stabilize encrypted credential key fallback"},{"closingIssuesReferences":[],"number":21,"state":"MERGED","title":"fix: harden URL encoding and input validation for AI/LLM callers"},{"closingIssuesReferences":[],"number":20,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":19,"state":"MERGED","title":"🧪 Add test for missing error paths in load_client_config"},{"closingIssuesReferences":[],"number":18,"state":"MERGED","title":"feat: skills expansion"},{"closingIssuesReferences":[],"number":17,"state":"MERGED","title":"🧪 Add test for EncryptedTokenStorage::new"},{"closingIssuesReferences":[],"number":16,"state":"MERGED","title":"Fix Gemini extension installation issue and update docs"},{"closingIssuesReferences":[],"number":15,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":14,"state":"MERGED","title":"fix(docs): improve README typography and spacing"},{"closingIssuesReferences":[],"number":13,"state":"MERGED","title":"docs: make README banner full-width and add changeset"},{"closingIssuesReferences":[],"number":12,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":11,"state":"MERGED","title":"fix: decrypt token cache before extracting refresh token"},{"closingIssuesReferences":[],"number":10,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":9,"state":"MERGED","title":"fix: use correct npm-scope/npm-package config keys for cargo-dist"},{"closingIssuesReferences":[],"number":8,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":7,"state":"MERGED","title":"fix: sync Cargo.toml version with changesets"},{"closingIssuesReferences":[],"number":6,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":5,"state":"MERGED","title":"docs: fix gif, readme, etc"},{"closingIssuesReferences":[],"number":4,"state":"CLOSED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":3,"state":"MERGED","title":"ci: speed up builds with rust-cache, sccache, and artifact reuse"},{"closingIssuesReferences":[],"number":2,"state":"MERGED","title":"fix: use changeset tag instead of publish, defer npm publishing to cargo-dist"},{"closingIssuesReferences":[],"number":1,"state":"MERGED","title":"feat: implement cli"}] diff --git a/analysis_results.json b/analysis_results.json new file mode 100644 index 00000000..70976283 --- /dev/null +++ b/analysis_results.json @@ -0,0 +1,256 @@ +[ + { + "assignees": [], + "body": "## Description\n\nOn Windows, the `.encryption_key` fallback path in `credential_store.rs` writes the AES-256-GCM encryption key as a plaintext file with no access controls applied.\n\nOn Unix, the key file is explicitly created with mode `0o600` (user-read/write only):\n\n```rust\n#[cfg(unix)]\n{\n use std::os::unix::fs::OpenOptionsExt;\n let mut options = std::fs::OpenOptions::new();\n options.write(true).create(true).truncate(true).mode(0o600);\n ...\n}\n#[cfg(not(unix))]\n{\n let _ = std::fs::write(&key_file, &b64_key); // no permission hardening\n}\n```\n\nOn Windows, `std::fs::write` is used with no equivalent ACL restrictions, meaning the key file inherits default directory permissions and is readable by other users or processes on the same machine.\n\nThis key decrypts `credentials..enc`, which contains the OAuth refresh token. An attacker with local read access can decrypt the token and gain long-lived Google account access.\n\n## Affected Code\n\nThree locations in `src/credential_store.rs` write sensitive files without Windows ACLs:\n\n1. **Line 112-115** \u2014 `.encryption_key` file (keyring `NoEntry` fallback)\n2. **Line 172-175** \u2014 `.encryption_key` file (keyring failure fallback)\n3. **Lines 246, 305** \u2014 `credentials.*.enc` files (via `atomic_write`)\n\nAdditionally, the config **directory** itself (`~/.config/gws/`) gets `0o700` on Unix (lines 89-99, 150-159) but no ACL on Windows.\n\n## Fix Plan\n\n### Option A: `windows-acl` crate (recommended)\n```toml\n[target.'cfg(windows)'.dependencies]\nwindows-acl = \"0.3\"\n```\n\nCreate a helper function:\n```rust\n#[cfg(windows)]\nfn set_owner_only_acl(path: &Path) -> std::io::Result<()> {\n use windows_acl::acl::ACL;\n // Get current user SID, set DACL to owner-only\n}\n```\n\nCall it after every file write on Windows (same locations where `#[cfg(unix)]` sets `0o600`).\n\n### Option B: Raw Win32 via `windows` crate\nUse `SetNamedSecurityInfoW` directly. More control but more boilerplate.\n\n### Option C: Interim warning\nPrint a warning on Windows when falling back to file storage:\n```\n\u26a0\ufe0f Warning: Encryption key stored without access controls. \n Run 'icacls %USERPROFILE%\\.config\\gws /inheritance:r /grant:r %USERNAME%:F' to restrict access.\n```\n\n## Files to Modify\n\n- `Cargo.toml` \u2014 add Windows-only dependency\n- `src/credential_store.rs` \u2014 add `#[cfg(windows)]` ACL blocks at all 3 write locations + directory creation\n- Optionally: extract a `set_secure_permissions(path)` helper that dispatches to `chmod 0o600` on Unix and ACL on Windows\n\n## Verification\n\n- Windows: `icacls ~/.config/gws/.encryption_key` should show only current user\n- Windows: `icacls ~/.config/gws/credentials.*.enc` should show only current user\n- Unix: behavior unchanged (`ls -la` confirms `0o600`)\n- CI: Windows test runner should exercise the keyring fallback path\n\n## Workaround\n\nUse Windows Credential Manager (the primary keyring path). There is currently no way to force this from the CLI. If keyring works, the file fallback is never used.\n", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPRQg", + "name": "area: auth", + "description": "", + "color": "1d76db" + } + ], + "number": 153, + "title": "[Security] Windows: encryption key written to plaintext file with no access controls", + "updatedAt": "2026-03-07T16:08:16Z" + }, + { + "assignees": [], + "body": "gws version: 0.4.4\nOS: macOS (Apple Silicon, MacBook Pro)\nAccount type: Personal @gmail.com\nDescription\nAfter authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects, all Google Drive API calls work correctly but all Apps Script API calls fail with 401 \"invalid authentication credentials.\"\nSteps to Reproduce\n\nInstall gws via npm: npm install -g @googleworkspace/cli\nPlace custom client_secret.json in ~/.config/gws/\nEnable both Google Drive API and Apps Script API in GCP project\nEnable Apps Script API at https://script.google.com/home/usersettings\nAuthenticate:\n\nbashgws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects\n\nExport unmasked credentials (required due to separate Keychain issue \u2014 see below):\n\nbashgws auth export --unmasked > ~/.config/gws/credentials.json\n\nVerify auth status shows correct scopes:\n\nbashgws auth status\n# Shows scope_count: 2, token_valid: true, both drive and script.projects scopes listed\n\nTest Drive API \u2014 works:\n\nbashgws drive files list --params '{\"pageSize\": 5, \"fields\": \"files(id,name,mimeType)\"}' --format table\n# Returns file listing successfully\n\nTest Apps Script API \u2014 fails:\n\nbashgws apps-script projects getContent --params '{\"scriptId\": \"VALID_SCRIPT_ID\"}'\n# Returns 401 error\nExpected Behavior\ngws apps-script projects getContent should return the script project content, since the credentials include the script.projects scope and the Apps Script API is enabled.\nActual Behavior\njson{\n \"error\": {\n \"code\": 401,\n \"message\": \"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\",\n \"reason\": \"unknown\"\n }\n}\nAdditional Context\nScope issue required revoking app access\nInitially, after adding script.projects to an existing auth session, the API returned 403 \"insufficient authentication scopes\" even though gws auth status showed the scope was present. This was resolved by revoking app access at https://myaccount.google.com/permissions and re-authenticating from scratch. After revoking, the error changed from 403 to 401.\nSeparate issue: encrypted credentials not usable for API calls\nOn macOS, gws auth login saves encrypted credentials via OS Keyring, but API calls (e.g., gws drive files list) fail with 401 \"No credentials provided\" unless you first run gws auth export --unmasked > ~/.config/gws/credentials.json to create a plaintext credentials file. The gws auth status command can read the encrypted credentials fine, but the API call code path cannot. This workaround resolves Drive API calls but does not resolve the Apps Script 401.", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPRQg", + "name": "area: auth", + "description": "", + "color": "1d76db" + } + ], + "number": 205, + "title": "Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials", + "updatedAt": "2026-03-06T01:20:33Z" + }, + { + "assignees": [], + "body": "This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication:\n\n\"Image\"\n\nUnderstandably, this may just be a limitation of Advanced Protection, though it's always a bit of a confusing threat model that I can grant Claude connectors extensive access to my emails and Gdrive, but not read-only scopes for my own app.", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPRQg", + "name": "area: auth", + "description": "", + "color": "1d76db" + } + ], + "number": 197, + "title": "Doesn't support Advanced Protection Program users", + "updatedAt": "2026-03-14T00:53:12Z" + }, + { + "assignees": [], + "body": "## Summary\nWhen authenticating with read-only scopes using `gws auth login --readonly`, exporting credentials with:\n\n```bash\ngws auth export --unmasked > credentials.json\n```\n\nand then using those credentials on another machine (OpenClaw), the resulting access appears to still allow full/write operations.\n\n## Reproduction\n1. Run:\n ```bash\n gws auth login --readonly\n ```\n2. Export credentials:\n ```bash\n gws auth export --unmasked > credentials.json\n ```\n3. Move `credentials.json` to an external machine/environment (OpenClaw).\n4. Use the exported credentials with `gws` there.\n5. Attempt a write operation (for example, Gmail send).\n\n## Actual behavior\nWrite/full-access behavior is still possible on the external machine, even though login was done with `--readonly`.\n\n## Expected behavior\nRead-only scope enforcement should persist with exported credentials and be enforced by Google-issued token scopes, not only by client-side command restrictions.\n\n## Questions\n- Are `--readonly` scopes currently enforced only in the local client flow, rather than in the OAuth token/scopes themselves?\n- If so, what is the recommended way to ensure exported credentials are truly read-only on external machines?\n- Can `--readonly` become the default behavior in the installer/setup flow (or at least a strongly guided default), not just a local/client-side option?\n\n## Why this matters\nUsers exporting credentials for remote execution expect least-privilege guarantees to carry over across machines/environments.\n", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPRQg", + "name": "area: auth", + "description": "", + "color": "1d76db" + } + ], + "number": 168, + "title": "gws auth login --readonly + auth export --unmasked appears to allow full access on external machine", + "updatedAt": "2026-03-12T12:31:16Z" + }, + { + "assignees": [], + "body": "I can't get `npx -y @googleworkspace/cli@latest auth login` to work properly. I triple checked the settings of my auth:\n* Status: testing\n* User type: external\n* Test user: includes myself\n* Client ID: Desktop\n\nThe only \"strange\" thing is that I can't set a redirect URI, which according to `auth setup` is necessary.\n\n`auth login` shows an URL, after browsing to it and approving all scopes on the \"... wants to access your Google Account\" page, I get this on a page https://accounts.google.com/info/unknownerror?...:\n\n\"Image\"", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPRQg", + "name": "area: auth", + "description": "", + "color": "1d76db" + } + ], + "number": 157, + "title": "\"Something went wrong\" in oauth screen", + "updatedAt": "2026-03-05T20:37:48Z" + }, + { + "assignees": [], + "body": "## Problem\n\nWhen an AI agent tries to help a user unsubscribe from mailing lists using `gws`, there is no guided path. The agent has to:\n\n1. Search for emails containing \"unsubscribe\"\n2. Fetch each message with `format: full` to extract `List-Unsubscribe` headers\n3. Guess whether to use the HTTPS URL or mailto address\n4. Attempt `curl` GET/POST requests to unsubscribe URLs (which mostly load confirmation pages requiring a browser)\n5. Fall back to sending emails to `mailto:` unsubscribe addresses via `gws gmail users messages send`\n\nThis entire process is trial-and-error, fragile, and often fails.\n\nMeanwhile, **Gmail natively supports one-click unsubscribe via RFC 8058**. Since June 2024, Google requires bulk senders to include `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers. The correct unsubscribe mechanism is a simple `POST` to the `List-Unsubscribe` HTTPS URL with the body `List-Unsubscribe=One-Click` \u2014 no browser needed, no mailto, no guessing.\n\n## Proposal\n\nAdd a `gws gmail +unsubscribe` helper skill that:\n\n1. **Lists candidates** \u2014 Scans recent emails for `List-Unsubscribe` and `List-Unsubscribe-Post` headers, groups by sender, and shows frequency/volume\n2. **Executes RFC 8058 one-click unsubscribe** \u2014 Sends the correct `POST` request with `List-Unsubscribe=One-Click` body to the HTTPS unsubscribe URL\n3. **Falls back gracefully** \u2014 For senders without RFC 8058 support, shows the mailto address or URL so the agent/user can decide\n\n### Example UX\n\n```bash\n# List subscription candidates with volume\ngws gmail +unsubscribe --list --max 200\n\n# Unsubscribe from a specific sender\ngws gmail +unsubscribe --from \"noreply@mail.michaelpage.com.sg\"\n\n# Dry run\ngws gmail +unsubscribe --from \"team@mail.cerebralvalley.ai\" --dry-run\n```\n\n## Why this matters for agents\n\nThe existing `gws-gmail-triage` skill is great for reading, but there's no skill for the common \"clean up my inbox subscriptions\" workflow. Without a dedicated skill, agents waste many API calls fumbling through message formats, header extraction, and URL guessing \u2014 exactly the kind of boilerplate `gws` is designed to eliminate.\n\n## References\n\n- [RFC 8058 \u2014 Signaling One-Click Functionality for List Email Headers](https://datatracker.ietf.org/doc/html/rfc8058)\n- [Gmail bulk sender requirements (June 2024)](https://support.google.com/a/answer/81126)", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPPVw", + "name": "area: skills", + "description": "", + "color": "7057ff" + } + ], + "number": 114, + "title": "Add a gmail +unsubscribe skill for one-click list unsubscribe (RFC 8058)", + "updatedAt": "2026-03-06T07:16:56Z" + }, + { + "assignees": [], + "body": "Hi! I love skills. But I also hate how they create issues for agents.\n\nContext pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem with this is that the top level skills all get loaded into , and 40 of them, added to any other skills that people use, will make for a really bad experience.\n\nObviously, one might say that not all of them need to be installed at once, but with how heavy the cross-references are, basically none of them are skippable.\n\nProposal:\n\nEven if it costs some duplication, bundle the granular skills in a top level structure, with /references/ folder.\n\nI believe this will save both the users and the devs a lot of headaches, and it will be easier for users to manage a situation where they might only need parts of the included skills.\n\n", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPPVw", + "name": "area: skills", + "description": "", + "color": "7057ff" + } + ], + "number": 82, + "title": "Optimisation: 40 skills => less skills + references", + "updatedAt": "2026-03-06T01:20:35Z" + }, + { + "assignees": [], + "body": "I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back.\n\nRelease 0.7.0 says that multiple accounts were removed. With no reason why.\n\nMultiple accounts can be handy but ESPECIALLY in the age of AI agents. I need to be able to assign my agent account (created in Google) to be able to take actions. And by using the agent account credentials, it becomes auditable. \n\nAnd access can be switched off at a moments notice.\n\nI would say that being able to have multiple agents is a security concern, because it enables proper separation of permissions.\n\nPlease bring it back", + "labels": [], + "number": 439, + "title": "Please restore multiple accounts being able to be used", + "updatedAt": "2026-03-13T00:31:55Z" + }, + { + "assignees": [], + "body": "Hi, \nAfter upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory API on my GCP project and on my auth scopes\n\n\"Image\"", + "labels": [], + "number": 430, + "title": "Missing admin directory SDK on the newest version?", + "updatedAt": "2026-03-14T11:13:10Z" + }, + { + "assignees": [], + "body": "Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: \nsrc/fs_util.rs\n, \nsrc/credential_store.rs\n, \nsrc/oauth_config.rs\n\nDetails: The \ngws\n CLI uses \natomic_write\n and \natomic_write_async\n in \nsrc/fs_util.rs\n to persist sensitive information like client_secret.json and encrypted OAuth tokens (credentials.enc).\n\nCurrently, these functions write the data to a temporary file (e.g., client_secret.json.tmp) using the default system umask (often 0644 or 0666), and then rename it into place. The permissions are only tightened to 0600 after the file has been renamed (e.g., in oauth_config.rs:88).\n\nImpact: A local attacker running a script that watches the ~/.config/gws/ directory via inotify can open the temporary file or the renamed file before the chmod 0o600 is applied. This allows a low-privileged user on a shared system to exfiltrate the victim's Google Workspace plaintext OAuth client secrets or encrypted credential material.\n\nProposed Fix: The temporary file must be created with secure 0o600 permissions atomically at creation time using std::os::unix::fs::OpenOptionsExt, completely eliminating the TOCTOU window. I have a patch ready to submit", + "labels": [], + "number": 401, + "title": "TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage", + "updatedAt": "2026-03-11T11:04:41Z" + }, + { + "assignees": [], + "body": "## Summary\n\nThe repository currently has no issue templates (only a PR template). As the project grows, issue templates would help contributors file consistent, actionable reports for bugs, feature requests, and docs improvements \u2014 reducing back-and-forth for maintainers.\n\n## Suggested templates\n\n- **Bug report** \u2014 reproduction steps, expected vs actual behavior, OS/version info\n- **Feature request** \u2014 problem description, proposed solution\n- **Docs improvement** \u2014 which doc, what's missing or wrong\n\nThese could live in `.github/ISSUE_TEMPLATE/` as standard GitHub issue forms (YAML) or plain markdown templates.\n\n## Bonus: structured input for AI agents\n\nGiven that this project explicitly targets AI agents as users, consistent issue structure also creates a reliable input format for automation. Skills could be built around issue triage, reproduction, or even implementation \u2014 but only if issues follow a predictable schema. Issue templates are a low-effort way to enable that.", + "labels": [], + "number": 400, + "title": "meta: add GitHub issue templates", + "updatedAt": "2026-03-11T10:15:47Z" + }, + { + "assignees": [], + "body": "This package contains a lot of skills. \n\nConsider implementing support for a skill.json file to make them more manageable and discoverable.\n\nsee https://github.com/automaton-labs/skill.json", + "labels": [], + "number": 356, + "title": "Consider making skills more discoverable with skill.json.", + "updatedAt": "2026-03-10T00:21:46Z" + }, + { + "assignees": [], + "body": "I see the agents flail around trying to find a document in a shared drive and I often have to stop it and say \"the data is in a shared drive\". Not sure if that can be a note in the skills document to mention \"if file is not found, try `supportsAllDrives: true`\"", + "labels": [], + "number": 327, + "title": "update skills document to mention shared drives (supportsAllDrives: true)", + "updatedAt": "2026-03-09T14:58:51Z" + }, + { + "assignees": [], + "body": "## Why\n\n`gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering available services/resources/methods without referring to `--help` each time.\n\n## What\n\nAdd shell completion generation for major shells:\n- bash\n- zsh\n- fish\n\nIdeally via a subcommand like `gws completion ` that outputs the completion script to stdout, following the common CLI pattern (e.g., `gh completion`, `kubectl completion`).\n\n## How\n\n- Top-level services (`drive`, `sheets`, `gmail`, `calendar`, etc.)\n- Resources per service (`files`, `spreadsheets`, `messages`, etc.)\n- Methods per resource (`list`, `get`, `create`, `update`, `delete`, etc.)\n- Global flags (`--params`, `--json`, `--format`, `--page-all`, etc.)", + "labels": [], + "number": 323, + "title": "feat: Add shell completion support (bash, zsh, fish)", + "updatedAt": "2026-03-09T13:24:20Z" + }, + { + "assignees": [], + "body": "gws auth login \r\n \"message\": \"Authentication successful. Encrypted credentials saved.\",\r\n \"scopes\": [\r\n \"https://www.googleapis.com/auth/drive\",\r\n \"https://www.googleapis.com/auth/spreadsheets\",\r\n \"https://www.googleapis.com/auth/gmail.modify\",\r\n \"https://www.googleapis.com/auth/calendar\",\r\n \"https://www.googleapis.com/auth/documents\",\r\n \"https://www.googleapis.com/auth/presentations\",\r\n \"https://www.googleapis.com/auth/tasks\",\r\n \"https://www.googleapis.com/auth/pubsub\",\r\n \"https://www.googleapis.com/auth/cloud-platform\",\r\n \"openid\",\r\n \"https://www.googleapis.com/auth/userinfo.email\"\r\n ],\r\n \"status\": \"success\"\r\n\r\nbut \r\ngws drive files list --params '{\"pageSize\": 5}'\r\n{\r\n \"error\": {\r\n \"code\": 500,\r\n \"message\": \"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found (tried both standard and $discovery URLs)\",\r\n \"reason\": \"discoveryError\"\r\n }\r\n}\r\n\r\n_Originally posted by @kaijung in https://github.com/googleworkspace/cli/discussions/257_", + "labels": [], + "number": 314, + "title": "Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found", + "updatedAt": "2026-03-09T18:29:01Z" + }, + { + "assignees": [], + "body": "I installed the cli and then ran the `gws auth setup` and it returned\n\n\"Image\"\n\nis it expected?", + "labels": [], + "number": 309, + "title": "bug: Error on doing the steps in readme", + "updatedAt": "2026-03-08T10:39:06Z" + }, + { + "assignees": [], + "body": "**Environment**\n- OS: Windows\n- Node.js: compatible with the package requirement (>=18)\n- npm: global install\n\n**What I ran**\n```\nnpm uninstall -g @googleworkspace/cli\nnpm cache clean --force\nnpm install -g @googleworkspace/cli\ngws auth setup\n```\n\n**What happened**\nThe command fails with:\n\n```\nError: spawnSync C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe ENOENT\n at Object.spawnSync (node:internal/child_process:1119:20)\n at spawnSync (node:child_process:911:24)\n at C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\binary-install.js:197:24\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {\n errno: -4058,\n code: 'ENOENT',\n syscall: 'spawnSync C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n path: 'C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n spawnargs: [ 'auth', 'setup' ]\n}\n```\n\n**Verification**\nI checked whether the expected binary exists:\n\n```\nTest-Path \"C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe\"\n```\n\nResult:\n```\nFalse\n```\n\nSo the package appears to install successfully via npm, but the actual Windows binary is missing from the location the wrapper expects.\n\n**Expected behavior**\n`npm install -g @googleworkspace/cli` should install or place `gws.exe` in the expected path so `gws auth setup` can run.\n\n**Notes**\nThis looks like a packaging/install-script issue specific to Windows, rather than a Node version issue.", + "labels": [], + "number": 306, + "title": "Windows install is missing `gws.exe`, causing `gws auth setup` to fail with ENOENT", + "updatedAt": "2026-03-08T07:27:47Z" + }, + { + "assignees": [], + "body": "```\nnpm install @googleworkspace/cli@0.7.0\nnpm error code 1\nnpm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli\nnpm error command failed\nnpm error command sh -c node ./install.js\nnpm error Your glibc isn't compatible; trying static musl binary instead\nnpm error Downloading release from https://github.com/googleworkspace/cli/releases/download/v0.7.0/gws-x86_64-unknown-linux-musl.tar.gz\nnpm error Error fetching release: spawnSync tar ENOENT\nnpm error A complete log of this run can be found in: /tmp/cache/_logs/2026-03-06T22_08_26_142Z-debug-0.log\n```\n\nThis appears to be using musl when it should be using glibc.\n\nThis was running on Amazon Linux 2023.", + "labels": [], + "number": 284, + "title": "npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT", + "updatedAt": "2026-03-09T21:31:38Z" + }, + { + "assignees": [], + "body": "Hi,\n\nThanks for this project!\n\nI just tried to auth with all scopes and got\n\nSome requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of XXXXXXXXXX, see error details. Error 400: invalid_scope\n\nI'm not sure if the scope is invalid or it's because my gcloud project is missing this API or because I'm on enterprise account and do not have super admin permission.\n\n", + "labels": [], + "number": 258, + "title": "Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope", + "updatedAt": "2026-03-13T17:42:39Z" + }, + { + "assignees": [], + "body": "## Problem\n\nThere's no way to send a Gmail message with an attachment using `gws`. Two approaches fail:\n\n### 1. `--json` with base64url-encoded raw MIME \u2014 hits OS argument length limit\n\nThe documented approach for sending raw MIME (with `threadId`, `In-Reply-To`, `References` headers, and attachments) is to base64url-encode the entire message and pass it via `--json '{\"raw\": \"...\"}'`. But even a modest ~577KB PDF attachment produces a ~1MB base64 payload, which exceeds macOS's ~256KB `ARG_MAX` for command-line arguments:\n\n```\ngws-behav gmail users messages send --params '{\"userId\": \"me\"}' --json \"{\\\"threadId\\\": \\\"...\\\", \\\"raw\\\": \\\"$RAW\\\"}\"\n# => zsh: argument list too long: gws\n```\n\n### 2. `--upload` with raw MIME file \u2014 wrong content type\n\nGmail's `/upload/gmail/v1/users/me/messages/send` endpoint accepts `message/rfc822` for media uploads. But `--upload` wraps the file in `multipart/related` with an auto-detected content type, which Gmail rejects:\n\n```\ngws gmail users messages send --params '{\"userId\": \"me\", \"uploadType\": \"media\"}' --upload message.eml\n# => Media type 'multipart/related; boundary=gws_boundary_...' is not supported.\n```\n\n## Suggested fixes\n\nAny of these would unblock the use case:\n\n1. **`--json-file `** or **`--json @`** \u2014 read JSON body from a file (like `curl -d @file.json`)\n2. **`--json -`** \u2014 read JSON body from stdin\n3. **`--upload-type `** \u2014 let users override the content type for `--upload` (e.g. `--upload-type message/rfc822`)\n4. **Auto-detect `.eml` \u2192 `message/rfc822`** in `--upload` content type detection\n\nOptions 1 or 2 are the most general and would help any API method with large request bodies, not just Gmail.\n\n## Environment\n\n- gws 0.4.4\n- macOS Darwin 25.3.0 (arm64)\n\n## Related\n\n- #88 \u2014 first-class reply/forward support would also solve this at a higher level\n- #244 \u2014 OOM on large file uploads (related large-payload issue)", + "labels": [], + "number": 247, + "title": "Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)", + "updatedAt": "2026-03-12T01:03:30Z" + }, + { + "assignees": [], + "body": "Does this project plan to support browsing Google Photos on the CLI? It would be nice to do analyses on photos using LLMs.", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTQH5w", + "name": "area: discovery", + "description": "", + "color": "006b75" + } + ], + "number": 163, + "title": "Any plan to support Google Photos?", + "updatedAt": "2026-03-05T20:48:15Z" + }, + { + "assignees": [], + "body": "Add an option to make HITL / requests gateway so those of us that are using agents and want to gatekeep them for oauth and write actions can do it", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTPT1Q", + "name": "area: mcp", + "description": "", + "color": "c46210" + } + ], + "number": 158, + "title": "Add built in HITL that the agents can't edit in code", + "updatedAt": "2026-03-05T20:38:09Z" + }, + { + "assignees": [], + "body": "It would be convenient to be able to install this tool using Homebrew \ud83d\ude04 ", + "labels": [ + { + "id": "LA_kwDORcxyRs8AAAACaTP5NQ", + "name": "area: distribution", + "description": "", + "color": "e07be0" + } + ], + "number": 90, + "title": "Feature request: Homebrew Formula", + "updatedAt": "2026-03-05T20:38:12Z" + } +] diff --git a/deep_analysis_results.json b/deep_analysis_results.json new file mode 100644 index 00000000..1be18b88 --- /dev/null +++ b/deep_analysis_results.json @@ -0,0 +1,151 @@ +[ + { + "number": 439, + "title": "Please restore multiple accounts being able to be used", + "labels": [], + "updatedAt": "2026-03-13T00:31:55Z", + "body_snippet": "I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back. Release 0.7.0 says that multiple accounts were removed. With no reason why...." + }, + { + "number": 430, + "title": "Missing admin directory SDK on the newest version?", + "labels": [], + "updatedAt": "2026-03-14T11:13:10Z", + "body_snippet": "Hi, After upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory ..." + }, + { + "number": 401, + "title": "TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage", + "labels": [], + "updatedAt": "2026-03-11T11:04:41Z", + "body_snippet": "Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: src/fs_util.rs , src/credential_store.rs , src/oauth_config.rs Details: The gws CL..." + }, + { + "number": 356, + "title": "Consider making skills more discoverable with skill.json.", + "labels": [], + "updatedAt": "2026-03-10T00:21:46Z", + "body_snippet": "This package contains a lot of skills. Consider implementing support for a skill.json file to make them more manageable and discoverable. see https://github.com/automaton-labs/skill.json..." + }, + { + "number": 323, + "title": "feat: Add shell completion support (bash, zsh, fish)", + "labels": [], + "updatedAt": "2026-03-09T13:24:20Z", + "body_snippet": "## Why `gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering..." + }, + { + "number": 314, + "title": "Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found", + "labels": [], + "updatedAt": "2026-03-09T18:29:01Z", + "body_snippet": "gws auth login \r \"message\": \"Authentication successful. Encrypted credentials saved.\",\r \"scopes\": [\r \"https://www.googleapis.com/auth/drive\",\r \"https://www.googleapis.com/auth/spreadsheets..." + }, + { + "number": 309, + "title": "bug: Error on doing the steps in readme", + "labels": [], + "updatedAt": "2026-03-08T10:39:06Z", + "body_snippet": "I installed the cli and then ran the `gws auth setup` and it returned \"Image\"=18) - npm: global install **What I ran** ``` npm uninstall -g @googleworkspace/cli npm cache clean --force npm inst..." + }, + { + "number": 284, + "title": "npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT", + "labels": [], + "updatedAt": "2026-03-09T21:31:38Z", + "body_snippet": "``` npm install @googleworkspace/cli@0.7.0 npm error code 1 npm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli npm error command failed npm error command sh -c node ...." + }, + { + "number": 258, + "title": "Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope", + "labels": [], + "updatedAt": "2026-03-13T17:42:39Z", + "body_snippet": "Hi, Thanks for this project! I just tried to auth with all scopes and got Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of X..." + }, + { + "number": 247, + "title": "Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)", + "labels": [], + "updatedAt": "2026-03-12T01:03:30Z", + "body_snippet": "## Problem There's no way to send a Gmail message with an attachment using `gws`. Two approaches fail: ### 1. `--json` with base64url-encoded raw MIME \u2014 hits OS argument length limit The documented..." + }, + { + "number": 205, + "title": "Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials", + "labels": [ + "area: auth" + ], + "updatedAt": "2026-03-06T01:20:33Z", + "body_snippet": "gws version: 0.4.4 OS: macOS (Apple Silicon, MacBook Pro) Account type: Personal @gmail.com Description After authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://..." + }, + { + "number": 197, + "title": "Doesn't support Advanced Protection Program users", + "labels": [ + "area: auth" + ], + "updatedAt": "2026-03-14T00:53:12Z", + "body_snippet": "This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication: less skills + references", + "labels": [ + "area: skills" + ], + "updatedAt": "2026-03-06T01:20:35Z", + "body_snippet": "Hi! I love skills. But I also hate how they create issues for agents. Context pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem wi..." + } +] diff --git a/issues.json b/issues.json new file mode 100644 index 00000000..9d817f30 --- /dev/null +++ b/issues.json @@ -0,0 +1 @@ +[{"assignees":[],"body":"## Summary\n\nThe Admin SDK Directory API (`admin:directory_v1`) is one of the most commonly used Google Workspace APIs for managing users, groups, and organizational units. Currently, accessing it through `gws` requires using the `admin-reports` alias with a version override:\n\n```bash\ngws admin-reports --api-version directory_v1 groups list --params '{\"domain\":\"example.com\"}'\n```\n\nThis works but is unintuitive — `admin-reports` implies audit/usage reporting, not user/group management. A built-in `admin-directory` alias would make the CLI more discoverable and self-documenting.\n\n## Proposed Change\n\nAdd a new `ServiceEntry` to `src/services.rs`:\n\n```rust\nServiceEntry {\n aliases: &[\"admin-directory\"],\n api_name: \"admin\",\n version: \"directory_v1\",\n description: \"Manage users, groups, and organizational units\",\n},\n```\n\nThis would enable:\n\n```bash\ngws admin-directory groups list --params '{\"domain\":\"example.com\"}'\ngws admin-directory members list --params '{\"groupKey\":\"group@example.com\"}'\ngws admin-directory users get --params '{\"userKey\":\"user@example.com\"}'\n```\n\n## Context\n\n- The `admin-reports` alias already maps to `admin:reports_v1` — same API name, different version\n- The `--api-version directory_v1` workaround works correctly today\n- Adding this alias is backward-compatible (no existing aliases change)\n- The Directory API is core to Workspace administration and used heavily for RBAC, onboarding/offboarding, and compliance workflows","labels":[],"number":473,"title":"Add admin-directory as a built-in service alias for Admin SDK Directory API","updatedAt":"2026-03-13T16:17:37Z"},{"assignees":[],"body":"Hi I have defined to auto-add hangout in the ui, yet when creating a calendar event with the CLI it doesn't add the hangout. \nIs it possible to add support for respecting the parameter in the UI? Or at least provide the option in the CLI to add a Google Meet hangout. \nThanks! ","labels":[],"number":461,"title":"Calendar event is created without Hangout","updatedAt":"2026-03-13T09:20:24Z"},{"assignees":[],"body":"I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back.\n\nRelease 0.7.0 says that multiple accounts were removed. With no reason why.\n\nMultiple accounts can be handy but ESPECIALLY in the age of AI agents. I need to be able to assign my agent account (created in Google) to be able to take actions. And by using the agent account credentials, it becomes auditable. \n\nAnd access can be switched off at a moments notice.\n\nI would say that being able to have multiple agents is a security concern, because it enables proper separation of permissions.\n\nPlease bring it back","labels":[],"number":439,"title":"Please restore multiple accounts being able to be used","updatedAt":"2026-03-13T00:31:55Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent needs to read the content of Gmail messages (e.g., to triage an inbox, summarize emails, or decide on actions), it must use the raw API:\n\n```bash\ngws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"...\", \"format\": \"full\"}'\n```\n\nThis returns the entire message payload including:\n- All email headers (Received, DKIM-Signature, ARC chains, SPF, etc.)\n- Base64-encoded MIME parts\n- Multipart boundaries and nested structure\n- Inline image attachments\n\nFor a typical email, this produces 10–60KB of JSON, most of which is noise. The agent then has to locate the correct MIME part, base64-decode it, and extract readable text — all of which is error-prone and wastes context window.\n\nBy contrast, `+triage` cleanly abstracts listing messages, but there is no equivalent for *reading* a single message body.\n\n## Proposal\n\nAdd a `gws gmail +read` helper that extracts the human-readable body from a message:\n\n```bash\n# Read a message body as plain text\ngws gmail +read --id \n\n# Read with metadata (from, to, subject, date)\ngws gmail +read --id --headers\n\n# JSON output for agent consumption\ngws gmail +read --id --format json\n```\n\nThe helper would:\n1. Fetch the message via `users.messages.get`\n2. Walk the MIME tree to find the `text/plain` part (or fall back to `text/html` → stripped text)\n3. Base64-decode and return clean, readable content\n4. Optionally include key headers (From, To, Subject, Date, Cc)\n\n### Example JSON output\n\n```json\n{\n \"id\": \"19ce2251577a09ea\",\n \"from\": \"Jira automation \",\n \"to\": \"gvilches@squarespace.com\",\n \"cc\": \"bkimmel@squarespace.com, tlong@squarespace.com\",\n \"subject\": \"[ACTION REQUIRED] Your COE violates 14-Day Incident SLA\",\n \"date\": \"Thu, 12 Mar 2026 13:03:25 +0000\",\n \"body\": \"George Vilches,\\n\\nAs the current Assignee for the Incident ticket COE-5367...\"\n}\n```\n\n## Why this matters for agents\n\n`+triage` answers \"what's in my inbox?\" but agents immediately need to follow up with \"what does this email say?\" to make decisions (e.g., does this need a reply? is this actionable?). Today that requires a raw API call that returns massive payloads full of transport headers and encoded content. This is exactly the kind of complexity that helpers are designed to abstract — similar to how `+send` abstracts RFC 2822 encoding.\n\nThe helpers README states helpers should be added when they \"abstract away significant complexity\" — MIME tree walking and base64 decoding for readable text extraction qualifies.","labels":[],"number":438,"title":"Feature request: Gmail +read helper for extracting message body as plain text","updatedAt":"2026-03-12T18:15:41Z"},{"assignees":[],"body":"Hi, \nAfter upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory API on my GCP project and on my auth scopes\n\n\"Image\"","labels":[],"number":430,"title":"Missing admin directory SDK on the newest version?","updatedAt":"2026-03-14T11:13:10Z"},{"assignees":[],"body":"Please consider adding a Gmail draft-only mode/policy to `gws`.\n\nDesired behavior:\n- allow reading Gmail as configured\n- allow creating/updating drafts\n- block all send paths\n\nIn particular, this mode should prevent:\n- `users.messages.send`\n- `users.drafts.send`\n- reply / reply-all / forward actions that result in sending\n- any equivalent helper/tool aliases\n\nA common agent workflow is: let the agent prepare Gmail drafts for human review, but guarantee that it cannot actually send mail.\n\nThat “draft yes, send no” boundary would be very useful for agent integrations.\n\nNotes:\n\nI understand this may depend on Gmail/OAuth/API limitations. If true Google-enforced draft-only permission is not currently possible, a `gws`-side policy/tool filter would still be very valuable.\n\nIf this request belongs in another repo/team, please feel free to redirect it.","labels":[],"number":424,"title":"Feature request: Gmail draft-only mode for CLI+agent workflows","updatedAt":"2026-03-12T06:28:52Z"},{"assignees":[],"body":"## Description\n\n`gws` commands fail in environments requiring HTTP proxy (e.g., users in China who need proxy to access Google APIs).\n\n## Problem\n\nThe underlying `yup-oauth2` and `hyper` HTTP clients don't respect standard proxy environment variables (`http_proxy`, `https_proxy`, `ALL_PROXY`).\n\n**Affected operations:**\n1. `gws auth login` - OAuth token exchange fails\n2. All API calls - token refresh fails\n\n**Error message:**\n```\n{\n \"error\": {\n \"code\": 401,\n \"message\": \"Authentication failed: Failed to get token: Connection failure: Hyper error: client error (Connect): tcp connect error: Operation timed out (os error 60)\",\n \"reason\": \"authError\"\n }\n}\n```\n\n## Environment\n- macOS 14.x (Sonoma)\n- gws 0.11.1\n- Proxy: http://127.0.0.1:7890\n\n## Current Workaround\n\n1. Manually exchange OAuth code using curl with proxy\n2. Save credentials to `~/.config/gws/credentials.json`\n3. Use `GOOGLE_WORKSPACE_CLI_TOKEN` env var with manually refreshed access token\n\nThis is cumbersome as tokens expire every hour.\n\n## Suggested Fix\n\n**Option 1:** Use `reqwest` with default features (includes proxy support):\n```toml\n# Cargo.toml - current\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\"], default-features = false }\n\n# Suggested - enable proxy support\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\", \"socks\"] }\n```\n\n**Option 2:** Pass a custom `hyper` client with proxy support to `yup-oauth2` using `.with_hyper_client()`.\n\n**Option 3:** Add explicit `--proxy` flag or `GWS_PROXY` / `GWS_HTTPS_PROXY` environment variable.\n\n## References\n- reqwest proxy support: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html\n- yup-oauth2 custom client support","labels":[],"number":422,"title":"Support HTTP proxy environment variables (http_proxy/https_proxy)","updatedAt":"2026-03-12T04:31:47Z"},{"assignees":[],"body":"## Problem\n\n`SKILL.md` files are auto-generated by `gws generate-skills` and contain API method listings. However, some services have non-obvious usage patterns that require hand-written guidance — particularly around:\n\n- **Google Docs tabs**: reading specific tabs (`includeTabsContent`), creating tabs (`addDocumentTab` vs the invalid `createTab`), writing to specific tabs (`tabId` in `location`/`range`)\n- **Formatted content insertion**: the insert-then-style pattern with `insertText` + `updateParagraphStyle`\n- **`+write` limitations**: no tab targeting, plain text only\n\nThese patterns are hard to discover from `gws schema` output alone and typically require trial-and-error.\n\n## Proposal\n\nAdd a `COOKBOOK.md` file alongside `SKILL.md` in skill directories that need hand-written recipes:\n\n\\`\\`\\`\nskills/gws-docs/\n SKILL.md ← auto-generated (unchanged)\n COOKBOOK.md ← hand-written recipes (new)\n\\`\\`\\`\n\nBenefits:\n- **Safe from regeneration** — `gws generate-skills` won't overwrite it\n- **Composable** — other skills can adopt the same pattern as needed\n- **Discoverable** — agents can check for COOKBOOK.md alongside SKILL.md\n\nOptional follow-up: teach `generate_skills.rs` to append a \"See also: COOKBOOK.md\" link when one exists.\n\n## Scope\n\nStarting with `gws-docs` as a proof of concept. The cookbook covers:\n1. Tab CRUD operations (read, create, write, rename, delete)\n2. Formatted content insertion (headings, bold/italic, paragraph styles)\n3. Valid `batchUpdate` request types reference table\n4. `+write` helper limitations and workarounds","labels":[],"number":420,"title":"docs(skills): add COOKBOOK.md pattern for hand-written recipes alongside generated SKILL.md","updatedAt":"2026-03-12T02:00:15Z"},{"assignees":[],"body":"Implement the ability to send meeting invites with video conferencing details.","labels":[],"number":419,"title":"Feature: Add video conferencing to meeting insert","updatedAt":"2026-03-11T22:49:33Z"},{"assignees":[],"body":"Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: \nsrc/fs_util.rs\n, \nsrc/credential_store.rs\n, \nsrc/oauth_config.rs\n\nDetails: The \ngws\n CLI uses \natomic_write\n and \natomic_write_async\n in \nsrc/fs_util.rs\n to persist sensitive information like client_secret.json and encrypted OAuth tokens (credentials.enc).\n\nCurrently, these functions write the data to a temporary file (e.g., client_secret.json.tmp) using the default system umask (often 0644 or 0666), and then rename it into place. The permissions are only tightened to 0600 after the file has been renamed (e.g., in oauth_config.rs:88).\n\nImpact: A local attacker running a script that watches the ~/.config/gws/ directory via inotify can open the temporary file or the renamed file before the chmod 0o600 is applied. This allows a low-privileged user on a shared system to exfiltrate the victim's Google Workspace plaintext OAuth client secrets or encrypted credential material.\n\nProposed Fix: The temporary file must be created with secure 0o600 permissions atomically at creation time using std::os::unix::fs::OpenOptionsExt, completely eliminating the TOCTOU window. I have a patch ready to submit","labels":[],"number":401,"title":"TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage","updatedAt":"2026-03-11T11:04:41Z"},{"assignees":[],"body":"## Summary\n\nThe repository currently has no issue templates (only a PR template). As the project grows, issue templates would help contributors file consistent, actionable reports for bugs, feature requests, and docs improvements — reducing back-and-forth for maintainers.\n\n## Suggested templates\n\n- **Bug report** — reproduction steps, expected vs actual behavior, OS/version info\n- **Feature request** — problem description, proposed solution\n- **Docs improvement** — which doc, what's missing or wrong\n\nThese could live in `.github/ISSUE_TEMPLATE/` as standard GitHub issue forms (YAML) or plain markdown templates.\n\n## Bonus: structured input for AI agents\n\nGiven that this project explicitly targets AI agents as users, consistent issue structure also creates a reliable input format for automation. Skills could be built around issue triage, reproduction, or even implementation — but only if issues follow a predictable schema. Issue templates are a low-effort way to enable that.","labels":[],"number":400,"title":"meta: add GitHub issue templates","updatedAt":"2026-03-11T10:15:47Z"},{"assignees":[],"body":"## Summary\n\nExpose a focused lib.rs so gws can be used as a Rust library dependency for programmatic access to Google Workspace APIs. This enables tools like CI integrations and custom automation to reuse the core logic without shelling out to the binary.\n\nRelated: #211, #376\n\n## Design Principles\n\n1. **Minimal surface area** — only expose modules that are stable and useful outside the CLI\n2. **No CLI coupling** — library consumers should not need clap, ratatui, or interactive OAuth flows\n3. **Facade over internals** — provide a clean execute() function rather than exposing executor.rs internals\n\n## Proposed Public API\n\n### Modules to expose (pub mod)\n\n| Module | Purpose |\n|---|---|\n| discovery | RestDescription, RestMethod, RestResource — introspect Google APIs |\n| error | GwsError — unified error type |\n| config | config_dir() — resolve config directory |\n| services | resolve_service(), SERVICES, select_scope() — service name to API mapping |\n| validate | validate_api_identifier(), validate_gcp_location() — input safety |\n| client | build_client(), send_with_retry() — HTTP with retry |\n\n### Modules to keep internal (pub(crate))\n\n| Module | Why |\n|---|---|\n| auth, auth_commands | CLI-specific OAuth flow (browser redirect, token caching) |\n| executor | Takes ArgMatches — CLI orchestration |\n| helpers/* | Each helper parses ArgMatches directly |\n| formatter | Presentation concern (JSON/CSV output) |\n| setup, setup_tui | Interactive TUI |\n| credential_store | Internal key storage detail |\n\n### Programmatic execution facade (future)\n\nFor users who need to execute API methods programmatically:\n\n```rust\npub async fn execute(\n service: &str,\n resource_path: &[&str], // [\"files\", \"list\"]\n method: &str, // \"list\", \"get\", \"create\"\n params: &HashMap,\n body: Option,\n auth: AuthToken,\n) -> Result\n```\n\nThis keeps CLI internals hidden while providing a clean interface. The AuthToken wrapper would accept a pre-obtained token string, decoupling from the CLI's OAuth flow.\n\n## Non-goals\n\n- Exposing the TUI or interactive setup flows\n- Guaranteeing API stability for internal modules\n- Replacing the binary — the library is a complement, not a fork\n\n## Prior art\n\n- PR #376 by @geofflittle — good first attempt but exposes too many CLI-specific modules. The SSRF validation (validate_gcp_location) from that PR is independently valuable and should be extracted.\n- Issue #211 — original feature request","labels":[],"number":386,"title":"feat: expose minimal library crate for programmatic API access","updatedAt":"2026-03-10T20:11:26Z"},{"assignees":[],"body":"This package contains a lot of skills. \n\nConsider implementing support for a skill.json file to make them more manageable and discoverable.\n\nsee https://github.com/automaton-labs/skill.json","labels":[],"number":356,"title":"Consider making skills more discoverable with skill.json.","updatedAt":"2026-03-10T00:21:46Z"},{"assignees":[],"body":"I see the agents flail around trying to find a document in a shared drive and I often have to stop it and say \"the data is in a shared drive\". Not sure if that can be a note in the skills document to mention \"if file is not found, try `supportsAllDrives: true`\"","labels":[],"number":327,"title":"update skills document to mention shared drives (supportsAllDrives: true)","updatedAt":"2026-03-09T14:58:51Z"},{"assignees":[],"body":"## Why\n\n`gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering available services/resources/methods without referring to `--help` each time.\n\n## What\n\nAdd shell completion generation for major shells:\n- bash\n- zsh\n- fish\n\nIdeally via a subcommand like `gws completion ` that outputs the completion script to stdout, following the common CLI pattern (e.g., `gh completion`, `kubectl completion`).\n\n## How\n\n- Top-level services (`drive`, `sheets`, `gmail`, `calendar`, etc.)\n- Resources per service (`files`, `spreadsheets`, `messages`, etc.)\n- Methods per resource (`list`, `get`, `create`, `update`, `delete`, etc.)\n- Global flags (`--params`, `--json`, `--format`, `--page-all`, etc.)","labels":[],"number":323,"title":"feat: Add shell completion support (bash, zsh, fish)","updatedAt":"2026-03-09T13:24:20Z"},{"assignees":[],"body":"The OAuth scopes are not properly ordered in the list. \n\nIt looks like it is ordered by risk levels. Maybe we neeed to provide options to reorder this - product-wise, access-wise (read / write / modify / delete), and then risk-wise. \n\n\"Image\"\n\n","labels":[],"number":319,"title":"OAuth scopes are not properly ordered","updatedAt":"2026-03-09T05:42:41Z"},{"assignees":[],"body":"## Problem\n\nCurrently all error output from `gws` is raw JSON, which is hard to read in an interactive terminal session:\n\n```json\n{\"error\":{\"code\":404,\"message\":\"Not Found\",\"reason\":\"notFound\"}}\n```\n\n## Proposal\n\nDetect whether stdout/stderr is a TTY and display human-friendly colored messages when running interactively:\n\n```\n✗ notFound (404): Not Found ← red bold\n⚠️ Model Armor: injection detected ← yellow bold\n💡 API not enabled for your project ← yellow bold\n```\n\nWhen piped or redirected, keep the existing JSON output for backward compatibility.\n\n## Implementation notes\n\n- Use `std::io::IsTerminal` (stable since Rust 1.70) for TTY detection\n- `colored` crate (or alternatively `anstream`/`anstyle` which are already indirect deps via clap) for ANSI colors\n- Error output goes to stderr; JSON pipe output stays on stdout\n- Zero behavior change for scripted/piped usage","labels":[],"number":317,"title":"feat: Display colored error/warning messages in TTY terminals","updatedAt":"2026-03-09T04:43:30Z"},{"assignees":[],"body":"gws auth login \r\n \"message\": \"Authentication successful. Encrypted credentials saved.\",\r\n \"scopes\": [\r\n \"https://www.googleapis.com/auth/drive\",\r\n \"https://www.googleapis.com/auth/spreadsheets\",\r\n \"https://www.googleapis.com/auth/gmail.modify\",\r\n \"https://www.googleapis.com/auth/calendar\",\r\n \"https://www.googleapis.com/auth/documents\",\r\n \"https://www.googleapis.com/auth/presentations\",\r\n \"https://www.googleapis.com/auth/tasks\",\r\n \"https://www.googleapis.com/auth/pubsub\",\r\n \"https://www.googleapis.com/auth/cloud-platform\",\r\n \"openid\",\r\n \"https://www.googleapis.com/auth/userinfo.email\"\r\n ],\r\n \"status\": \"success\"\r\n\r\nbut \r\ngws drive files list --params '{\"pageSize\": 5}'\r\n{\r\n \"error\": {\r\n \"code\": 500,\r\n \"message\": \"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found (tried both standard and $discovery URLs)\",\r\n \"reason\": \"discoveryError\"\r\n }\r\n}\r\n\r\n_Originally posted by @kaijung in https://github.com/googleworkspace/cli/discussions/257_","labels":[],"number":314,"title":"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found","updatedAt":"2026-03-09T18:29:01Z"},{"assignees":[],"body":"I installed the cli and then ran the `gws auth setup` and it returned\n\n\"Image\"\n\nis it expected?","labels":[],"number":309,"title":"bug: Error on doing the steps in readme","updatedAt":"2026-03-08T10:39:06Z"},{"assignees":[],"body":"**Environment**\n- OS: Windows\n- Node.js: compatible with the package requirement (>=18)\n- npm: global install\n\n**What I ran**\n```\nnpm uninstall -g @googleworkspace/cli\nnpm cache clean --force\nnpm install -g @googleworkspace/cli\ngws auth setup\n```\n\n**What happened**\nThe command fails with:\n\n```\nError: spawnSync C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe ENOENT\n at Object.spawnSync (node:internal/child_process:1119:20)\n at spawnSync (node:child_process:911:24)\n at C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\binary-install.js:197:24\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {\n errno: -4058,\n code: 'ENOENT',\n syscall: 'spawnSync C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n path: 'C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n spawnargs: [ 'auth', 'setup' ]\n}\n```\n\n**Verification**\nI checked whether the expected binary exists:\n\n```\nTest-Path \"C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe\"\n```\n\nResult:\n```\nFalse\n```\n\nSo the package appears to install successfully via npm, but the actual Windows binary is missing from the location the wrapper expects.\n\n**Expected behavior**\n`npm install -g @googleworkspace/cli` should install or place `gws.exe` in the expected path so `gws auth setup` can run.\n\n**Notes**\nThis looks like a packaging/install-script issue specific to Windows, rather than a Node version issue.","labels":[],"number":306,"title":"Windows install is missing `gws.exe`, causing `gws auth setup` to fail with ENOENT","updatedAt":"2026-03-08T07:27:47Z"},{"assignees":[],"body":"```\nnpm install @googleworkspace/cli@0.7.0\nnpm error code 1\nnpm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli\nnpm error command failed\nnpm error command sh -c node ./install.js\nnpm error Your glibc isn't compatible; trying static musl binary instead\nnpm error Downloading release from https://github.com/googleworkspace/cli/releases/download/v0.7.0/gws-x86_64-unknown-linux-musl.tar.gz\nnpm error Error fetching release: spawnSync tar ENOENT\nnpm error A complete log of this run can be found in: /tmp/cache/_logs/2026-03-06T22_08_26_142Z-debug-0.log\n```\n\nThis appears to be using musl when it should be using glibc.\n\nThis was running on Amazon Linux 2023.","labels":[],"number":284,"title":"npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT","updatedAt":"2026-03-09T21:31:38Z"},{"assignees":[],"body":"Hi team — I found a UX bug while trying the CLI:\n\n## Repro\n\n```bash\ngws auth setup --help\n```\n\nOn machines without `gcloud`, this executes setup and fails with:\n\n```json\n{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n}\n```\n\nExpected behavior is to print setup help/usage and exit successfully.\n\n## Proposed fix\n\nEarly-return from `run_setup()` when `-h/--help` is present, before any setup checks.\n\nI prepared a patch in my fork/branch:\n\n- Branch: `stablegenius49/cli:fix-auth-setup-help-flag`\n- Compare URL: https://github.com/googleworkspace/cli/compare/main...stablegenius49:cli:fix-auth-setup-help-flag\n\nI also added unit tests for the help flag detection.\n\nI couldn't open a PR directly because this repo currently blocks PR creation for non-collaborators (`CreatePullRequest` permission error), so sharing via issue + compare link.\n","labels":[],"number":280,"title":"{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n} executes setup instead of showing help","updatedAt":"2026-03-06T20:16:56Z"},{"assignees":[],"body":"Hi,\n\nThanks for this project!\n\nI just tried to auth with all scopes and got\n\nSome requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of XXXXXXXXXX, see error details. Error 400: invalid_scope\n\nI'm not sure if the scope is invalid or it's because my gcloud project is missing this API or because I'm on enterprise account and do not have super admin permission.\n\n","labels":[],"number":258,"title":"Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope","updatedAt":"2026-03-13T17:42:39Z"},{"assignees":[],"body":"## Problem\n\nThere's no way to send a Gmail message with an attachment using `gws`. Two approaches fail:\n\n### 1. `--json` with base64url-encoded raw MIME — hits OS argument length limit\n\nThe documented approach for sending raw MIME (with `threadId`, `In-Reply-To`, `References` headers, and attachments) is to base64url-encode the entire message and pass it via `--json '{\"raw\": \"...\"}'`. But even a modest ~577KB PDF attachment produces a ~1MB base64 payload, which exceeds macOS's ~256KB `ARG_MAX` for command-line arguments:\n\n```\ngws-behav gmail users messages send --params '{\"userId\": \"me\"}' --json \"{\\\"threadId\\\": \\\"...\\\", \\\"raw\\\": \\\"$RAW\\\"}\"\n# => zsh: argument list too long: gws\n```\n\n### 2. `--upload` with raw MIME file — wrong content type\n\nGmail's `/upload/gmail/v1/users/me/messages/send` endpoint accepts `message/rfc822` for media uploads. But `--upload` wraps the file in `multipart/related` with an auto-detected content type, which Gmail rejects:\n\n```\ngws gmail users messages send --params '{\"userId\": \"me\", \"uploadType\": \"media\"}' --upload message.eml\n# => Media type 'multipart/related; boundary=gws_boundary_...' is not supported.\n```\n\n## Suggested fixes\n\nAny of these would unblock the use case:\n\n1. **`--json-file `** or **`--json @`** — read JSON body from a file (like `curl -d @file.json`)\n2. **`--json -`** — read JSON body from stdin\n3. **`--upload-type `** — let users override the content type for `--upload` (e.g. `--upload-type message/rfc822`)\n4. **Auto-detect `.eml` → `message/rfc822`** in `--upload` content type detection\n\nOptions 1 or 2 are the most general and would help any API method with large request bodies, not just Gmail.\n\n## Environment\n\n- gws 0.4.4\n- macOS Darwin 25.3.0 (arm64)\n\n## Related\n\n- #88 — first-class reply/forward support would also solve this at a higher level\n- #244 — OOM on large file uploads (related large-payload issue)","labels":[],"number":247,"title":"Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)","updatedAt":"2026-03-12T01:03:30Z"},{"assignees":[],"body":"gws version: 0.4.4\nOS: macOS (Apple Silicon, MacBook Pro)\nAccount type: Personal @gmail.com\nDescription\nAfter authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects, all Google Drive API calls work correctly but all Apps Script API calls fail with 401 \"invalid authentication credentials.\"\nSteps to Reproduce\n\nInstall gws via npm: npm install -g @googleworkspace/cli\nPlace custom client_secret.json in ~/.config/gws/\nEnable both Google Drive API and Apps Script API in GCP project\nEnable Apps Script API at https://script.google.com/home/usersettings\nAuthenticate:\n\nbashgws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects\n\nExport unmasked credentials (required due to separate Keychain issue — see below):\n\nbashgws auth export --unmasked > ~/.config/gws/credentials.json\n\nVerify auth status shows correct scopes:\n\nbashgws auth status\n# Shows scope_count: 2, token_valid: true, both drive and script.projects scopes listed\n\nTest Drive API — works:\n\nbashgws drive files list --params '{\"pageSize\": 5, \"fields\": \"files(id,name,mimeType)\"}' --format table\n# Returns file listing successfully\n\nTest Apps Script API — fails:\n\nbashgws apps-script projects getContent --params '{\"scriptId\": \"VALID_SCRIPT_ID\"}'\n# Returns 401 error\nExpected Behavior\ngws apps-script projects getContent should return the script project content, since the credentials include the script.projects scope and the Apps Script API is enabled.\nActual Behavior\njson{\n \"error\": {\n \"code\": 401,\n \"message\": \"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\",\n \"reason\": \"unknown\"\n }\n}\nAdditional Context\nScope issue required revoking app access\nInitially, after adding script.projects to an existing auth session, the API returned 403 \"insufficient authentication scopes\" even though gws auth status showed the scope was present. This was resolved by revoking app access at https://myaccount.google.com/permissions and re-authenticating from scratch. After revoking, the error changed from 403 to 401.\nSeparate issue: encrypted credentials not usable for API calls\nOn macOS, gws auth login saves encrypted credentials via OS Keyring, but API calls (e.g., gws drive files list) fail with 401 \"No credentials provided\" unless you first run gws auth export --unmasked > ~/.config/gws/credentials.json to create a plaintext credentials file. The gws auth status command can read the encrypted credentials fine, but the API call code path cannot. This workaround resolves Drive API calls but does not resolve the Apps Script 401.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":205,"title":"Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials","updatedAt":"2026-03-06T01:20:33Z"},{"assignees":[],"body":"This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication:\n\n\"Image\"\n\nUnderstandably, this may just be a limitation of Advanced Protection, though it's always a bit of a confusing threat model that I can grant Claude connectors extensive access to my emails and Gdrive, but not read-only scopes for my own app.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":197,"title":"Doesn't support Advanced Protection Program users","updatedAt":"2026-03-14T00:53:12Z"},{"assignees":[],"body":"## Description\n\nWhen creating a comment on a Google Docs file using `gws drive comments create` with an `anchor` field, the comment is posted successfully (HTTP 200) but appears in the Google Docs UI as **\"Original content deleted\"** instead of being anchored to the specified text.\n\n## Steps to Reproduce\n\n```bash\ngws drive comments create \\\n --params '{\"fileId\": \"\", \"fields\": \"id,content,anchor,quotedFileContent\"}' \\\n --json '{\n \"content\": \"Hello\",\n \"quotedFileContent\": {\n \"mimeType\": \"text/plain\",\n \"value\": \"Overview\"\n },\n \"anchor\": \"{\\\"r\\\":\\\"head\\\",\\\"a\\\":[{\\\"lt\\\":\\\"b\\\",\\\"lv\\\":\\\"Overview\\\"}]}\"\n }'\n```\n\n## Expected Behavior\n\nThe comment should be anchored to the matching text in the document, or the CLI/docs should clearly indicate that anchored comments are not supported for Google Docs files via the API.\n\n## Actual Behavior\n\nThe API returns a 200 with the anchor saved, but the comment appears in the Google Docs UI as **\"Original content deleted\"** — no text is highlighted and the anchor has no visible effect.\n\n## Additional Context\n\n- This appears to be a known Google Drive API limitation: for Google Workspace editor files (Docs, Sheets, Slides), the `anchor` field is saved but ignored by the editor UI. See the [official docs](https://developers.google.com/workspace/drive/api/v3/manage-comments#add_an_anchored_comment_to_the_latest_revision_of_a_document): *\"The anchor is saved and returned when retrieving the comment, however Google Workspace editor apps treat these comments as un-anchored comments.\"*\n- It would be helpful if `gws` surfaced a warning when using `anchor` on a `application/vnd.google-apps.kix` (Google Docs) file, since the result is misleading.\n- `gws` version: `0.4.4`","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":169,"title":"drive comments create: anchor field results in 'Original content deleted' in Google Docs","updatedAt":"2026-03-05T20:38:24Z"},{"assignees":[],"body":"## Summary\nWhen authenticating with read-only scopes using `gws auth login --readonly`, exporting credentials with:\n\n```bash\ngws auth export --unmasked > credentials.json\n```\n\nand then using those credentials on another machine (OpenClaw), the resulting access appears to still allow full/write operations.\n\n## Reproduction\n1. Run:\n ```bash\n gws auth login --readonly\n ```\n2. Export credentials:\n ```bash\n gws auth export --unmasked > credentials.json\n ```\n3. Move `credentials.json` to an external machine/environment (OpenClaw).\n4. Use the exported credentials with `gws` there.\n5. Attempt a write operation (for example, Gmail send).\n\n## Actual behavior\nWrite/full-access behavior is still possible on the external machine, even though login was done with `--readonly`.\n\n## Expected behavior\nRead-only scope enforcement should persist with exported credentials and be enforced by Google-issued token scopes, not only by client-side command restrictions.\n\n## Questions\n- Are `--readonly` scopes currently enforced only in the local client flow, rather than in the OAuth token/scopes themselves?\n- If so, what is the recommended way to ensure exported credentials are truly read-only on external machines?\n- Can `--readonly` become the default behavior in the installer/setup flow (or at least a strongly guided default), not just a local/client-side option?\n\n## Why this matters\nUsers exporting credentials for remote execution expect least-privilege guarantees to carry over across machines/environments.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":168,"title":"gws auth login --readonly + auth export --unmasked appears to allow full access on external machine","updatedAt":"2026-03-12T12:31:16Z"},{"assignees":[],"body":"Does this project plan to support browsing Google Photos on the CLI? It would be nice to do analyses on photos using LLMs.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":163,"title":"Any plan to support Google Photos?","updatedAt":"2026-03-05T20:48:15Z"},{"assignees":[],"body":"Add an option to make HITL / requests gateway so those of us that are using agents and want to gatekeep them for oauth and write actions can do it","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPT1Q","name":"area: mcp","description":"","color":"c46210"}],"number":158,"title":"Add built in HITL that the agents can't edit in code","updatedAt":"2026-03-05T20:38:09Z"},{"assignees":[],"body":"I can't get `npx -y @googleworkspace/cli@latest auth login` to work properly. I triple checked the settings of my auth:\n* Status: testing\n* User type: external\n* Test user: includes myself\n* Client ID: Desktop\n\nThe only \"strange\" thing is that I can't set a redirect URI, which according to `auth setup` is necessary.\n\n`auth login` shows an URL, after browsing to it and approving all scopes on the \"... wants to access your Google Account\" page, I get this on a page https://accounts.google.com/info/unknownerror?...:\n\n\"Image\"","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":157,"title":"\"Something went wrong\" in oauth screen","updatedAt":"2026-03-05T20:37:48Z"},{"assignees":[],"body":"## Description\n\nOn Windows, the `.encryption_key` fallback path in `credential_store.rs` writes the AES-256-GCM encryption key as a plaintext file with no access controls applied.\n\nOn Unix, the key file is explicitly created with mode `0o600` (user-read/write only):\n\n```rust\n#[cfg(unix)]\n{\n use std::os::unix::fs::OpenOptionsExt;\n let mut options = std::fs::OpenOptions::new();\n options.write(true).create(true).truncate(true).mode(0o600);\n ...\n}\n#[cfg(not(unix))]\n{\n let _ = std::fs::write(&key_file, &b64_key); // no permission hardening\n}\n```\n\nOn Windows, `std::fs::write` is used with no equivalent ACL restrictions, meaning the key file inherits default directory permissions and is readable by other users or processes on the same machine.\n\nThis key decrypts `credentials..enc`, which contains the OAuth refresh token. An attacker with local read access can decrypt the token and gain long-lived Google account access.\n\n## Affected Code\n\nThree locations in `src/credential_store.rs` write sensitive files without Windows ACLs:\n\n1. **Line 112-115** — `.encryption_key` file (keyring `NoEntry` fallback)\n2. **Line 172-175** — `.encryption_key` file (keyring failure fallback)\n3. **Lines 246, 305** — `credentials.*.enc` files (via `atomic_write`)\n\nAdditionally, the config **directory** itself (`~/.config/gws/`) gets `0o700` on Unix (lines 89-99, 150-159) but no ACL on Windows.\n\n## Fix Plan\n\n### Option A: `windows-acl` crate (recommended)\n```toml\n[target.'cfg(windows)'.dependencies]\nwindows-acl = \"0.3\"\n```\n\nCreate a helper function:\n```rust\n#[cfg(windows)]\nfn set_owner_only_acl(path: &Path) -> std::io::Result<()> {\n use windows_acl::acl::ACL;\n // Get current user SID, set DACL to owner-only\n}\n```\n\nCall it after every file write on Windows (same locations where `#[cfg(unix)]` sets `0o600`).\n\n### Option B: Raw Win32 via `windows` crate\nUse `SetNamedSecurityInfoW` directly. More control but more boilerplate.\n\n### Option C: Interim warning\nPrint a warning on Windows when falling back to file storage:\n```\n⚠️ Warning: Encryption key stored without access controls. \n Run 'icacls %USERPROFILE%\\.config\\gws /inheritance:r /grant:r %USERNAME%:F' to restrict access.\n```\n\n## Files to Modify\n\n- `Cargo.toml` — add Windows-only dependency\n- `src/credential_store.rs` — add `#[cfg(windows)]` ACL blocks at all 3 write locations + directory creation\n- Optionally: extract a `set_secure_permissions(path)` helper that dispatches to `chmod 0o600` on Unix and ACL on Windows\n\n## Verification\n\n- Windows: `icacls ~/.config/gws/.encryption_key` should show only current user\n- Windows: `icacls ~/.config/gws/credentials.*.enc` should show only current user\n- Unix: behavior unchanged (`ls -la` confirms `0o600`)\n- CI: Windows test runner should exercise the keyring fallback path\n\n## Workaround\n\nUse Windows Credential Manager (the primary keyring path). There is currently no way to force this from the CLI. If keyring works, the file fallback is never used.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":153,"title":"[Security] Windows: encryption key written to plaintext file with no access controls","updatedAt":"2026-03-07T16:08:16Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent tries to help a user unsubscribe from mailing lists using `gws`, there is no guided path. The agent has to:\n\n1. Search for emails containing \"unsubscribe\"\n2. Fetch each message with `format: full` to extract `List-Unsubscribe` headers\n3. Guess whether to use the HTTPS URL or mailto address\n4. Attempt `curl` GET/POST requests to unsubscribe URLs (which mostly load confirmation pages requiring a browser)\n5. Fall back to sending emails to `mailto:` unsubscribe addresses via `gws gmail users messages send`\n\nThis entire process is trial-and-error, fragile, and often fails.\n\nMeanwhile, **Gmail natively supports one-click unsubscribe via RFC 8058**. Since June 2024, Google requires bulk senders to include `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers. The correct unsubscribe mechanism is a simple `POST` to the `List-Unsubscribe` HTTPS URL with the body `List-Unsubscribe=One-Click` — no browser needed, no mailto, no guessing.\n\n## Proposal\n\nAdd a `gws gmail +unsubscribe` helper skill that:\n\n1. **Lists candidates** — Scans recent emails for `List-Unsubscribe` and `List-Unsubscribe-Post` headers, groups by sender, and shows frequency/volume\n2. **Executes RFC 8058 one-click unsubscribe** — Sends the correct `POST` request with `List-Unsubscribe=One-Click` body to the HTTPS unsubscribe URL\n3. **Falls back gracefully** — For senders without RFC 8058 support, shows the mailto address or URL so the agent/user can decide\n\n### Example UX\n\n```bash\n# List subscription candidates with volume\ngws gmail +unsubscribe --list --max 200\n\n# Unsubscribe from a specific sender\ngws gmail +unsubscribe --from \"noreply@mail.michaelpage.com.sg\"\n\n# Dry run\ngws gmail +unsubscribe --from \"team@mail.cerebralvalley.ai\" --dry-run\n```\n\n## Why this matters for agents\n\nThe existing `gws-gmail-triage` skill is great for reading, but there's no skill for the common \"clean up my inbox subscriptions\" workflow. Without a dedicated skill, agents waste many API calls fumbling through message formats, header extraction, and URL guessing — exactly the kind of boilerplate `gws` is designed to eliminate.\n\n## References\n\n- [RFC 8058 — Signaling One-Click Functionality for List Email Headers](https://datatracker.ietf.org/doc/html/rfc8058)\n- [Gmail bulk sender requirements (June 2024)](https://support.google.com/a/answer/81126)","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":114,"title":"Add a gmail +unsubscribe skill for one-click list unsubscribe (RFC 8058)","updatedAt":"2026-03-06T07:16:56Z"},{"assignees":[],"body":"It would be convenient to be able to install this tool using Homebrew 😄 ","labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"}],"number":90,"title":"Feature request: Homebrew Formula","updatedAt":"2026-03-05T20:38:12Z"},{"assignees":[],"body":"Hi! I love skills. But I also hate how they create issues for agents.\n\nContext pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem with this is that the top level skills all get loaded into , and 40 of them, added to any other skills that people use, will make for a really bad experience.\n\nObviously, one might say that not all of them need to be installed at once, but with how heavy the cross-references are, basically none of them are skippable.\n\nProposal:\n\nEven if it costs some duplication, bundle the granular skills in a top level structure, with /references/ folder.\n\nI believe this will save both the users and the devs a lot of headaches, and it will be easier for users to manage a situation where they might only need parts of the included skills.\n\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":82,"title":"Optimisation: 40 skills => less skills + references","updatedAt":"2026-03-06T01:20:35Z"}] diff --git a/merged_prs.json b/merged_prs.json new file mode 100644 index 00000000..53ee5768 --- /dev/null +++ b/merged_prs.json @@ -0,0 +1 @@ +[{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":482,"title":"fix(gmail): RFC 2047 encode non-ASCII display names in address headers","updatedAt":"2026-03-13T23:22:00Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":481,"title":"chore: release versions","updatedAt":"2026-03-13T23:29:21Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":480,"title":"feat(timezone): use Google account timezone for day-boundary calculations","updatedAt":"2026-03-13T22:42:12Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":479,"title":"chore: release versions","updatedAt":"2026-03-13T21:31:40Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":478,"title":"feat: add opt-in structured HTTP request logging via tracing","updatedAt":"2026-03-13T21:29:07Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wUIxh","number":244,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/244"}],"number":477,"title":"fix: stream multipart uploads to avoid OOM on large files","updatedAt":"2026-03-13T20:58:27Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":475,"title":"chore: release versions","updatedAt":"2026-03-13T21:02:32Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1K7_","number":399,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/399"}],"number":467,"title":"docs: add Homebrew as an installation option in README","updatedAt":"2026-03-13T19:42:59Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":454,"title":"chore: sync skills with Discovery API","updatedAt":"2026-03-13T19:39:40Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":453,"title":"chore: release versions","updatedAt":"2026-03-13T20:01:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":451,"title":"fix(docs): correct broken flag names in recipes","updatedAt":"2026-03-13T00:15:15Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":449,"title":"chore: fix maintainer email typo in flake.nix and harden coverage.sh","updatedAt":"2026-03-13T00:18:58Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":445,"title":"test(gmail): add unit tests for +triage argument parsing","updatedAt":"2026-03-13T19:35:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":444,"title":"fix(security): validate space name in chat +send","updatedAt":"2026-03-13T19:57:27Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wVZBB","number":259,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/259"}],"number":443,"title":"fix(calendar): use local timezone for agenda day boundaries","updatedAt":"2026-03-13T19:17:55Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7x8C7Y","number":408,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/408"}],"number":441,"title":"fix(auth): validate --subscription in gmail +watch and deduplicate PUBSUB_API_BASE","updatedAt":"2026-03-12T20:28:27Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":440,"title":"chore: release versions","updatedAt":"2026-03-12T20:35:43Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":437,"title":"ci: avoid scope creep in gemini code assist reviews","updatedAt":"2026-03-12T18:43:20Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":436,"title":"chore: release versions","updatedAt":"2026-03-12T18:06:27Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xprae","number":389,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/389"}],"number":435,"title":"fix(auth): auto-recover from stale encrypted credentials after upgrade","updatedAt":"2026-03-12T17:59:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":434,"title":"chore: release versions","updatedAt":"2026-03-12T16:16:32Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":433,"title":"chore: release versions","updatedAt":"2026-03-12T16:07:07Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":429,"title":"feat: add --upload-content-type flag and smart MIME inference for uploads","updatedAt":"2026-03-13T23:10:34Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":428,"title":"feat(error): add structured exit codes for scriptable error handling","updatedAt":"2026-03-12T16:03:18Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":427,"title":"docs: document helper commands and the + prefix convention","updatedAt":"2026-03-12T16:06:14Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":417,"title":"feat(gmail): add --html flag for HTML email composition","updatedAt":"2026-03-12T18:34:10Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7w0s3N","number":300,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/300"}],"number":415,"title":"fix(schema): expose repeated field and expand array query params","updatedAt":"2026-03-13T19:14:58Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7w38zb","number":310,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/310"},{"id":"I_kwDORcxyRs7w_cbk","number":316,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/316"}],"number":414,"title":"fix(auth): map People service to contacts/directory scope prefixes","updatedAt":"2026-03-13T19:30:10Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wYcZh","number":268,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/268"}],"number":412,"title":"docs: warn about zsh ! history expansion in sheet range examples","updatedAt":"2026-03-12T16:16:07Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7w49i3","number":311,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/311"}],"number":410,"title":"fix(sheets): preserve multi-row structure in +append --json-values","updatedAt":"2026-03-13T19:13:58Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xq8wj","number":392,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/392"}],"number":407,"title":"fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops","updatedAt":"2026-03-12T20:16:11Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":390,"title":"chore: release versions","updatedAt":"2026-03-11T00:49:04Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":388,"title":"chore: Update workflow to use `GOOGLEWORKSPACE_BOT_TOKEN` and `googleworkspace-bot` identity for operations.","updatedAt":"2026-03-10T20:25:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":387,"title":"chore: release versions","updatedAt":"2026-03-10T20:18:05Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":385,"title":"fix(ci): check remote tags in tag-release.sh","updatedAt":"2026-03-10T20:06:17Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":384,"title":"ci: use bot token for /gemini review comment","updatedAt":"2026-03-10T19:23:55Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":373,"title":"fix(auth): enable native keyring backends on top of #359","updatedAt":"2026-03-12T15:59:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":362,"title":"feat(gmail): add recipient management flags and extract shared message builder","updatedAt":"2026-03-11T04:25:49Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":359,"title":"feat(credential_store): add GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND env var","updatedAt":"2026-03-11T17:18:29Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":353,"title":"fix(gmail): bring +forward behavior in line with Gmail web UI","updatedAt":"2026-03-10T00:32:12Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTXZE","number":236,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/236"}],"number":352,"title":"fix(auth): format and deduplicate dynamic scope fallback","updatedAt":"2026-03-09T23:42:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":351,"title":"chore: release versions","updatedAt":"2026-03-10T18:53:13Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":350,"title":"chore: release versions","updatedAt":"2026-03-09T23:24:04Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":349,"title":"ci: run smoketest immediately after linux build, gate other builds on it","updatedAt":"2026-03-09T22:04:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":348,"title":"chore: release versions","updatedAt":"2026-03-09T23:17:45Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":347,"title":"fix(ci): add missing tool input to install-action for cargo-llvm-cov","updatedAt":"2026-03-09T21:41:39Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":346,"title":"ci: make sccache setup resilient to cache API outages","updatedAt":"2026-03-09T21:00:12Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":345,"title":"fix(credential_store): stop persisting encryption key file when keyring is available","updatedAt":"2026-03-09T23:11:46Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":341,"title":"ci: pin all actions to a sha","updatedAt":"2026-03-09T19:52:51Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":339,"title":"ci: ignore sccache errors when GitHub down","updatedAt":"2026-03-09T18:54:08Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":331,"title":"build(ci): optimize nix and windows cache layers","updatedAt":"2026-03-09T17:54:17Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":330,"title":"chore: release versions","updatedAt":"2026-03-09T19:16:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":329,"title":"fix(ci): use default GITHUB_TOKEN for PR reviews","updatedAt":"2026-03-09T17:07:07Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":326,"title":"chore: Synced file(s) with googleworkspace/.github","updatedAt":"2026-03-09T17:47:49Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXIUW","number":266,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/266"}],"number":322,"title":"fix: RFC 2047 encode non-ASCII email subjects in +send helper","updatedAt":"2026-03-09T18:29:48Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":313,"title":"chore: release versions","updatedAt":"2026-03-09T00:30:40Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXH30","number":265,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/265"}],"number":304,"title":"fix: use gmail.readonly scope in +triage to avoid metadata scope 403","updatedAt":"2026-03-09T23:08:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":302,"title":"fix: use native OS certificate store for TLS","updatedAt":"2026-03-09T17:40:59Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":295,"title":"fix(auth): prioritize local project configuration over global ADC for quota attribution","updatedAt":"2026-03-09T00:21:02Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wlYBe","number":283,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/283"}],"number":288,"title":"fix: handle array-of-arrays in CSV formatter","updatedAt":"2026-03-11T00:46:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":277,"title":"chore: release versions","updatedAt":"2026-03-07T01:31:57Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":275,"title":"BREAKING CHANGE: Remove MCP server mode","updatedAt":"2026-03-10T00:11:58Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":254,"title":"chore: Remove a subset of skills and recipes along with their service entries and registry references","updatedAt":"2026-03-06T03:55:59Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":253,"title":"feat!: remove multi-account, DWD, and impersonation support","updatedAt":"2026-03-06T03:43:52Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":250,"title":"chore: Remove dwd support","updatedAt":"2026-03-06T03:26:28Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":243,"title":"chore: release versions","updatedAt":"2026-03-06T03:55:18Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":242,"title":"fix(client): Move x-goog-user-project header to API requests only","updatedAt":"2026-03-06T02:03:44Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wCYOn","number":152,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/152"}],"number":240,"title":"fix: use .zip for Windows npm installer to fix Git Bash install failure","updatedAt":"2026-03-06T02:02:31Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":228,"title":"chore: release versions","updatedAt":"2026-03-06T01:20:03Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wKfRa","number":181,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/181"},{"id":"I_kwDORcxyRs7wR6BO","number":221,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/221"}],"number":223,"title":"fix: respect account selection in MCP server and CLI --account flag","updatedAt":"2026-03-06T20:59:39Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wINp6","number":171,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/171"}],"number":222,"title":"docs: document all environment variables and enable CONFIG_DIR override","updatedAt":"2026-03-06T00:43:34Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":218,"title":"chore: release versions","updatedAt":"2026-03-05T23:35:37Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":217,"title":"fix: clean up nits from PR #175 auth fix","updatedAt":"2026-03-05T23:32:50Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":216,"title":"ci: add path filtering to skip expensive jobs on non-Rust changes","updatedAt":"2026-03-05T22:28:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":215,"title":"fix(client): For ADC, send x-goog-user-project header","updatedAt":"2026-03-05T23:45:28Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wQkNd","number":212,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/212"}],"number":213,"title":"fix(mcp): conditionally include body/upload in tool schemas, drop empty body on execution","updatedAt":"2026-03-06T00:30:41Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":209,"title":"ci: auto-trigger Gemini Code Assist review on PR push","updatedAt":"2026-03-05T21:54:05Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wPmsr","number":206,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/206"}],"number":208,"title":"fix: isolate flaky auth tests from host ADC credentials","updatedAt":"2026-03-05T23:00:24Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":207,"title":"chore: release versions","updatedAt":"2026-03-05T23:35:43Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":204,"title":"chore: auto triage and fmt","updatedAt":"2026-03-05T21:07:45Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":203,"title":"chore: use labels","updatedAt":"2026-03-05T20:49:46Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wJPN0","number":174,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/174"}],"number":200,"title":"fix: prevent gmail.metadata scope from blocking query parameters","updatedAt":"2026-03-05T23:34:50Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":199,"title":"chore: release versions","updatedAt":"2026-03-05T21:03:48Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":193,"title":"fix: log token cache errors instead of silently swallowing them","updatedAt":"2026-03-05T23:35:46Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":192,"title":"fix: replace strip_suffix(\".readonly\").unwrap() with unwrap_or","updatedAt":"2026-03-05T23:46:20Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":186,"title":"fix(setup): drain stale keypresses between TUI screen transitions","updatedAt":"2026-03-05T17:45:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":185,"title":"chore: steps to reduce contributor friction","updatedAt":"2026-03-05T17:08:53Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wK1fc","number":182,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/182"}],"number":183,"title":"fix(executor): add Content-Length: 0 for body-less POST requests","updatedAt":"2026-03-05T21:11:37Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":180,"title":"chore: release versions","updatedAt":"2026-03-05T17:56:18Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7v-Ciy","number":119,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/119"},{"id":"I_kwDORcxyRs7wAXev","number":138,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/138"}],"number":177,"title":"fix: allow services flag for login and improve default list for consumer accounts","updatedAt":"2026-03-05T16:16:09Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":176,"title":"chore(docs): easier cmd to install from source","updatedAt":"2026-03-05T16:22:23Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":175,"title":"Fix auth failures when accounts.json registry is missing","updatedAt":"2026-03-05T23:15:26Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":172,"title":"feat(mcp): add compact tool mode to reduce context window usage","updatedAt":"2026-03-05T16:23:45Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":154,"title":"fix: migrate to new Darwin SDK pattern in nix flake","updatedAt":"2026-03-06T02:22:25Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":147,"title":"chore: release versions","updatedAt":"2026-03-05T09:14:56Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAisd","number":139,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/139"}],"number":146,"title":"fix: use reverse video for TUI highlight style","updatedAt":"2026-03-05T09:12:40Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":142,"title":"chore: release versions","updatedAt":"2026-03-05T08:53:10Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":140,"title":"chore: release versions","updatedAt":"2026-03-05T08:44:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":136,"title":"docs: improve readme especially auth which is complex no matter what","updatedAt":"2026-03-05T08:48:50Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":134,"title":"fix: use ~/.config/gws on all platforms for consistent config path","updatedAt":"2026-03-05T08:31:03Z"}] diff --git a/open_prs.json b/open_prs.json new file mode 100644 index 00000000..e26bdb88 --- /dev/null +++ b/open_prs.json @@ -0,0 +1 @@ +[{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"},{"id":"LA_kwDORcxyRs8AAAACaTQCGw","name":"area: docs","description":"","color":"0052cc"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":488,"title":"feat(credential_store): default keyring backend to OS-only, add keyring-with-file option","updatedAt":"2026-03-14T09:51:54Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wHXwu","number":169,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/169"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":487,"title":"fix(drive): warn when anchor field targets Workspace editor files","updatedAt":"2026-03-14T05:59:31Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQFcg","name":"area: http","description":"","color":"d876e3"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":486,"title":"feat(http): add --verbose / -v flag for request/response diagnostics","updatedAt":"2026-03-14T06:13:57Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":485,"title":"fix(http): route error and empty-result messages to stderr","updatedAt":"2026-03-14T05:48:20Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":484,"title":"fix(validate): reject dangerous Unicode characters in input validation","updatedAt":"2026-03-14T05:44:11Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7ytu2a","number":473,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/473"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":476,"title":"Add admin-directory as built-in service alias for Admin SDK Directory API","updatedAt":"2026-03-13T20:22:40Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQCGw","name":"area: docs","description":"","color":"0052cc"}],"number":468,"title":"Revise OAuth login fix and consent screen instructions","updatedAt":"2026-03-13T11:18:18Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xABA3","number":317,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/317"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":466,"title":"feat(error): display colored error labels on TTY stderr","updatedAt":"2026-03-13T20:40:56Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yYHMc","number":438,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/438"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":465,"title":"feat(gmail): add +read helper for extracting message body as plain text","updatedAt":"2026-03-13T20:42:39Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yD8x1","number":419,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/419"},{"id":"I_kwDORcxyRs7yliQV","number":461,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/461"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":464,"title":"feat(calendar): add --conference flag to +insert for Google Meet links","updatedAt":"2026-03-13T20:42:52Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":463,"title":"fix(sheets): report error for invalid --json-values instead of silent empty append","updatedAt":"2026-03-13T20:43:08Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yJP70","number":424,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/424"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":460,"title":"feat: add `--draft-only` mode for agent workflows","updatedAt":"2026-03-14T05:48:40Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":457,"title":"feat: add --dry-run support to events helper commands","updatedAt":"2026-03-13T09:57:32Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQFcg","name":"area: http","description":"","color":"d876e3"}],"number":448,"title":"fix(security): cap Retry-After sleep and sanitize mimeType in uploads","updatedAt":"2026-03-13T20:43:43Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":447,"title":"fix(security): validate --upload and --output paths against traversal","updatedAt":"2026-03-13T20:43:57Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":446,"title":"fix: report auth errors instead of silently proceeding unauthenticated","updatedAt":"2026-03-13T20:44:09Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wkDt1","number":280,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/280"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":442,"title":"fix: `gws auth setup --help` prints usage instead of running wizard","updatedAt":"2026-03-13T20:44:23Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTQCGw","name":"area: docs","description":"","color":"0052cc"},{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":431,"title":"feat: support GOOGLE_WORKSPACE_CLI_API_BASE to override Google API base URL","updatedAt":"2026-03-12T16:00:46Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":426,"title":"feat(formatter): add --format tsv output format","updatedAt":"2026-03-13T04:24:40Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yHkZX","number":422,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/422"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"},{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"}],"number":423,"title":"feat: add HTTP proxy support via environment variables","updatedAt":"2026-03-14T05:54:47Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yFvFe","number":420,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/420"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":421,"title":"docs(skills): add COOKBOOK.md for gws-docs with tab and formatting recipes","updatedAt":"2026-03-12T02:11:56Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xAq2T","number":319,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/319"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":413,"title":"feat(auth): sort OAuth scopes by service, then access level","updatedAt":"2026-03-13T20:36:06Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xourR","number":386,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/386"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"},{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":397,"title":"feat: expose minimal library crate for programmatic API access","updatedAt":"2026-03-11T08:40:41Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":395,"title":"feat(gmail): add --attachment flag to +send, +reply, +reply-all, +forward","updatedAt":"2026-03-11T14:09:01Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTQFcg","name":"area: http","description":"","color":"d876e3"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":374,"title":"fix(security): validate --upload and --output file paths","updatedAt":"2026-03-11T03:57:06Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":318,"title":"[Night Shift] Fix +append --json-values multi-row bug","updatedAt":"2026-03-12T00:57:59Z"}] diff --git a/src/auth_commands.rs b/src/auth_commands.rs index f51ba6dd..f9969d35 100644 --- a/src/auth_commands.rs +++ b/src/auth_commands.rs @@ -178,6 +178,7 @@ pub async fn run_login(args: &[String]) -> Result<(), GwsError> { /// Optionally includes `login_hint` in the URL for account pre-selection. struct CliFlowDelegate { login_hint: Option, + force_consent: bool, } impl yup_oauth2::authenticator_delegate::InstalledFlowDelegate for CliFlowDelegate { @@ -189,7 +190,7 @@ impl yup_oauth2::authenticator_delegate::InstalledFlowDelegate for CliFlowDelega { Box::pin(async move { // Inject login_hint into the OAuth URL if we have one - let display_url = if let Some(ref hint) = self.login_hint { + let mut display_url = if let Some(ref hint) = self.login_hint { let encoded: String = percent_encoding::percent_encode( hint.as_bytes(), percent_encoding::NON_ALPHANUMERIC, @@ -203,6 +204,15 @@ impl yup_oauth2::authenticator_delegate::InstalledFlowDelegate for CliFlowDelega } else { url.to_string() }; + + // Inject prompt=consent if requested (for readonly enforcement) + if self.force_consent { + if display_url.contains('?') { + display_url.push_str("&prompt=consent"); + } else { + display_url.push_str("?prompt=consent"); + } + } eprintln!("Open this URL in your browser to authenticate:\n"); eprintln!(" {display_url}\n"); Ok(String::new()) @@ -296,6 +306,8 @@ async fn handle_login(args: &[String]) -> Result<(), GwsError> { .map_err(|e| GwsError::Validation(format!("Failed to create config directory: {e}")))?; } + let is_readonly = args.iter().any(|a| a == "--readonly"); + let auth = yup_oauth2::InstalledFlowAuthenticator::builder( secret, yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect, @@ -303,8 +315,11 @@ async fn handle_login(args: &[String]) -> Result<(), GwsError> { .with_storage(Box::new(crate::token_storage::EncryptedTokenStorage::new( temp_path.clone(), ))) - .force_account_selection(true) // Adds prompt=consent so Google always returns a refresh_token - .flow_delegate(Box::new(CliFlowDelegate { login_hint: None })) + .force_account_selection(true) + .flow_delegate(Box::new(CliFlowDelegate { + login_hint: None, + force_consent: is_readonly, + })) .build() .await .map_err(|e| GwsError::Auth(format!("Failed to build authenticator: {e}")))?; diff --git a/src/fs_util.rs b/src/fs_util.rs index b387565e..b028297c 100644 --- a/src/fs_util.rs +++ b/src/fs_util.rs @@ -14,7 +14,7 @@ //! File-system utilities. -use std::io; +use std::io::{self, Write}; use std::path::Path; /// Write `data` to `path` atomically. @@ -40,7 +40,20 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> io::Result<()> { .map(|p| p.join(&tmp_name)) .unwrap_or_else(|| std::path::PathBuf::from(&tmp_name)); - std::fs::write(&tmp_path, data)?; + let mut options = std::fs::OpenOptions::new(); + options.write(true).create(true).truncate(true); + + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.mode(0o600); + } + + let mut file = options.open(&tmp_path)?; + file.write_all(data)?; + file.sync_all()?; // Ensure bits hit disk before rename + drop(file); + std::fs::rename(&tmp_path, path)?; Ok(()) } @@ -56,7 +69,21 @@ pub async fn atomic_write_async(path: &Path, data: &[u8]) -> io::Result<()> { .map(|p| p.join(&tmp_name)) .unwrap_or_else(|| std::path::PathBuf::from(&tmp_name)); - tokio::fs::write(&tmp_path, data).await?; + let mut options = tokio::fs::OpenOptions::new(); + options.write(true).create(true).truncate(true); + + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.mode(0o600); + } + + let mut file = options.open(&tmp_path).await?; + use tokio::io::AsyncWriteExt; + file.write_all(data).await?; + file.sync_all().await?; + drop(file); + tokio::fs::rename(&tmp_path, path).await?; Ok(()) } @@ -99,4 +126,17 @@ mod tests { atomic_write_async(&path, b"async hello").await.unwrap(); assert_eq!(fs::read(&path).unwrap(), b"async hello"); } + + #[cfg(unix)] + #[test] + fn test_atomic_write_permissions() { + use std::os::unix::fs::MetadataExt; + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("secure.txt"); + atomic_write(&path, b"secret").unwrap(); + let metadata = fs::metadata(&path).unwrap(); + let mode = metadata.mode(); + // Check that mode & 0o777 is 0o600 + assert_eq!(mode & 0o777, 0o600); + } } diff --git a/src/services.rs b/src/services.rs index 40a4b816..6698fdb1 100644 --- a/src/services.rs +++ b/src/services.rs @@ -54,6 +54,12 @@ pub const SERVICES: &[ServiceEntry] = &[ version: "reports_v1", description: "Audit logs and usage reports", }, + ServiceEntry { + aliases: &["admin-directory", "directory"], + api_name: "admin", + version: "directory_v1", + description: "Manage users, groups, and devices", + }, ServiceEntry { aliases: &["docs"], api_name: "docs", diff --git a/src/setup_tui.rs b/src/setup_tui.rs index 67591526..204d99db 100644 --- a/src/setup_tui.rs +++ b/src/setup_tui.rs @@ -30,6 +30,7 @@ use ratatui::{ widgets::{Block, Borders, List, ListItem, ListState, Paragraph}, DefaultTerminal, }; +use ratatui::prelude::Stylize; use std::io::stdout; /// An item in the multi-select list. From 989e8a66d7d254b235d9eb7cd291af4e8e8b17f0 Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sun, 15 Mar 2026 22:52:42 +0530 Subject: [PATCH 2/7] refactor(auth): robustly inject prompt=consent for readonly enforcement and fix warnings --- src/auth_commands.rs | 12 +++++++++++- src/fs_util.rs | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/auth_commands.rs b/src/auth_commands.rs index f9969d35..dc231ac8 100644 --- a/src/auth_commands.rs +++ b/src/auth_commands.rs @@ -207,7 +207,17 @@ impl yup_oauth2::authenticator_delegate::InstalledFlowDelegate for CliFlowDelega // Inject prompt=consent if requested (for readonly enforcement) if self.force_consent { - if display_url.contains('?') { + if let Some(pos) = display_url.find("prompt=") { + let end_pos = display_url[pos..] + .find('&') + .map(|i| pos + i) + .unwrap_or(display_url.len()); + let current_prompt = &display_url[pos + 7..end_pos]; + if !current_prompt.contains("consent") { + let new_prompt = format!("{}%20consent", current_prompt); + display_url.replace_range(pos + 7..end_pos, &new_prompt); + } + } else if display_url.contains('?') { display_url.push_str("&prompt=consent"); } else { display_url.push_str("?prompt=consent"); diff --git a/src/fs_util.rs b/src/fs_util.rs index b028297c..5dd1f03a 100644 --- a/src/fs_util.rs +++ b/src/fs_util.rs @@ -74,7 +74,6 @@ pub async fn atomic_write_async(path: &Path, data: &[u8]) -> io::Result<()> { #[cfg(unix)] { - use std::os::unix::fs::OpenOptionsExt; options.mode(0o600); } From 8296366a97eeeceb4e7fdf808f2375ef22bdc6a1 Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sun, 15 Mar 2026 23:02:01 +0530 Subject: [PATCH 3/7] chore: add changeset for issue 168 and admin-directory --- .changeset/fix-issue-168-readonly-scope.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-issue-168-readonly-scope.md diff --git a/.changeset/fix-issue-168-readonly-scope.md b/.changeset/fix-issue-168-readonly-scope.md new file mode 100644 index 00000000..e802e0c7 --- /dev/null +++ b/.changeset/fix-issue-168-readonly-scope.md @@ -0,0 +1,5 @@ +--- +'gws': patch +--- + +fix(auth): robustly enforce readonly scopes by injecting prompt=consent and add admin-directory service alias From 63dbda43f2d867e89a1c7a6d5561ca465d81839a Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sun, 15 Mar 2026 23:02:13 +0530 Subject: [PATCH 4/7] chore: minor import cleanup in setup_tui --- src/setup_tui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup_tui.rs b/src/setup_tui.rs index 204d99db..75087cf9 100644 --- a/src/setup_tui.rs +++ b/src/setup_tui.rs @@ -23,6 +23,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, }; +use ratatui::prelude::Stylize; use ratatui::{ layout::{Constraint, Layout}, style::{Color, Modifier, Style}, @@ -30,7 +31,6 @@ use ratatui::{ widgets::{Block, Borders, List, ListItem, ListState, Paragraph}, DefaultTerminal, }; -use ratatui::prelude::Stylize; use std::io::stdout; /// An item in the multi-select list. From 7e14e1f61ff7916d2041dbbafbdd6261d4200e9e Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sun, 15 Mar 2026 23:12:40 +0530 Subject: [PATCH 5/7] fix(auth): robustly enforce readonly scopes by injecting prompt=consent --- .changeset/fix-issue-168-readonly-scope.md | 2 +- Cargo.lock | 823 +++++++++++++++++---- Cargo.toml | 3 +- src/auth_commands.rs | 2 +- src/fs_util.rs | 45 +- src/services.rs | 6 - src/setup_tui.rs | 1 - 7 files changed, 687 insertions(+), 195 deletions(-) diff --git a/.changeset/fix-issue-168-readonly-scope.md b/.changeset/fix-issue-168-readonly-scope.md index e802e0c7..c6e349e9 100644 --- a/.changeset/fix-issue-168-readonly-scope.md +++ b/.changeset/fix-issue-168-readonly-scope.md @@ -2,4 +2,4 @@ 'gws': patch --- -fix(auth): robustly enforce readonly scopes by injecting prompt=consent and add admin-directory service alias +fix(auth): robustly enforce readonly scopes by injecting prompt=consent diff --git a/Cargo.lock b/Cargo.lock index 91051795..0d55ea8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,7 +125,16 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", ] [[package]] @@ -146,6 +155,27 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" @@ -167,6 +197,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -179,12 +215,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "castaway" version = "0.2.4" @@ -196,9 +226,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -236,7 +266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", - "phf", + "phf 0.12.1", ] [[package]] @@ -280,7 +310,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -297,9 +327,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -368,35 +398,19 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags", - "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", - "rustix 1.1.4", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -422,6 +436,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf 0.11.3", +] + [[package]] name = "ctr" version = "0.9.2" @@ -437,8 +461,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -452,7 +486,20 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", ] [[package]] @@ -461,19 +508,36 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -491,10 +555,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -504,7 +568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.117", ] [[package]] @@ -526,7 +590,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.117", ] [[package]] @@ -568,7 +632,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -608,18 +672,60 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "euclid" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -632,6 +738,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -681,7 +793,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -784,7 +896,7 @@ dependencies = [ "chrono", "chrono-tz", "clap", - "crossterm 0.29.0", + "crossterm", "derive_builder", "dirs", "dotenvy", @@ -837,9 +949,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -847,6 +957,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -854,6 +969,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hostname" version = "0.4.2" @@ -1143,15 +1264,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" dependencies = [ - "darling", + "darling 0.23.0", "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1178,9 +1299,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -1201,6 +1322,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + [[package]] name = "keyring" version = "3.6.3" @@ -1215,6 +1347,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1243,10 +1381,13 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "line-clipping" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +dependencies = [ + "bitflags 2.11.0", +] [[package]] name = "linux-raw-sys" @@ -1283,11 +1424,11 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.12.5" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -1296,6 +1437,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1311,6 +1462,27 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "1.1.1" @@ -1323,6 +1495,29 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1334,9 +1529,20 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "num-traits" @@ -1386,6 +1592,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1409,25 +1624,114 @@ dependencies = [ "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared 0.11.3", +] + [[package]] name = "phf" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ - "phf_shared", + "phf_shared 0.12.1", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", ] [[package]] @@ -1463,6 +1767,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.4" @@ -1494,7 +1804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -1643,23 +1953,87 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" dependencies = [ - "bitflags", - "cassowary", + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termwiz", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.11.0", "compact_str", - "crossterm 0.28.1", + "hashbrown 0.16.1", "indoc", - "instability", "itertools", + "kasuari", "lru", - "paste", "strum", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.2.0", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -1668,7 +2042,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -1682,6 +2056,18 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -1769,29 +2155,16 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -1896,7 +2269,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -1909,7 +2282,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -1959,7 +2332,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2023,7 +2396,7 @@ checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2131,24 +2504,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", - "syn", + "syn 2.0.117", ] [[package]] @@ -2157,6 +2529,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -2185,7 +2568,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2197,10 +2580,73 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf 0.11.3", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64", + "bitflags 2.11.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf 0.11.3", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2227,7 +2673,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2238,7 +2684,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2252,9 +2698,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -2262,22 +2708,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2333,7 +2779,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2380,7 +2826,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.11.0", "bytes", "futures-util", "http", @@ -2435,7 +2881,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2502,6 +2948,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2516,26 +2968,20 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -2589,6 +3035,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "atomic", + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2601,6 +3059,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + [[package]] name = "want" version = "0.3.1" @@ -2680,7 +3147,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -2734,7 +3201,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -2760,6 +3227,78 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2803,7 +3342,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2814,7 +3353,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3093,7 +3632,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3109,7 +3648,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3121,7 +3660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.0", "indexmap", "log", "serde", @@ -3176,7 +3715,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3222,7 +3761,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3242,7 +3781,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3263,7 +3802,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3296,7 +3835,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b61b9694..24bc253b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ name = "gws" version = "0.16.0" edition = "2021" -rust-version = "1.87.0" description = "Google Workspace CLI — dynamic command surface from Discovery Service" license = "Apache-2.0" repository = "https://github.com/googleworkspace/cli" @@ -52,7 +51,7 @@ tokio-util = { version = "0.7", features = ["io"] } bytes = "1" base64 = "0.22.1" derive_builder = "0.20.2" -ratatui = "0.29.0" +ratatui = "0.30.0" crossterm = "0.29.0" chrono = "0.4.44" chrono-tz = "0.10" diff --git a/src/auth_commands.rs b/src/auth_commands.rs index dc231ac8..17da082f 100644 --- a/src/auth_commands.rs +++ b/src/auth_commands.rs @@ -325,7 +325,7 @@ async fn handle_login(args: &[String]) -> Result<(), GwsError> { .with_storage(Box::new(crate::token_storage::EncryptedTokenStorage::new( temp_path.clone(), ))) - .force_account_selection(true) + .force_account_selection(true) // Adds prompt=consent so Google always returns a refresh_token .flow_delegate(Box::new(CliFlowDelegate { login_hint: None, force_consent: is_readonly, diff --git a/src/fs_util.rs b/src/fs_util.rs index 5dd1f03a..b387565e 100644 --- a/src/fs_util.rs +++ b/src/fs_util.rs @@ -14,7 +14,7 @@ //! File-system utilities. -use std::io::{self, Write}; +use std::io; use std::path::Path; /// Write `data` to `path` atomically. @@ -40,20 +40,7 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> io::Result<()> { .map(|p| p.join(&tmp_name)) .unwrap_or_else(|| std::path::PathBuf::from(&tmp_name)); - let mut options = std::fs::OpenOptions::new(); - options.write(true).create(true).truncate(true); - - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - options.mode(0o600); - } - - let mut file = options.open(&tmp_path)?; - file.write_all(data)?; - file.sync_all()?; // Ensure bits hit disk before rename - drop(file); - + std::fs::write(&tmp_path, data)?; std::fs::rename(&tmp_path, path)?; Ok(()) } @@ -69,20 +56,7 @@ pub async fn atomic_write_async(path: &Path, data: &[u8]) -> io::Result<()> { .map(|p| p.join(&tmp_name)) .unwrap_or_else(|| std::path::PathBuf::from(&tmp_name)); - let mut options = tokio::fs::OpenOptions::new(); - options.write(true).create(true).truncate(true); - - #[cfg(unix)] - { - options.mode(0o600); - } - - let mut file = options.open(&tmp_path).await?; - use tokio::io::AsyncWriteExt; - file.write_all(data).await?; - file.sync_all().await?; - drop(file); - + tokio::fs::write(&tmp_path, data).await?; tokio::fs::rename(&tmp_path, path).await?; Ok(()) } @@ -125,17 +99,4 @@ mod tests { atomic_write_async(&path, b"async hello").await.unwrap(); assert_eq!(fs::read(&path).unwrap(), b"async hello"); } - - #[cfg(unix)] - #[test] - fn test_atomic_write_permissions() { - use std::os::unix::fs::MetadataExt; - let dir = tempfile::tempdir().unwrap(); - let path = dir.path().join("secure.txt"); - atomic_write(&path, b"secret").unwrap(); - let metadata = fs::metadata(&path).unwrap(); - let mode = metadata.mode(); - // Check that mode & 0o777 is 0o600 - assert_eq!(mode & 0o777, 0o600); - } } diff --git a/src/services.rs b/src/services.rs index 6698fdb1..40a4b816 100644 --- a/src/services.rs +++ b/src/services.rs @@ -54,12 +54,6 @@ pub const SERVICES: &[ServiceEntry] = &[ version: "reports_v1", description: "Audit logs and usage reports", }, - ServiceEntry { - aliases: &["admin-directory", "directory"], - api_name: "admin", - version: "directory_v1", - description: "Manage users, groups, and devices", - }, ServiceEntry { aliases: &["docs"], api_name: "docs", diff --git a/src/setup_tui.rs b/src/setup_tui.rs index 75087cf9..67591526 100644 --- a/src/setup_tui.rs +++ b/src/setup_tui.rs @@ -23,7 +23,6 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, }; -use ratatui::prelude::Stylize; use ratatui::{ layout::{Constraint, Layout}, style::{Color, Modifier, Style}, From 9c33fc51514ff562fc0b3b2f7838e52bd77ede82 Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sun, 15 Mar 2026 23:14:22 +0530 Subject: [PATCH 6/7] fix(auth): robustly enforce readonly scopes by injecting prompt=consent --- all_issues.json | 1 - all_prs.json | 1 - analysis_results.json | 256 ------------------------------------- deep_analysis_results.json | 151 ---------------------- issues.json | 1 - merged_prs.json | 1 - open_prs.json | 1 - 7 files changed, 412 deletions(-) delete mode 100644 all_issues.json delete mode 100644 all_prs.json delete mode 100644 analysis_results.json delete mode 100644 deep_analysis_results.json delete mode 100644 issues.json delete mode 100644 merged_prs.json delete mode 100644 open_prs.json diff --git a/all_issues.json b/all_issues.json deleted file mode 100644 index 9d817f30..00000000 --- a/all_issues.json +++ /dev/null @@ -1 +0,0 @@ -[{"assignees":[],"body":"## Summary\n\nThe Admin SDK Directory API (`admin:directory_v1`) is one of the most commonly used Google Workspace APIs for managing users, groups, and organizational units. Currently, accessing it through `gws` requires using the `admin-reports` alias with a version override:\n\n```bash\ngws admin-reports --api-version directory_v1 groups list --params '{\"domain\":\"example.com\"}'\n```\n\nThis works but is unintuitive — `admin-reports` implies audit/usage reporting, not user/group management. A built-in `admin-directory` alias would make the CLI more discoverable and self-documenting.\n\n## Proposed Change\n\nAdd a new `ServiceEntry` to `src/services.rs`:\n\n```rust\nServiceEntry {\n aliases: &[\"admin-directory\"],\n api_name: \"admin\",\n version: \"directory_v1\",\n description: \"Manage users, groups, and organizational units\",\n},\n```\n\nThis would enable:\n\n```bash\ngws admin-directory groups list --params '{\"domain\":\"example.com\"}'\ngws admin-directory members list --params '{\"groupKey\":\"group@example.com\"}'\ngws admin-directory users get --params '{\"userKey\":\"user@example.com\"}'\n```\n\n## Context\n\n- The `admin-reports` alias already maps to `admin:reports_v1` — same API name, different version\n- The `--api-version directory_v1` workaround works correctly today\n- Adding this alias is backward-compatible (no existing aliases change)\n- The Directory API is core to Workspace administration and used heavily for RBAC, onboarding/offboarding, and compliance workflows","labels":[],"number":473,"title":"Add admin-directory as a built-in service alias for Admin SDK Directory API","updatedAt":"2026-03-13T16:17:37Z"},{"assignees":[],"body":"Hi I have defined to auto-add hangout in the ui, yet when creating a calendar event with the CLI it doesn't add the hangout. \nIs it possible to add support for respecting the parameter in the UI? Or at least provide the option in the CLI to add a Google Meet hangout. \nThanks! ","labels":[],"number":461,"title":"Calendar event is created without Hangout","updatedAt":"2026-03-13T09:20:24Z"},{"assignees":[],"body":"I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back.\n\nRelease 0.7.0 says that multiple accounts were removed. With no reason why.\n\nMultiple accounts can be handy but ESPECIALLY in the age of AI agents. I need to be able to assign my agent account (created in Google) to be able to take actions. And by using the agent account credentials, it becomes auditable. \n\nAnd access can be switched off at a moments notice.\n\nI would say that being able to have multiple agents is a security concern, because it enables proper separation of permissions.\n\nPlease bring it back","labels":[],"number":439,"title":"Please restore multiple accounts being able to be used","updatedAt":"2026-03-13T00:31:55Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent needs to read the content of Gmail messages (e.g., to triage an inbox, summarize emails, or decide on actions), it must use the raw API:\n\n```bash\ngws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"...\", \"format\": \"full\"}'\n```\n\nThis returns the entire message payload including:\n- All email headers (Received, DKIM-Signature, ARC chains, SPF, etc.)\n- Base64-encoded MIME parts\n- Multipart boundaries and nested structure\n- Inline image attachments\n\nFor a typical email, this produces 10–60KB of JSON, most of which is noise. The agent then has to locate the correct MIME part, base64-decode it, and extract readable text — all of which is error-prone and wastes context window.\n\nBy contrast, `+triage` cleanly abstracts listing messages, but there is no equivalent for *reading* a single message body.\n\n## Proposal\n\nAdd a `gws gmail +read` helper that extracts the human-readable body from a message:\n\n```bash\n# Read a message body as plain text\ngws gmail +read --id \n\n# Read with metadata (from, to, subject, date)\ngws gmail +read --id --headers\n\n# JSON output for agent consumption\ngws gmail +read --id --format json\n```\n\nThe helper would:\n1. Fetch the message via `users.messages.get`\n2. Walk the MIME tree to find the `text/plain` part (or fall back to `text/html` → stripped text)\n3. Base64-decode and return clean, readable content\n4. Optionally include key headers (From, To, Subject, Date, Cc)\n\n### Example JSON output\n\n```json\n{\n \"id\": \"19ce2251577a09ea\",\n \"from\": \"Jira automation \",\n \"to\": \"gvilches@squarespace.com\",\n \"cc\": \"bkimmel@squarespace.com, tlong@squarespace.com\",\n \"subject\": \"[ACTION REQUIRED] Your COE violates 14-Day Incident SLA\",\n \"date\": \"Thu, 12 Mar 2026 13:03:25 +0000\",\n \"body\": \"George Vilches,\\n\\nAs the current Assignee for the Incident ticket COE-5367...\"\n}\n```\n\n## Why this matters for agents\n\n`+triage` answers \"what's in my inbox?\" but agents immediately need to follow up with \"what does this email say?\" to make decisions (e.g., does this need a reply? is this actionable?). Today that requires a raw API call that returns massive payloads full of transport headers and encoded content. This is exactly the kind of complexity that helpers are designed to abstract — similar to how `+send` abstracts RFC 2822 encoding.\n\nThe helpers README states helpers should be added when they \"abstract away significant complexity\" — MIME tree walking and base64 decoding for readable text extraction qualifies.","labels":[],"number":438,"title":"Feature request: Gmail +read helper for extracting message body as plain text","updatedAt":"2026-03-12T18:15:41Z"},{"assignees":[],"body":"Hi, \nAfter upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory API on my GCP project and on my auth scopes\n\n\"Image\"","labels":[],"number":430,"title":"Missing admin directory SDK on the newest version?","updatedAt":"2026-03-14T11:13:10Z"},{"assignees":[],"body":"Please consider adding a Gmail draft-only mode/policy to `gws`.\n\nDesired behavior:\n- allow reading Gmail as configured\n- allow creating/updating drafts\n- block all send paths\n\nIn particular, this mode should prevent:\n- `users.messages.send`\n- `users.drafts.send`\n- reply / reply-all / forward actions that result in sending\n- any equivalent helper/tool aliases\n\nA common agent workflow is: let the agent prepare Gmail drafts for human review, but guarantee that it cannot actually send mail.\n\nThat “draft yes, send no” boundary would be very useful for agent integrations.\n\nNotes:\n\nI understand this may depend on Gmail/OAuth/API limitations. If true Google-enforced draft-only permission is not currently possible, a `gws`-side policy/tool filter would still be very valuable.\n\nIf this request belongs in another repo/team, please feel free to redirect it.","labels":[],"number":424,"title":"Feature request: Gmail draft-only mode for CLI+agent workflows","updatedAt":"2026-03-12T06:28:52Z"},{"assignees":[],"body":"## Description\n\n`gws` commands fail in environments requiring HTTP proxy (e.g., users in China who need proxy to access Google APIs).\n\n## Problem\n\nThe underlying `yup-oauth2` and `hyper` HTTP clients don't respect standard proxy environment variables (`http_proxy`, `https_proxy`, `ALL_PROXY`).\n\n**Affected operations:**\n1. `gws auth login` - OAuth token exchange fails\n2. All API calls - token refresh fails\n\n**Error message:**\n```\n{\n \"error\": {\n \"code\": 401,\n \"message\": \"Authentication failed: Failed to get token: Connection failure: Hyper error: client error (Connect): tcp connect error: Operation timed out (os error 60)\",\n \"reason\": \"authError\"\n }\n}\n```\n\n## Environment\n- macOS 14.x (Sonoma)\n- gws 0.11.1\n- Proxy: http://127.0.0.1:7890\n\n## Current Workaround\n\n1. Manually exchange OAuth code using curl with proxy\n2. Save credentials to `~/.config/gws/credentials.json`\n3. Use `GOOGLE_WORKSPACE_CLI_TOKEN` env var with manually refreshed access token\n\nThis is cumbersome as tokens expire every hour.\n\n## Suggested Fix\n\n**Option 1:** Use `reqwest` with default features (includes proxy support):\n```toml\n# Cargo.toml - current\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\"], default-features = false }\n\n# Suggested - enable proxy support\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\", \"socks\"] }\n```\n\n**Option 2:** Pass a custom `hyper` client with proxy support to `yup-oauth2` using `.with_hyper_client()`.\n\n**Option 3:** Add explicit `--proxy` flag or `GWS_PROXY` / `GWS_HTTPS_PROXY` environment variable.\n\n## References\n- reqwest proxy support: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html\n- yup-oauth2 custom client support","labels":[],"number":422,"title":"Support HTTP proxy environment variables (http_proxy/https_proxy)","updatedAt":"2026-03-12T04:31:47Z"},{"assignees":[],"body":"## Problem\n\n`SKILL.md` files are auto-generated by `gws generate-skills` and contain API method listings. However, some services have non-obvious usage patterns that require hand-written guidance — particularly around:\n\n- **Google Docs tabs**: reading specific tabs (`includeTabsContent`), creating tabs (`addDocumentTab` vs the invalid `createTab`), writing to specific tabs (`tabId` in `location`/`range`)\n- **Formatted content insertion**: the insert-then-style pattern with `insertText` + `updateParagraphStyle`\n- **`+write` limitations**: no tab targeting, plain text only\n\nThese patterns are hard to discover from `gws schema` output alone and typically require trial-and-error.\n\n## Proposal\n\nAdd a `COOKBOOK.md` file alongside `SKILL.md` in skill directories that need hand-written recipes:\n\n\\`\\`\\`\nskills/gws-docs/\n SKILL.md ← auto-generated (unchanged)\n COOKBOOK.md ← hand-written recipes (new)\n\\`\\`\\`\n\nBenefits:\n- **Safe from regeneration** — `gws generate-skills` won't overwrite it\n- **Composable** — other skills can adopt the same pattern as needed\n- **Discoverable** — agents can check for COOKBOOK.md alongside SKILL.md\n\nOptional follow-up: teach `generate_skills.rs` to append a \"See also: COOKBOOK.md\" link when one exists.\n\n## Scope\n\nStarting with `gws-docs` as a proof of concept. The cookbook covers:\n1. Tab CRUD operations (read, create, write, rename, delete)\n2. Formatted content insertion (headings, bold/italic, paragraph styles)\n3. Valid `batchUpdate` request types reference table\n4. `+write` helper limitations and workarounds","labels":[],"number":420,"title":"docs(skills): add COOKBOOK.md pattern for hand-written recipes alongside generated SKILL.md","updatedAt":"2026-03-12T02:00:15Z"},{"assignees":[],"body":"Implement the ability to send meeting invites with video conferencing details.","labels":[],"number":419,"title":"Feature: Add video conferencing to meeting insert","updatedAt":"2026-03-11T22:49:33Z"},{"assignees":[],"body":"Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: \nsrc/fs_util.rs\n, \nsrc/credential_store.rs\n, \nsrc/oauth_config.rs\n\nDetails: The \ngws\n CLI uses \natomic_write\n and \natomic_write_async\n in \nsrc/fs_util.rs\n to persist sensitive information like client_secret.json and encrypted OAuth tokens (credentials.enc).\n\nCurrently, these functions write the data to a temporary file (e.g., client_secret.json.tmp) using the default system umask (often 0644 or 0666), and then rename it into place. The permissions are only tightened to 0600 after the file has been renamed (e.g., in oauth_config.rs:88).\n\nImpact: A local attacker running a script that watches the ~/.config/gws/ directory via inotify can open the temporary file or the renamed file before the chmod 0o600 is applied. This allows a low-privileged user on a shared system to exfiltrate the victim's Google Workspace plaintext OAuth client secrets or encrypted credential material.\n\nProposed Fix: The temporary file must be created with secure 0o600 permissions atomically at creation time using std::os::unix::fs::OpenOptionsExt, completely eliminating the TOCTOU window. I have a patch ready to submit","labels":[],"number":401,"title":"TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage","updatedAt":"2026-03-11T11:04:41Z"},{"assignees":[],"body":"## Summary\n\nThe repository currently has no issue templates (only a PR template). As the project grows, issue templates would help contributors file consistent, actionable reports for bugs, feature requests, and docs improvements — reducing back-and-forth for maintainers.\n\n## Suggested templates\n\n- **Bug report** — reproduction steps, expected vs actual behavior, OS/version info\n- **Feature request** — problem description, proposed solution\n- **Docs improvement** — which doc, what's missing or wrong\n\nThese could live in `.github/ISSUE_TEMPLATE/` as standard GitHub issue forms (YAML) or plain markdown templates.\n\n## Bonus: structured input for AI agents\n\nGiven that this project explicitly targets AI agents as users, consistent issue structure also creates a reliable input format for automation. Skills could be built around issue triage, reproduction, or even implementation — but only if issues follow a predictable schema. Issue templates are a low-effort way to enable that.","labels":[],"number":400,"title":"meta: add GitHub issue templates","updatedAt":"2026-03-11T10:15:47Z"},{"assignees":[],"body":"## Summary\n\nExpose a focused lib.rs so gws can be used as a Rust library dependency for programmatic access to Google Workspace APIs. This enables tools like CI integrations and custom automation to reuse the core logic without shelling out to the binary.\n\nRelated: #211, #376\n\n## Design Principles\n\n1. **Minimal surface area** — only expose modules that are stable and useful outside the CLI\n2. **No CLI coupling** — library consumers should not need clap, ratatui, or interactive OAuth flows\n3. **Facade over internals** — provide a clean execute() function rather than exposing executor.rs internals\n\n## Proposed Public API\n\n### Modules to expose (pub mod)\n\n| Module | Purpose |\n|---|---|\n| discovery | RestDescription, RestMethod, RestResource — introspect Google APIs |\n| error | GwsError — unified error type |\n| config | config_dir() — resolve config directory |\n| services | resolve_service(), SERVICES, select_scope() — service name to API mapping |\n| validate | validate_api_identifier(), validate_gcp_location() — input safety |\n| client | build_client(), send_with_retry() — HTTP with retry |\n\n### Modules to keep internal (pub(crate))\n\n| Module | Why |\n|---|---|\n| auth, auth_commands | CLI-specific OAuth flow (browser redirect, token caching) |\n| executor | Takes ArgMatches — CLI orchestration |\n| helpers/* | Each helper parses ArgMatches directly |\n| formatter | Presentation concern (JSON/CSV output) |\n| setup, setup_tui | Interactive TUI |\n| credential_store | Internal key storage detail |\n\n### Programmatic execution facade (future)\n\nFor users who need to execute API methods programmatically:\n\n```rust\npub async fn execute(\n service: &str,\n resource_path: &[&str], // [\"files\", \"list\"]\n method: &str, // \"list\", \"get\", \"create\"\n params: &HashMap,\n body: Option,\n auth: AuthToken,\n) -> Result\n```\n\nThis keeps CLI internals hidden while providing a clean interface. The AuthToken wrapper would accept a pre-obtained token string, decoupling from the CLI's OAuth flow.\n\n## Non-goals\n\n- Exposing the TUI or interactive setup flows\n- Guaranteeing API stability for internal modules\n- Replacing the binary — the library is a complement, not a fork\n\n## Prior art\n\n- PR #376 by @geofflittle — good first attempt but exposes too many CLI-specific modules. The SSRF validation (validate_gcp_location) from that PR is independently valuable and should be extracted.\n- Issue #211 — original feature request","labels":[],"number":386,"title":"feat: expose minimal library crate for programmatic API access","updatedAt":"2026-03-10T20:11:26Z"},{"assignees":[],"body":"This package contains a lot of skills. \n\nConsider implementing support for a skill.json file to make them more manageable and discoverable.\n\nsee https://github.com/automaton-labs/skill.json","labels":[],"number":356,"title":"Consider making skills more discoverable with skill.json.","updatedAt":"2026-03-10T00:21:46Z"},{"assignees":[],"body":"I see the agents flail around trying to find a document in a shared drive and I often have to stop it and say \"the data is in a shared drive\". Not sure if that can be a note in the skills document to mention \"if file is not found, try `supportsAllDrives: true`\"","labels":[],"number":327,"title":"update skills document to mention shared drives (supportsAllDrives: true)","updatedAt":"2026-03-09T14:58:51Z"},{"assignees":[],"body":"## Why\n\n`gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering available services/resources/methods without referring to `--help` each time.\n\n## What\n\nAdd shell completion generation for major shells:\n- bash\n- zsh\n- fish\n\nIdeally via a subcommand like `gws completion ` that outputs the completion script to stdout, following the common CLI pattern (e.g., `gh completion`, `kubectl completion`).\n\n## How\n\n- Top-level services (`drive`, `sheets`, `gmail`, `calendar`, etc.)\n- Resources per service (`files`, `spreadsheets`, `messages`, etc.)\n- Methods per resource (`list`, `get`, `create`, `update`, `delete`, etc.)\n- Global flags (`--params`, `--json`, `--format`, `--page-all`, etc.)","labels":[],"number":323,"title":"feat: Add shell completion support (bash, zsh, fish)","updatedAt":"2026-03-09T13:24:20Z"},{"assignees":[],"body":"The OAuth scopes are not properly ordered in the list. \n\nIt looks like it is ordered by risk levels. Maybe we neeed to provide options to reorder this - product-wise, access-wise (read / write / modify / delete), and then risk-wise. \n\n\"Image\"\n\n","labels":[],"number":319,"title":"OAuth scopes are not properly ordered","updatedAt":"2026-03-09T05:42:41Z"},{"assignees":[],"body":"## Problem\n\nCurrently all error output from `gws` is raw JSON, which is hard to read in an interactive terminal session:\n\n```json\n{\"error\":{\"code\":404,\"message\":\"Not Found\",\"reason\":\"notFound\"}}\n```\n\n## Proposal\n\nDetect whether stdout/stderr is a TTY and display human-friendly colored messages when running interactively:\n\n```\n✗ notFound (404): Not Found ← red bold\n⚠️ Model Armor: injection detected ← yellow bold\n💡 API not enabled for your project ← yellow bold\n```\n\nWhen piped or redirected, keep the existing JSON output for backward compatibility.\n\n## Implementation notes\n\n- Use `std::io::IsTerminal` (stable since Rust 1.70) for TTY detection\n- `colored` crate (or alternatively `anstream`/`anstyle` which are already indirect deps via clap) for ANSI colors\n- Error output goes to stderr; JSON pipe output stays on stdout\n- Zero behavior change for scripted/piped usage","labels":[],"number":317,"title":"feat: Display colored error/warning messages in TTY terminals","updatedAt":"2026-03-09T04:43:30Z"},{"assignees":[],"body":"gws auth login \r\n \"message\": \"Authentication successful. Encrypted credentials saved.\",\r\n \"scopes\": [\r\n \"https://www.googleapis.com/auth/drive\",\r\n \"https://www.googleapis.com/auth/spreadsheets\",\r\n \"https://www.googleapis.com/auth/gmail.modify\",\r\n \"https://www.googleapis.com/auth/calendar\",\r\n \"https://www.googleapis.com/auth/documents\",\r\n \"https://www.googleapis.com/auth/presentations\",\r\n \"https://www.googleapis.com/auth/tasks\",\r\n \"https://www.googleapis.com/auth/pubsub\",\r\n \"https://www.googleapis.com/auth/cloud-platform\",\r\n \"openid\",\r\n \"https://www.googleapis.com/auth/userinfo.email\"\r\n ],\r\n \"status\": \"success\"\r\n\r\nbut \r\ngws drive files list --params '{\"pageSize\": 5}'\r\n{\r\n \"error\": {\r\n \"code\": 500,\r\n \"message\": \"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found (tried both standard and $discovery URLs)\",\r\n \"reason\": \"discoveryError\"\r\n }\r\n}\r\n\r\n_Originally posted by @kaijung in https://github.com/googleworkspace/cli/discussions/257_","labels":[],"number":314,"title":"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found","updatedAt":"2026-03-09T18:29:01Z"},{"assignees":[],"body":"I installed the cli and then ran the `gws auth setup` and it returned\n\n\"Image\"\n\nis it expected?","labels":[],"number":309,"title":"bug: Error on doing the steps in readme","updatedAt":"2026-03-08T10:39:06Z"},{"assignees":[],"body":"**Environment**\n- OS: Windows\n- Node.js: compatible with the package requirement (>=18)\n- npm: global install\n\n**What I ran**\n```\nnpm uninstall -g @googleworkspace/cli\nnpm cache clean --force\nnpm install -g @googleworkspace/cli\ngws auth setup\n```\n\n**What happened**\nThe command fails with:\n\n```\nError: spawnSync C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe ENOENT\n at Object.spawnSync (node:internal/child_process:1119:20)\n at spawnSync (node:child_process:911:24)\n at C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\binary-install.js:197:24\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {\n errno: -4058,\n code: 'ENOENT',\n syscall: 'spawnSync C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n path: 'C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n spawnargs: [ 'auth', 'setup' ]\n}\n```\n\n**Verification**\nI checked whether the expected binary exists:\n\n```\nTest-Path \"C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe\"\n```\n\nResult:\n```\nFalse\n```\n\nSo the package appears to install successfully via npm, but the actual Windows binary is missing from the location the wrapper expects.\n\n**Expected behavior**\n`npm install -g @googleworkspace/cli` should install or place `gws.exe` in the expected path so `gws auth setup` can run.\n\n**Notes**\nThis looks like a packaging/install-script issue specific to Windows, rather than a Node version issue.","labels":[],"number":306,"title":"Windows install is missing `gws.exe`, causing `gws auth setup` to fail with ENOENT","updatedAt":"2026-03-08T07:27:47Z"},{"assignees":[],"body":"```\nnpm install @googleworkspace/cli@0.7.0\nnpm error code 1\nnpm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli\nnpm error command failed\nnpm error command sh -c node ./install.js\nnpm error Your glibc isn't compatible; trying static musl binary instead\nnpm error Downloading release from https://github.com/googleworkspace/cli/releases/download/v0.7.0/gws-x86_64-unknown-linux-musl.tar.gz\nnpm error Error fetching release: spawnSync tar ENOENT\nnpm error A complete log of this run can be found in: /tmp/cache/_logs/2026-03-06T22_08_26_142Z-debug-0.log\n```\n\nThis appears to be using musl when it should be using glibc.\n\nThis was running on Amazon Linux 2023.","labels":[],"number":284,"title":"npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT","updatedAt":"2026-03-09T21:31:38Z"},{"assignees":[],"body":"Hi team — I found a UX bug while trying the CLI:\n\n## Repro\n\n```bash\ngws auth setup --help\n```\n\nOn machines without `gcloud`, this executes setup and fails with:\n\n```json\n{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n}\n```\n\nExpected behavior is to print setup help/usage and exit successfully.\n\n## Proposed fix\n\nEarly-return from `run_setup()` when `-h/--help` is present, before any setup checks.\n\nI prepared a patch in my fork/branch:\n\n- Branch: `stablegenius49/cli:fix-auth-setup-help-flag`\n- Compare URL: https://github.com/googleworkspace/cli/compare/main...stablegenius49:cli:fix-auth-setup-help-flag\n\nI also added unit tests for the help flag detection.\n\nI couldn't open a PR directly because this repo currently blocks PR creation for non-collaborators (`CreatePullRequest` permission error), so sharing via issue + compare link.\n","labels":[],"number":280,"title":"{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n} executes setup instead of showing help","updatedAt":"2026-03-06T20:16:56Z"},{"assignees":[],"body":"Hi,\n\nThanks for this project!\n\nI just tried to auth with all scopes and got\n\nSome requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of XXXXXXXXXX, see error details. Error 400: invalid_scope\n\nI'm not sure if the scope is invalid or it's because my gcloud project is missing this API or because I'm on enterprise account and do not have super admin permission.\n\n","labels":[],"number":258,"title":"Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope","updatedAt":"2026-03-13T17:42:39Z"},{"assignees":[],"body":"## Problem\n\nThere's no way to send a Gmail message with an attachment using `gws`. Two approaches fail:\n\n### 1. `--json` with base64url-encoded raw MIME — hits OS argument length limit\n\nThe documented approach for sending raw MIME (with `threadId`, `In-Reply-To`, `References` headers, and attachments) is to base64url-encode the entire message and pass it via `--json '{\"raw\": \"...\"}'`. But even a modest ~577KB PDF attachment produces a ~1MB base64 payload, which exceeds macOS's ~256KB `ARG_MAX` for command-line arguments:\n\n```\ngws-behav gmail users messages send --params '{\"userId\": \"me\"}' --json \"{\\\"threadId\\\": \\\"...\\\", \\\"raw\\\": \\\"$RAW\\\"}\"\n# => zsh: argument list too long: gws\n```\n\n### 2. `--upload` with raw MIME file — wrong content type\n\nGmail's `/upload/gmail/v1/users/me/messages/send` endpoint accepts `message/rfc822` for media uploads. But `--upload` wraps the file in `multipart/related` with an auto-detected content type, which Gmail rejects:\n\n```\ngws gmail users messages send --params '{\"userId\": \"me\", \"uploadType\": \"media\"}' --upload message.eml\n# => Media type 'multipart/related; boundary=gws_boundary_...' is not supported.\n```\n\n## Suggested fixes\n\nAny of these would unblock the use case:\n\n1. **`--json-file `** or **`--json @`** — read JSON body from a file (like `curl -d @file.json`)\n2. **`--json -`** — read JSON body from stdin\n3. **`--upload-type `** — let users override the content type for `--upload` (e.g. `--upload-type message/rfc822`)\n4. **Auto-detect `.eml` → `message/rfc822`** in `--upload` content type detection\n\nOptions 1 or 2 are the most general and would help any API method with large request bodies, not just Gmail.\n\n## Environment\n\n- gws 0.4.4\n- macOS Darwin 25.3.0 (arm64)\n\n## Related\n\n- #88 — first-class reply/forward support would also solve this at a higher level\n- #244 — OOM on large file uploads (related large-payload issue)","labels":[],"number":247,"title":"Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)","updatedAt":"2026-03-12T01:03:30Z"},{"assignees":[],"body":"gws version: 0.4.4\nOS: macOS (Apple Silicon, MacBook Pro)\nAccount type: Personal @gmail.com\nDescription\nAfter authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects, all Google Drive API calls work correctly but all Apps Script API calls fail with 401 \"invalid authentication credentials.\"\nSteps to Reproduce\n\nInstall gws via npm: npm install -g @googleworkspace/cli\nPlace custom client_secret.json in ~/.config/gws/\nEnable both Google Drive API and Apps Script API in GCP project\nEnable Apps Script API at https://script.google.com/home/usersettings\nAuthenticate:\n\nbashgws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects\n\nExport unmasked credentials (required due to separate Keychain issue — see below):\n\nbashgws auth export --unmasked > ~/.config/gws/credentials.json\n\nVerify auth status shows correct scopes:\n\nbashgws auth status\n# Shows scope_count: 2, token_valid: true, both drive and script.projects scopes listed\n\nTest Drive API — works:\n\nbashgws drive files list --params '{\"pageSize\": 5, \"fields\": \"files(id,name,mimeType)\"}' --format table\n# Returns file listing successfully\n\nTest Apps Script API — fails:\n\nbashgws apps-script projects getContent --params '{\"scriptId\": \"VALID_SCRIPT_ID\"}'\n# Returns 401 error\nExpected Behavior\ngws apps-script projects getContent should return the script project content, since the credentials include the script.projects scope and the Apps Script API is enabled.\nActual Behavior\njson{\n \"error\": {\n \"code\": 401,\n \"message\": \"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\",\n \"reason\": \"unknown\"\n }\n}\nAdditional Context\nScope issue required revoking app access\nInitially, after adding script.projects to an existing auth session, the API returned 403 \"insufficient authentication scopes\" even though gws auth status showed the scope was present. This was resolved by revoking app access at https://myaccount.google.com/permissions and re-authenticating from scratch. After revoking, the error changed from 403 to 401.\nSeparate issue: encrypted credentials not usable for API calls\nOn macOS, gws auth login saves encrypted credentials via OS Keyring, but API calls (e.g., gws drive files list) fail with 401 \"No credentials provided\" unless you first run gws auth export --unmasked > ~/.config/gws/credentials.json to create a plaintext credentials file. The gws auth status command can read the encrypted credentials fine, but the API call code path cannot. This workaround resolves Drive API calls but does not resolve the Apps Script 401.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":205,"title":"Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials","updatedAt":"2026-03-06T01:20:33Z"},{"assignees":[],"body":"This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication:\n\n\"Image\"\n\nUnderstandably, this may just be a limitation of Advanced Protection, though it's always a bit of a confusing threat model that I can grant Claude connectors extensive access to my emails and Gdrive, but not read-only scopes for my own app.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":197,"title":"Doesn't support Advanced Protection Program users","updatedAt":"2026-03-14T00:53:12Z"},{"assignees":[],"body":"## Description\n\nWhen creating a comment on a Google Docs file using `gws drive comments create` with an `anchor` field, the comment is posted successfully (HTTP 200) but appears in the Google Docs UI as **\"Original content deleted\"** instead of being anchored to the specified text.\n\n## Steps to Reproduce\n\n```bash\ngws drive comments create \\\n --params '{\"fileId\": \"\", \"fields\": \"id,content,anchor,quotedFileContent\"}' \\\n --json '{\n \"content\": \"Hello\",\n \"quotedFileContent\": {\n \"mimeType\": \"text/plain\",\n \"value\": \"Overview\"\n },\n \"anchor\": \"{\\\"r\\\":\\\"head\\\",\\\"a\\\":[{\\\"lt\\\":\\\"b\\\",\\\"lv\\\":\\\"Overview\\\"}]}\"\n }'\n```\n\n## Expected Behavior\n\nThe comment should be anchored to the matching text in the document, or the CLI/docs should clearly indicate that anchored comments are not supported for Google Docs files via the API.\n\n## Actual Behavior\n\nThe API returns a 200 with the anchor saved, but the comment appears in the Google Docs UI as **\"Original content deleted\"** — no text is highlighted and the anchor has no visible effect.\n\n## Additional Context\n\n- This appears to be a known Google Drive API limitation: for Google Workspace editor files (Docs, Sheets, Slides), the `anchor` field is saved but ignored by the editor UI. See the [official docs](https://developers.google.com/workspace/drive/api/v3/manage-comments#add_an_anchored_comment_to_the_latest_revision_of_a_document): *\"The anchor is saved and returned when retrieving the comment, however Google Workspace editor apps treat these comments as un-anchored comments.\"*\n- It would be helpful if `gws` surfaced a warning when using `anchor` on a `application/vnd.google-apps.kix` (Google Docs) file, since the result is misleading.\n- `gws` version: `0.4.4`","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":169,"title":"drive comments create: anchor field results in 'Original content deleted' in Google Docs","updatedAt":"2026-03-05T20:38:24Z"},{"assignees":[],"body":"## Summary\nWhen authenticating with read-only scopes using `gws auth login --readonly`, exporting credentials with:\n\n```bash\ngws auth export --unmasked > credentials.json\n```\n\nand then using those credentials on another machine (OpenClaw), the resulting access appears to still allow full/write operations.\n\n## Reproduction\n1. Run:\n ```bash\n gws auth login --readonly\n ```\n2. Export credentials:\n ```bash\n gws auth export --unmasked > credentials.json\n ```\n3. Move `credentials.json` to an external machine/environment (OpenClaw).\n4. Use the exported credentials with `gws` there.\n5. Attempt a write operation (for example, Gmail send).\n\n## Actual behavior\nWrite/full-access behavior is still possible on the external machine, even though login was done with `--readonly`.\n\n## Expected behavior\nRead-only scope enforcement should persist with exported credentials and be enforced by Google-issued token scopes, not only by client-side command restrictions.\n\n## Questions\n- Are `--readonly` scopes currently enforced only in the local client flow, rather than in the OAuth token/scopes themselves?\n- If so, what is the recommended way to ensure exported credentials are truly read-only on external machines?\n- Can `--readonly` become the default behavior in the installer/setup flow (or at least a strongly guided default), not just a local/client-side option?\n\n## Why this matters\nUsers exporting credentials for remote execution expect least-privilege guarantees to carry over across machines/environments.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":168,"title":"gws auth login --readonly + auth export --unmasked appears to allow full access on external machine","updatedAt":"2026-03-12T12:31:16Z"},{"assignees":[],"body":"Does this project plan to support browsing Google Photos on the CLI? It would be nice to do analyses on photos using LLMs.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":163,"title":"Any plan to support Google Photos?","updatedAt":"2026-03-05T20:48:15Z"},{"assignees":[],"body":"Add an option to make HITL / requests gateway so those of us that are using agents and want to gatekeep them for oauth and write actions can do it","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPT1Q","name":"area: mcp","description":"","color":"c46210"}],"number":158,"title":"Add built in HITL that the agents can't edit in code","updatedAt":"2026-03-05T20:38:09Z"},{"assignees":[],"body":"I can't get `npx -y @googleworkspace/cli@latest auth login` to work properly. I triple checked the settings of my auth:\n* Status: testing\n* User type: external\n* Test user: includes myself\n* Client ID: Desktop\n\nThe only \"strange\" thing is that I can't set a redirect URI, which according to `auth setup` is necessary.\n\n`auth login` shows an URL, after browsing to it and approving all scopes on the \"... wants to access your Google Account\" page, I get this on a page https://accounts.google.com/info/unknownerror?...:\n\n\"Image\"","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":157,"title":"\"Something went wrong\" in oauth screen","updatedAt":"2026-03-05T20:37:48Z"},{"assignees":[],"body":"## Description\n\nOn Windows, the `.encryption_key` fallback path in `credential_store.rs` writes the AES-256-GCM encryption key as a plaintext file with no access controls applied.\n\nOn Unix, the key file is explicitly created with mode `0o600` (user-read/write only):\n\n```rust\n#[cfg(unix)]\n{\n use std::os::unix::fs::OpenOptionsExt;\n let mut options = std::fs::OpenOptions::new();\n options.write(true).create(true).truncate(true).mode(0o600);\n ...\n}\n#[cfg(not(unix))]\n{\n let _ = std::fs::write(&key_file, &b64_key); // no permission hardening\n}\n```\n\nOn Windows, `std::fs::write` is used with no equivalent ACL restrictions, meaning the key file inherits default directory permissions and is readable by other users or processes on the same machine.\n\nThis key decrypts `credentials..enc`, which contains the OAuth refresh token. An attacker with local read access can decrypt the token and gain long-lived Google account access.\n\n## Affected Code\n\nThree locations in `src/credential_store.rs` write sensitive files without Windows ACLs:\n\n1. **Line 112-115** — `.encryption_key` file (keyring `NoEntry` fallback)\n2. **Line 172-175** — `.encryption_key` file (keyring failure fallback)\n3. **Lines 246, 305** — `credentials.*.enc` files (via `atomic_write`)\n\nAdditionally, the config **directory** itself (`~/.config/gws/`) gets `0o700` on Unix (lines 89-99, 150-159) but no ACL on Windows.\n\n## Fix Plan\n\n### Option A: `windows-acl` crate (recommended)\n```toml\n[target.'cfg(windows)'.dependencies]\nwindows-acl = \"0.3\"\n```\n\nCreate a helper function:\n```rust\n#[cfg(windows)]\nfn set_owner_only_acl(path: &Path) -> std::io::Result<()> {\n use windows_acl::acl::ACL;\n // Get current user SID, set DACL to owner-only\n}\n```\n\nCall it after every file write on Windows (same locations where `#[cfg(unix)]` sets `0o600`).\n\n### Option B: Raw Win32 via `windows` crate\nUse `SetNamedSecurityInfoW` directly. More control but more boilerplate.\n\n### Option C: Interim warning\nPrint a warning on Windows when falling back to file storage:\n```\n⚠️ Warning: Encryption key stored without access controls. \n Run 'icacls %USERPROFILE%\\.config\\gws /inheritance:r /grant:r %USERNAME%:F' to restrict access.\n```\n\n## Files to Modify\n\n- `Cargo.toml` — add Windows-only dependency\n- `src/credential_store.rs` — add `#[cfg(windows)]` ACL blocks at all 3 write locations + directory creation\n- Optionally: extract a `set_secure_permissions(path)` helper that dispatches to `chmod 0o600` on Unix and ACL on Windows\n\n## Verification\n\n- Windows: `icacls ~/.config/gws/.encryption_key` should show only current user\n- Windows: `icacls ~/.config/gws/credentials.*.enc` should show only current user\n- Unix: behavior unchanged (`ls -la` confirms `0o600`)\n- CI: Windows test runner should exercise the keyring fallback path\n\n## Workaround\n\nUse Windows Credential Manager (the primary keyring path). There is currently no way to force this from the CLI. If keyring works, the file fallback is never used.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":153,"title":"[Security] Windows: encryption key written to plaintext file with no access controls","updatedAt":"2026-03-07T16:08:16Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent tries to help a user unsubscribe from mailing lists using `gws`, there is no guided path. The agent has to:\n\n1. Search for emails containing \"unsubscribe\"\n2. Fetch each message with `format: full` to extract `List-Unsubscribe` headers\n3. Guess whether to use the HTTPS URL or mailto address\n4. Attempt `curl` GET/POST requests to unsubscribe URLs (which mostly load confirmation pages requiring a browser)\n5. Fall back to sending emails to `mailto:` unsubscribe addresses via `gws gmail users messages send`\n\nThis entire process is trial-and-error, fragile, and often fails.\n\nMeanwhile, **Gmail natively supports one-click unsubscribe via RFC 8058**. Since June 2024, Google requires bulk senders to include `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers. The correct unsubscribe mechanism is a simple `POST` to the `List-Unsubscribe` HTTPS URL with the body `List-Unsubscribe=One-Click` — no browser needed, no mailto, no guessing.\n\n## Proposal\n\nAdd a `gws gmail +unsubscribe` helper skill that:\n\n1. **Lists candidates** — Scans recent emails for `List-Unsubscribe` and `List-Unsubscribe-Post` headers, groups by sender, and shows frequency/volume\n2. **Executes RFC 8058 one-click unsubscribe** — Sends the correct `POST` request with `List-Unsubscribe=One-Click` body to the HTTPS unsubscribe URL\n3. **Falls back gracefully** — For senders without RFC 8058 support, shows the mailto address or URL so the agent/user can decide\n\n### Example UX\n\n```bash\n# List subscription candidates with volume\ngws gmail +unsubscribe --list --max 200\n\n# Unsubscribe from a specific sender\ngws gmail +unsubscribe --from \"noreply@mail.michaelpage.com.sg\"\n\n# Dry run\ngws gmail +unsubscribe --from \"team@mail.cerebralvalley.ai\" --dry-run\n```\n\n## Why this matters for agents\n\nThe existing `gws-gmail-triage` skill is great for reading, but there's no skill for the common \"clean up my inbox subscriptions\" workflow. Without a dedicated skill, agents waste many API calls fumbling through message formats, header extraction, and URL guessing — exactly the kind of boilerplate `gws` is designed to eliminate.\n\n## References\n\n- [RFC 8058 — Signaling One-Click Functionality for List Email Headers](https://datatracker.ietf.org/doc/html/rfc8058)\n- [Gmail bulk sender requirements (June 2024)](https://support.google.com/a/answer/81126)","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":114,"title":"Add a gmail +unsubscribe skill for one-click list unsubscribe (RFC 8058)","updatedAt":"2026-03-06T07:16:56Z"},{"assignees":[],"body":"It would be convenient to be able to install this tool using Homebrew 😄 ","labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"}],"number":90,"title":"Feature request: Homebrew Formula","updatedAt":"2026-03-05T20:38:12Z"},{"assignees":[],"body":"Hi! I love skills. But I also hate how they create issues for agents.\n\nContext pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem with this is that the top level skills all get loaded into , and 40 of them, added to any other skills that people use, will make for a really bad experience.\n\nObviously, one might say that not all of them need to be installed at once, but with how heavy the cross-references are, basically none of them are skippable.\n\nProposal:\n\nEven if it costs some duplication, bundle the granular skills in a top level structure, with /references/ folder.\n\nI believe this will save both the users and the devs a lot of headaches, and it will be easier for users to manage a situation where they might only need parts of the included skills.\n\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":82,"title":"Optimisation: 40 skills => less skills + references","updatedAt":"2026-03-06T01:20:35Z"}] diff --git a/all_prs.json b/all_prs.json deleted file mode 100644 index 32ae37b0..00000000 --- a/all_prs.json +++ /dev/null @@ -1 +0,0 @@ -[{"closingIssuesReferences":[],"number":488,"state":"OPEN","title":"feat(credential_store): default keyring backend to OS-only, add keyring-with-file option"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wHXwu","number":169,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/169"}],"number":487,"state":"OPEN","title":"fix(drive): warn when anchor field targets Workspace editor files"},{"closingIssuesReferences":[],"number":486,"state":"OPEN","title":"feat(http): add --verbose / -v flag for request/response diagnostics"},{"closingIssuesReferences":[],"number":485,"state":"OPEN","title":"fix(http): route error and empty-result messages to stderr"},{"closingIssuesReferences":[],"number":484,"state":"OPEN","title":"fix(validate): reject dangerous Unicode characters in input validation"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":482,"state":"MERGED","title":"fix(gmail): RFC 2047 encode non-ASCII display names in address headers"},{"closingIssuesReferences":[],"number":481,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":480,"state":"MERGED","title":"feat(timezone): use Google account timezone for day-boundary calculations"},{"closingIssuesReferences":[],"number":479,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":478,"state":"MERGED","title":"feat: add opt-in structured HTTP request logging via tracing"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wUIxh","number":244,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/244"}],"number":477,"state":"MERGED","title":"fix: stream multipart uploads to avoid OOM on large files"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7ytu2a","number":473,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/473"}],"number":476,"state":"OPEN","title":"Add admin-directory as built-in service alias for Admin SDK Directory API"},{"closingIssuesReferences":[],"number":475,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xKrfZ","number":327,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/327"}],"number":471,"state":"CLOSED","title":"docs(skills): add supportsAllDrives guidance for Shared Drives"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xl-XV","number":380,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/380"}],"number":470,"state":"CLOSED","title":"feat(drive): auto-detect markdown uploads and convert to Google Docs"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":469,"state":"CLOSED","title":"fix(gmail): RFC 2047 encode non-ASCII display names in address headers"},{"closingIssuesReferences":[],"number":468,"state":"OPEN","title":"Revise OAuth login fix and consent screen instructions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1K7_","number":399,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/399"}],"number":467,"state":"MERGED","title":"docs: add Homebrew as an installation option in README"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xABA3","number":317,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/317"}],"number":466,"state":"OPEN","title":"feat(error): display colored error labels on TTY stderr"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yYHMc","number":438,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/438"}],"number":465,"state":"OPEN","title":"feat(gmail): add +read helper for extracting message body as plain text"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yD8x1","number":419,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/419"},{"id":"I_kwDORcxyRs7yliQV","number":461,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/461"}],"number":464,"state":"OPEN","title":"feat(calendar): add --conference flag to +insert for Google Meet links"},{"closingIssuesReferences":[],"number":463,"state":"OPEN","title":"fix(sheets): report error for invalid --json-values instead of silent empty append"},{"closingIssuesReferences":[],"number":462,"state":"CLOSED","title":"fix(workflow): use local timezone for standup-report and weekly-digest boundaries"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yJP70","number":424,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/424"}],"number":460,"state":"OPEN","title":"feat: add `--draft-only` mode for agent workflows"},{"closingIssuesReferences":[],"number":459,"state":"CLOSED","title":"chore: add package lockfile"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":458,"state":"CLOSED","title":"fix(gmail): RFC 2047 encode draft address headers"},{"closingIssuesReferences":[],"number":457,"state":"OPEN","title":"feat: add --dry-run support to events helper commands"},{"closingIssuesReferences":[],"number":456,"state":"CLOSED","title":"feature/add-dry-run-to-helpers"},{"closingIssuesReferences":[],"number":455,"state":"CLOSED","title":"feat: add --dry-run support to events helper commands"},{"closingIssuesReferences":[],"number":454,"state":"MERGED","title":"chore: sync skills with Discovery API"},{"closingIssuesReferences":[],"number":453,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":451,"state":"MERGED","title":"fix(docs): correct broken flag names in recipes"},{"closingIssuesReferences":[],"number":450,"state":"CLOSED","title":"chore: replace deprecated serde_yaml with serde_yml"},{"closingIssuesReferences":[],"number":449,"state":"MERGED","title":"chore: fix maintainer email typo in flake.nix and harden coverage.sh"},{"closingIssuesReferences":[],"number":448,"state":"OPEN","title":"fix(security): cap Retry-After sleep and sanitize mimeType in uploads"},{"closingIssuesReferences":[],"number":447,"state":"OPEN","title":"fix(security): validate --upload and --output paths against traversal"},{"closingIssuesReferences":[],"number":446,"state":"OPEN","title":"fix: report auth errors instead of silently proceeding unauthenticated"},{"closingIssuesReferences":[],"number":445,"state":"MERGED","title":"test(gmail): add unit tests for +triage argument parsing"},{"closingIssuesReferences":[],"number":444,"state":"MERGED","title":"fix(security): validate space name in chat +send"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wVZBB","number":259,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/259"}],"number":443,"state":"MERGED","title":"fix(calendar): use local timezone for agenda day boundaries"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wkDt1","number":280,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/280"}],"number":442,"state":"OPEN","title":"fix: `gws auth setup --help` prints usage instead of running wizard"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x8C7Y","number":408,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/408"}],"number":441,"state":"MERGED","title":"fix(auth): validate --subscription in gmail +watch and deduplicate PUBSUB_API_BASE"},{"closingIssuesReferences":[],"number":440,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":437,"state":"MERGED","title":"ci: avoid scope creep in gemini code assist reviews"},{"closingIssuesReferences":[],"number":436,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xprae","number":389,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/389"}],"number":435,"state":"MERGED","title":"fix(auth): auto-recover from stale encrypted credentials after upgrade"},{"closingIssuesReferences":[],"number":434,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":433,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":431,"state":"OPEN","title":"feat: support GOOGLE_WORKSPACE_CLI_API_BASE to override Google API base URL"},{"closingIssuesReferences":[],"number":429,"state":"MERGED","title":"feat: add --upload-content-type flag and smart MIME inference for uploads"},{"closingIssuesReferences":[],"number":428,"state":"MERGED","title":"feat(error): add structured exit codes for scriptable error handling"},{"closingIssuesReferences":[],"number":427,"state":"MERGED","title":"docs: document helper commands and the + prefix convention"},{"closingIssuesReferences":[],"number":426,"state":"OPEN","title":"feat(formatter): add --format tsv output format"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yHkZX","number":422,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/422"}],"number":423,"state":"OPEN","title":"feat: add HTTP proxy support via environment variables"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yFvFe","number":420,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/420"}],"number":421,"state":"OPEN","title":"docs(skills): add COOKBOOK.md for gws-docs with tab and formatting recipes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wUIxh","number":244,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/244"}],"number":418,"state":"CLOSED","title":"fix: stream multipart uploads to avoid OOM on large files"},{"closingIssuesReferences":[],"number":417,"state":"MERGED","title":"feat(gmail): add --html flag for HTML email composition"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w0s3N","number":300,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/300"}],"number":415,"state":"MERGED","title":"fix(schema): expose repeated field and expand array query params"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w38zb","number":310,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/310"},{"id":"I_kwDORcxyRs7w_cbk","number":316,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/316"}],"number":414,"state":"MERGED","title":"fix(auth): map People service to contacts/directory scope prefixes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xAq2T","number":319,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/319"}],"number":413,"state":"OPEN","title":"feat(auth): sort OAuth scopes by service, then access level"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wYcZh","number":268,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/268"}],"number":412,"state":"MERGED","title":"docs: warn about zsh ! history expansion in sheet range examples"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1Mwr","number":400,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/400"}],"number":411,"state":"CLOSED","title":"meta: add GitHub issue templates for bugs, features, and docs"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w49i3","number":311,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/311"}],"number":410,"state":"MERGED","title":"fix(sheets): preserve multi-row structure in +append --json-values"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x8C7Y","number":408,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/408"}],"number":409,"state":"CLOSED","title":"fix(security): validate resource names in watch and subscribe helpers"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xq8wj","number":392,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/392"}],"number":407,"state":"MERGED","title":"fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1K7_","number":399,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/399"}],"number":406,"state":"CLOSED","title":"docs: add Homebrew installation option"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":405,"state":"CLOSED","title":"fix: RFC 2047 encode non-ASCII display names in address headers"},{"closingIssuesReferences":[],"number":402,"state":"CLOSED","title":"fix(security): resolve TOCTOU race condition in atomic writes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xprae","number":389,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/389"}],"number":398,"state":"CLOSED","title":"fix(auth): auto-recover from credential decryption failures after upgrade"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xourR","number":386,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/386"}],"number":397,"state":"OPEN","title":"feat: expose minimal library crate for programmatic API access"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xq8wj","number":392,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/392"}],"number":396,"state":"CLOSED","title":"fix: refresh OAuth2 tokens in long-running watch/subscribe loops"},{"closingIssuesReferences":[],"number":395,"state":"OPEN","title":"feat(gmail): add --attachment flag to +send, +reply, +reply-all, +forward"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xABA3","number":317,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/317"}],"number":394,"state":"CLOSED","title":"fix: colorize stderr warnings and errors on TTY terminals"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xKrfZ","number":327,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/327"}],"number":393,"state":"CLOSED","title":"docs(drive): add supportsAllDrives note for shared drive access"},{"closingIssuesReferences":[],"number":391,"state":"CLOSED","title":"🔒 [security] Secure atomic file creation with restrictive permissions"},{"closingIssuesReferences":[],"number":390,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":388,"state":"MERGED","title":"chore: Update workflow to use `GOOGLEWORKSPACE_BOT_TOKEN` and `googleworkspace-bot` identity for operations."},{"closingIssuesReferences":[],"number":387,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":385,"state":"MERGED","title":"fix(ci): check remote tags in tag-release.sh"},{"closingIssuesReferences":[],"number":384,"state":"MERGED","title":"ci: use bot token for /gemini review comment"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xl-XV","number":380,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/380"}],"number":383,"state":"CLOSED","title":"feat(docs): add --content-format markdown to +write helper"},{"closingIssuesReferences":[],"number":382,"state":"CLOSED","title":"feat(vertex): add Vertex AI inference helper for Gemini models"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xVg7o","number":360,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/360"}],"number":379,"state":"CLOSED","title":"fix(auth): verify keyring roundtrip before deleting .encryption_key"},{"closingIssuesReferences":[],"number":377,"state":"CLOSED","title":"Ashwin/codex/pr2 retry consistency"},{"closingIssuesReferences":[],"number":376,"state":"CLOSED","title":"feat: expose library crate for programmatic API access"},{"closingIssuesReferences":[],"number":374,"state":"OPEN","title":"fix(security): validate --upload and --output file paths"},{"closingIssuesReferences":[],"number":373,"state":"MERGED","title":"fix(auth): enable native keyring backends on top of #359"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xWmWr","number":364,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/364"}],"number":371,"state":"CLOSED","title":"fix(auth): preserve Linux encryption key fallback"},{"closingIssuesReferences":[],"number":370,"state":"CLOSED","title":"fix(auth): verify keyring round-trip to prevent phantom key loss"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wVZBB","number":259,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/259"}],"number":369,"state":"CLOSED","title":"fix(calendar): use local day boundaries for agenda date filters"},{"closingIssuesReferences":[],"number":366,"state":"CLOSED","title":"docs(auth export): documment --unmasked flag"},{"closingIssuesReferences":[],"number":365,"state":"CLOSED","title":"feat: add MCP server for Google Workspace"},{"closingIssuesReferences":[],"number":363,"state":"CLOSED","title":"fix(security): harden --upload/--output file I/O against traversal, symlink escape, and TOCTOU races"},{"closingIssuesReferences":[],"number":362,"state":"MERGED","title":"feat(gmail): add recipient management flags and extract shared message builder"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":359,"state":"MERGED","title":"feat(credential_store): add GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND env var"},{"closingIssuesReferences":[],"number":358,"state":"CLOSED","title":"feat: Implement multi-account Configuration Profiles"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":357,"state":"CLOSED","title":"fix(credential_store): preserve .encryption_key file for keyring-less environments"},{"closingIssuesReferences":[],"number":355,"state":"CLOSED","title":"chore: sync skills with Discovery API"},{"closingIssuesReferences":[],"number":354,"state":"CLOSED","title":"Claude/rebase onto parent 2 z rr z"},{"closingIssuesReferences":[],"number":353,"state":"MERGED","title":"fix(gmail): bring +forward behavior in line with Gmail web UI"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTXZE","number":236,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/236"}],"number":352,"state":"MERGED","title":"fix(auth): format and deduplicate dynamic scope fallback"},{"closingIssuesReferences":[],"number":351,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":350,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":349,"state":"MERGED","title":"ci: run smoketest immediately after linux build, gate other builds on it"},{"closingIssuesReferences":[],"number":348,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":347,"state":"MERGED","title":"fix(ci): add missing tool input to install-action for cargo-llvm-cov"},{"closingIssuesReferences":[],"number":346,"state":"MERGED","title":"ci: make sccache setup resilient to cache API outages"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":345,"state":"MERGED","title":"fix(credential_store): stop persisting encryption key file when keyring is available"},{"closingIssuesReferences":[],"number":341,"state":"MERGED","title":"ci: pin all actions to a sha"},{"closingIssuesReferences":[],"number":339,"state":"MERGED","title":"ci: ignore sccache errors when GitHub down"},{"closingIssuesReferences":[],"number":331,"state":"MERGED","title":"build(ci): optimize nix and windows cache layers"},{"closingIssuesReferences":[],"number":330,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":329,"state":"MERGED","title":"fix(ci): use default GITHUB_TOKEN for PR reviews"},{"closingIssuesReferences":[],"number":326,"state":"MERGED","title":"chore: Synced file(s) with googleworkspace/.github"},{"closingIssuesReferences":[],"number":325,"state":"CLOSED","title":"fix: allow api:version escape hatch for unlisted APIs"},{"closingIssuesReferences":[],"number":324,"state":"CLOSED","title":"feat: add Google Search Console as a known service"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXIUW","number":266,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/266"}],"number":322,"state":"MERGED","title":"fix: RFC 2047 encode non-ASCII email subjects in +send helper"},{"closingIssuesReferences":[],"number":320,"state":"CLOSED","title":"feat: improve 88 skills descriptions, examples, and validation"},{"closingIssuesReferences":[],"number":318,"state":"OPEN","title":"[Night Shift] Fix +append --json-values multi-row bug"},{"closingIssuesReferences":[],"number":313,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":307,"state":"CLOSED","title":"feat: add gws cache clear and --format tsv"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wdDt-","number":271,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/271"}],"number":305,"state":"CLOSED","title":"fix: handle null libc version on Android/Termux"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXH30","number":265,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/265"}],"number":304,"state":"MERGED","title":"fix: use gmail.readonly scope in +triage to avoid metadata scope 403"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wkDt1","number":280,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/280"}],"number":303,"state":"CLOSED","title":"fix: handle --help flag in gws auth setup"},{"closingIssuesReferences":[],"number":302,"state":"MERGED","title":"fix: use native OS certificate store for TLS"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7w0s3N","number":300,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/300"}],"number":301,"state":"CLOSED","title":"fix(schema): keep repeated query metadata and honor array params"},{"closingIssuesReferences":[],"number":297,"state":"CLOSED","title":"feat: semantic exit codes and actionable error hints"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXGI1","number":264,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/264"}],"number":296,"state":"CLOSED","title":"docs: add asdf installation instructions"},{"closingIssuesReferences":[],"number":295,"state":"MERGED","title":"fix(auth): prioritize local project configuration over global ADC for quota attribution"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wZBDL","number":269,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/269"}],"number":294,"state":"CLOSED","title":"skills: remove openclaw vendor metadata from SKILL frontmatter"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXIUW","number":266,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/266"}],"number":292,"state":"CLOSED","title":"docs: clarify fields usage and UTF-8 subjects for gmail raw drafts"},{"closingIssuesReferences":[],"number":290,"state":"CLOSED","title":"docs: quote sheet ranges and JSON examples to avoid zsh ! expansion"},{"closingIssuesReferences":[],"number":289,"state":"CLOSED","title":"feat(auth): add configuration profiles for account switching"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wlYBe","number":283,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/283"}],"number":288,"state":"MERGED","title":"fix: handle array-of-arrays in CSV formatter"},{"closingIssuesReferences":[],"number":277,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":275,"state":"MERGED","title":"BREAKING CHANGE: Remove MCP server mode"},{"closingIssuesReferences":[],"number":254,"state":"MERGED","title":"chore: Remove a subset of skills and recipes along with their service entries and registry references"},{"closingIssuesReferences":[],"number":253,"state":"MERGED","title":"feat!: remove multi-account, DWD, and impersonation support"},{"closingIssuesReferences":[],"number":250,"state":"MERGED","title":"chore: Remove dwd support"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTXZE","number":236,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/236"}],"number":246,"state":"CLOSED","title":"fix(auth): fall back to Discovery docs when `-s` specifies services not in static scope lists"},{"closingIssuesReferences":[],"number":243,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":242,"state":"MERGED","title":"fix(client): Move x-goog-user-project header to API requests only"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTRyl","number":234,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/234"}],"number":241,"state":"CLOSED","title":"feat(auth): redesign scope picker to show services instead of raw scopes"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wCYOn","number":152,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/152"}],"number":240,"state":"MERGED","title":"fix: use .zip for Windows npm installer to fix Git Bash install failure"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTPKr","number":232,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/232"}],"number":239,"state":"CLOSED","title":"feat(auth): auto-migrate legacy credentials.enc to per-account format"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9tfD","number":116,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/116"}],"number":238,"state":"CLOSED","title":"refactor: extract timeout constant and DRY project input logic (closes #116)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wFU83","number":162,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/162"},{"id":"I_kwDORcxyRs7wTVgl","number":235,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/235"}],"number":237,"state":"CLOSED","title":"fix(mcp): switch tool names from underscore to hyphen separator"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9tfD","number":116,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/116"}],"number":233,"state":"CLOSED","title":"fix: increase project timeout and add manual entry in gws auth setup (closes #116)"},{"closingIssuesReferences":[],"number":231,"state":"CLOSED","title":"feat(auth): add `--no-localhost` flag to `gws auth login`"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wKUCH","number":179,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/179"}],"number":230,"state":"CLOSED","title":"fix(auth): resolve per-account credentials in `auth export`"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9ppc","number":114,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/114"}],"number":229,"state":"CLOSED","title":"feat: add gmail +unsubscribe skill for RFC 8058 one-click unsubscribe"},{"closingIssuesReferences":[],"number":228,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7vE9","number":88,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/88"}],"number":227,"state":"CLOSED","title":"feat: add gmail +reply and +forward skills"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wQJzz","number":210,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/210"}],"number":226,"state":"CLOSED","title":"feat: add --port and --no-browser flags for headless auth"},{"closingIssuesReferences":[],"number":225,"state":"CLOSED","title":"docs: add usage examples for Docs and Drive commands"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wFU83","number":162,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/162"}],"number":224,"state":"CLOSED","title":"fix: use service alias in MCP tool names for aliased services"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wKfRa","number":181,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/181"},{"id":"I_kwDORcxyRs7wR6BO","number":221,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/221"}],"number":223,"state":"MERGED","title":"fix: respect account selection in MCP server and CLI --account flag"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wINp6","number":171,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/171"}],"number":222,"state":"MERGED","title":"docs: document all environment variables and enable CONFIG_DIR override"},{"closingIssuesReferences":[],"number":218,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":217,"state":"MERGED","title":"fix: clean up nits from PR #175 auth fix"},{"closingIssuesReferences":[],"number":216,"state":"MERGED","title":"ci: add path filtering to skip expensive jobs on non-Rust changes"},{"closingIssuesReferences":[],"number":215,"state":"MERGED","title":"fix(client): For ADC, send x-goog-user-project header"},{"closingIssuesReferences":[],"number":214,"state":"CLOSED","title":"feat: expose library crate for programmatic API access"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wQkNd","number":212,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/212"}],"number":213,"state":"MERGED","title":"fix(mcp): conditionally include body/upload in tool schemas, drop empty body on execution"},{"closingIssuesReferences":[],"number":209,"state":"MERGED","title":"ci: auto-trigger Gemini Code Assist review on PR push"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wPmsr","number":206,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/206"}],"number":208,"state":"MERGED","title":"fix: isolate flaky auth tests from host ADC credentials"},{"closingIssuesReferences":[],"number":207,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":204,"state":"MERGED","title":"chore: auto triage and fmt"},{"closingIssuesReferences":[],"number":203,"state":"MERGED","title":"chore: use labels"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wHTys","number":168,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/168"}],"number":202,"state":"CLOSED","title":"fix: store granted scopes in credentials, warn on scope escalation via export"},{"closingIssuesReferences":[],"number":201,"state":"CLOSED","title":"feat: add native Factory Droid integration"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wJPN0","number":174,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/174"}],"number":200,"state":"MERGED","title":"fix: prevent gmail.metadata scope from blocking query parameters"},{"closingIssuesReferences":[],"number":199,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAG6G","number":137,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/137"}],"number":196,"state":"CLOSED","title":"fix: surface auth errors instead of sending unauthenticated requests"},{"closingIssuesReferences":[],"number":193,"state":"MERGED","title":"fix: log token cache errors instead of silently swallowing them"},{"closingIssuesReferences":[],"number":192,"state":"MERGED","title":"fix: replace strip_suffix(\".readonly\").unwrap() with unwrap_or"},{"closingIssuesReferences":[],"number":191,"state":"CLOSED","title":"fix: warn on stderr when discovery cache write fails"},{"closingIssuesReferences":[],"number":190,"state":"CLOSED","title":"fix: log auth errors to stderr instead of silently discarding"},{"closingIssuesReferences":[],"number":189,"state":"CLOSED","title":"fix: replace unwrap() with proper error handling in MCP server"},{"closingIssuesReferences":[],"number":186,"state":"MERGED","title":"fix(setup): drain stale keypresses between TUI screen transitions"},{"closingIssuesReferences":[],"number":185,"state":"MERGED","title":"chore: steps to reduce contributor friction"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wK1fc","number":182,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/182"}],"number":183,"state":"MERGED","title":"fix(executor): add Content-Length: 0 for body-less POST requests"},{"closingIssuesReferences":[],"number":180,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v-Ciy","number":119,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/119"},{"id":"I_kwDORcxyRs7wAXev","number":138,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/138"}],"number":177,"state":"MERGED","title":"fix: allow services flag for login and improve default list for consumer accounts"},{"closingIssuesReferences":[],"number":176,"state":"MERGED","title":"chore(docs): easier cmd to install from source"},{"closingIssuesReferences":[],"number":175,"state":"MERGED","title":"Fix auth failures when accounts.json registry is missing"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wDc8I","number":156,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/156"}],"number":173,"state":"CLOSED","title":"fix: gracefully fallback on legacy credentials instead of bailing"},{"closingIssuesReferences":[],"number":172,"state":"MERGED","title":"feat(mcp): add compact tool mode to reduce context window usage"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wFU83","number":162,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/162"}],"number":165,"state":"CLOSED","title":"fix(mcp): align tools/list namespace with aliased services"},{"closingIssuesReferences":[],"number":164,"state":"CLOSED","title":"fix: remove legacy darwin.apple_sdk reference in flake.nix"},{"closingIssuesReferences":[],"number":161,"state":"CLOSED","title":"Fix typo in gws-reseller/SKILL.md"},{"closingIssuesReferences":[],"number":154,"state":"MERGED","title":"fix: migrate to new Darwin SDK pattern in nix flake"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAXev","number":138,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/138"}],"number":148,"state":"CLOSED","title":"fix: add -s / --services flag to `gws auth login` to limit OAuth scope picker"},{"closingIssuesReferences":[],"number":147,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAisd","number":139,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/139"}],"number":146,"state":"MERGED","title":"fix: use reverse video for TUI highlight style"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAisd","number":139,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/139"}],"number":145,"state":"CLOSED","title":"fix: use terminal-default colors for TUI text to fix readability on light themes"},{"closingIssuesReferences":[],"number":143,"state":"CLOSED","title":"Remote mcp gateway"},{"closingIssuesReferences":[],"number":142,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAG6G","number":137,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/137"}],"number":141,"state":"CLOSED","title":"fix: enhance account resolution and export command handling"},{"closingIssuesReferences":[],"number":140,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":136,"state":"MERGED","title":"docs: improve readme especially auth which is complex no matter what"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v-Ciy","number":119,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/119"}],"number":135,"state":"CLOSED","title":"fix: fix gws auth login for personal @gmail.com accounts"},{"closingIssuesReferences":[],"number":134,"state":"MERGED","title":"fix: use ~/.config/gws on all platforms for consistent config path"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7nJ6","number":87,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/87"}],"number":133,"state":"CLOSED","title":"fix: harden URL and path construction across helper modules"},{"closingIssuesReferences":[],"number":132,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":131,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":129,"state":"MERGED","title":"Fix URL template substitution for media upload endpoints in executor"},{"closingIssuesReferences":[],"number":128,"state":"MERGED","title":"feat: add Linux ARM64 build targets (gnu + musl)"},{"closingIssuesReferences":[],"number":127,"state":"MERGED","title":"fix: exclude Workspace-admin-only scopes from Recommended preset"},{"closingIssuesReferences":[],"number":126,"state":"MERGED","title":"fix: use gcloud.cmd on Windows and show platform-correct config paths"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9DYA","number":103,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/103"}],"number":125,"state":"MERGED","title":"feat: add Application Default Credentials (ADC) support"},{"closingIssuesReferences":[],"number":124,"state":"MERGED","title":"Fix/warn credential permission failures"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9tfD","number":116,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/116"}],"number":123,"state":"MERGED","title":"fix: add manual project ID entry to setup project picker"},{"closingIssuesReferences":[],"number":122,"state":"MERGED","title":"fix: reject DEL character (0x7F) in input validation"},{"closingIssuesReferences":[],"number":121,"state":"CLOSED","title":"feat: add [MANUAL] notation for human-interaction steps (closes #66)"},{"closingIssuesReferences":[],"number":120,"state":"MERGED","title":"fix: build_url now falls back to method.path when flatPath placeholders do not match parameter names, resolving Slides API issues."},{"closingIssuesReferences":[],"number":117,"state":"CLOSED","title":"fix: clean up per-account token caches on logout"},{"closingIssuesReferences":[],"number":115,"state":"MERGED","title":"docs: add note about not official product more prominently"},{"closingIssuesReferences":[],"number":113,"state":"CLOSED","title":"feat: add Homebrew installer via cargo-dist"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7kdn","number":86,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/86"}],"number":112,"state":"MERGED","title":"fix: add x86_64-unknown-linux-musl build target"},{"closingIssuesReferences":[],"number":111,"state":"CLOSED","title":"test: add coverage for helper dispatch and service alias resolution"},{"closingIssuesReferences":[],"number":110,"state":"CLOSED","title":"feat: add [MANUAL] notation for human interaction steps"},{"closingIssuesReferences":[],"number":109,"state":"MERGED","title":"fix: replace unwrap() calls with proper error handling in MCP server"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v5T0c","number":73,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/73"}],"number":108,"state":"MERGED","title":"fix: filter alertcenter scopes from user OAuth login flow"},{"closingIssuesReferences":[],"number":107,"state":"CLOSED","title":"feat: Add Agoragentic marketplace skill — agent-to-agent commerce for Workspace automations"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v8Yqt","number":96,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/96"}],"number":106,"state":"MERGED","title":"fix: drain stdout pipe to prevent project listing deadlock"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7vE9","number":88,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/88"}],"number":105,"state":"MERGED","title":"feat(gmail): add +reply, +reply-all, and +forward helpers"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7nJ6","number":87,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/87"}],"number":102,"state":"MERGED","title":"fix: harden URL and path construction across helper modules"},{"closingIssuesReferences":[],"number":100,"state":"CLOSED","title":"feat: add GOOGLE_WORKSPACE_CLI_API_BASE_URL for custom/mock endpoint support"},{"closingIssuesReferences":[],"number":99,"state":"CLOSED","title":"feat(auth): add gws auth use-adc command "},{"closingIssuesReferences":[],"number":98,"state":"CLOSED","title":"fix: resolve Clippy warnings"},{"closingIssuesReferences":[],"number":97,"state":"CLOSED","title":"feat: add GWS_API_BASE_URL for custom/mock endpoint support"},{"closingIssuesReferences":[],"number":95,"state":"MERGED","title":"Improve auth setup project-creation error handling and retry UX"},{"closingIssuesReferences":[],"number":94,"state":"MERGED","title":"feat: Add nix flake"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v8Evj","number":92,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/92"}],"number":93,"state":"CLOSED","title":"Fix typo: 'descrived' to 'described' in SKILL.md"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v9-9P","number":118,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/118"}],"number":91,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":89,"state":"MERGED","title":"Fix: correct author email typo in package.json"},{"closingIssuesReferences":[],"number":85,"state":"MERGED","title":"feat: multi-account support"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v7cam","number":83,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/83"}],"number":84,"state":"MERGED","title":"fix: encode URL template path params in build_url"},{"closingIssuesReferences":[],"number":81,"state":"MERGED","title":"fix: credential masking panic and silent token write errors"},{"closingIssuesReferences":[],"number":80,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":79,"state":"MERGED","title":"fix: add github.com instead of just github in readme"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v6b3_","number":75,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/75"}],"number":77,"state":"MERGED","title":"fix: enable APIs individually and surface gcloud errors"},{"closingIssuesReferences":[],"number":72,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":71,"state":"MERGED","title":"feat: add `gws version` bare subcommand"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v2zmI","number":64,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/64"}],"number":70,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7v2zmI","number":64,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/64"}],"number":68,"state":"MERGED","title":"feat: smarter truncation of method/resource descriptions"},{"closingIssuesReferences":[],"number":67,"state":"CLOSED","title":"feat: implement clasp"},{"closingIssuesReferences":[],"number":65,"state":"MERGED","title":"chore: sync skills with Discovery API"},{"closingIssuesReferences":[],"number":63,"state":"MERGED","title":"feat: Implement hourly cron and manual trigger for the generate-skills workflow to auto-sync skills via pull requests and downgrade CI drift check to a warning."},{"closingIssuesReferences":[],"number":62,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vwMum","number":56,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/56"}],"number":61,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vwMum","number":56,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/56"}],"number":60,"state":"MERGED","title":"docs: fix gws setup → gws auth setup"},{"closingIssuesReferences":[],"number":59,"state":"MERGED","title":"fix: use gl-rust/ prefix in x-goog-api-client header"},{"closingIssuesReferences":[],"number":58,"state":"MERGED","title":"feat: add gws mcp server"},{"closingIssuesReferences":[],"number":55,"state":"MERGED","title":"ci: add workflow to publish OpenClaw skills to ClawHub"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhe3","number":24,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/24"},{"id":"I_kwDORcxyRs7vmhjU","number":25,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/25"},{"id":"I_kwDORcxyRs7vm-Tz","number":31,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/31"}],"number":54,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":53,"state":"MERGED","title":"ci: auto-generate and commit skills on branch push"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vo3kB","number":42,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/42"}],"number":52,"state":"MERGED","title":"fix: atomic credential file writes to prevent corruption on crash (fixes #42)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vohTs","number":40,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/40"},{"id":"I_kwDORcxyRs7vo3k6","number":43,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/43"}],"number":51,"state":"MERGED","title":"fix(table): flatten nested objects to dot-notation, safe multi-byte truncation (fixes #40 #43)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vohKf","number":39,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/39"}],"number":50,"state":"MERGED","title":"fix: add YAML document separators when paginating (fixes #39)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vohAX","number":38,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/38"}],"number":49,"state":"MERGED","title":"fix: warn to stderr when unknown --format value is provided (fixes #38)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhp-","number":26,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/26"}],"number":48,"state":"MERGED","title":"fix(auth): support --help / -h flag on auth subcommand (fixes #26)"},{"closingIssuesReferences":[],"number":47,"state":"MERGED","title":"ci: skip smoketest on fork pull requests"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmtp7","number":27,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/27"}],"number":46,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":41,"state":"MERGED","title":"docs(gws-shared): add community guidance for stars + issue hygiene"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vogNm","number":35,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/35"},{"id":"I_kwDORcxyRs7vogX7","number":36,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/36"}],"number":37,"state":"MERGED","title":"fix: YAML block scalar for single-line strings, and repeated CSV/table headers with --page-all"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vm-UW","number":32,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/32"}],"number":34,"state":"CLOSED","title":"feat: support kebab-case aliases for camelCase subcommands (fixes #32)"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vm-Tz","number":31,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/31"}],"number":33,"state":"MERGED","title":"feat(error): detect accessNotConfigured and guide users to enable APIs"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhe3","number":24,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/24"},{"id":"I_kwDORcxyRs7vmhjU","number":25,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/25"}],"number":30,"state":"MERGED","title":"fix: narrow default OAuth scopes to avoid restricted_client, improve non-interactive setup UX"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmhe3","number":24,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/24"},{"id":"I_kwDORcxyRs7vmhjU","number":25,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/25"}],"number":29,"state":"MERGED","title":"docs(auth): add manual OAuth client and browser consent guidance"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7vmtp7","number":27,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/27"}],"number":28,"state":"MERGED","title":"fix(auth): stabilize encrypted credential key fallback"},{"closingIssuesReferences":[],"number":21,"state":"MERGED","title":"fix: harden URL encoding and input validation for AI/LLM callers"},{"closingIssuesReferences":[],"number":20,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":19,"state":"MERGED","title":"🧪 Add test for missing error paths in load_client_config"},{"closingIssuesReferences":[],"number":18,"state":"MERGED","title":"feat: skills expansion"},{"closingIssuesReferences":[],"number":17,"state":"MERGED","title":"🧪 Add test for EncryptedTokenStorage::new"},{"closingIssuesReferences":[],"number":16,"state":"MERGED","title":"Fix Gemini extension installation issue and update docs"},{"closingIssuesReferences":[],"number":15,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":14,"state":"MERGED","title":"fix(docs): improve README typography and spacing"},{"closingIssuesReferences":[],"number":13,"state":"MERGED","title":"docs: make README banner full-width and add changeset"},{"closingIssuesReferences":[],"number":12,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":11,"state":"MERGED","title":"fix: decrypt token cache before extracting refresh token"},{"closingIssuesReferences":[],"number":10,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":9,"state":"MERGED","title":"fix: use correct npm-scope/npm-package config keys for cargo-dist"},{"closingIssuesReferences":[],"number":8,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":7,"state":"MERGED","title":"fix: sync Cargo.toml version with changesets"},{"closingIssuesReferences":[],"number":6,"state":"MERGED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":5,"state":"MERGED","title":"docs: fix gif, readme, etc"},{"closingIssuesReferences":[],"number":4,"state":"CLOSED","title":"chore: release versions"},{"closingIssuesReferences":[],"number":3,"state":"MERGED","title":"ci: speed up builds with rust-cache, sccache, and artifact reuse"},{"closingIssuesReferences":[],"number":2,"state":"MERGED","title":"fix: use changeset tag instead of publish, defer npm publishing to cargo-dist"},{"closingIssuesReferences":[],"number":1,"state":"MERGED","title":"feat: implement cli"}] diff --git a/analysis_results.json b/analysis_results.json deleted file mode 100644 index 70976283..00000000 --- a/analysis_results.json +++ /dev/null @@ -1,256 +0,0 @@ -[ - { - "assignees": [], - "body": "## Description\n\nOn Windows, the `.encryption_key` fallback path in `credential_store.rs` writes the AES-256-GCM encryption key as a plaintext file with no access controls applied.\n\nOn Unix, the key file is explicitly created with mode `0o600` (user-read/write only):\n\n```rust\n#[cfg(unix)]\n{\n use std::os::unix::fs::OpenOptionsExt;\n let mut options = std::fs::OpenOptions::new();\n options.write(true).create(true).truncate(true).mode(0o600);\n ...\n}\n#[cfg(not(unix))]\n{\n let _ = std::fs::write(&key_file, &b64_key); // no permission hardening\n}\n```\n\nOn Windows, `std::fs::write` is used with no equivalent ACL restrictions, meaning the key file inherits default directory permissions and is readable by other users or processes on the same machine.\n\nThis key decrypts `credentials..enc`, which contains the OAuth refresh token. An attacker with local read access can decrypt the token and gain long-lived Google account access.\n\n## Affected Code\n\nThree locations in `src/credential_store.rs` write sensitive files without Windows ACLs:\n\n1. **Line 112-115** \u2014 `.encryption_key` file (keyring `NoEntry` fallback)\n2. **Line 172-175** \u2014 `.encryption_key` file (keyring failure fallback)\n3. **Lines 246, 305** \u2014 `credentials.*.enc` files (via `atomic_write`)\n\nAdditionally, the config **directory** itself (`~/.config/gws/`) gets `0o700` on Unix (lines 89-99, 150-159) but no ACL on Windows.\n\n## Fix Plan\n\n### Option A: `windows-acl` crate (recommended)\n```toml\n[target.'cfg(windows)'.dependencies]\nwindows-acl = \"0.3\"\n```\n\nCreate a helper function:\n```rust\n#[cfg(windows)]\nfn set_owner_only_acl(path: &Path) -> std::io::Result<()> {\n use windows_acl::acl::ACL;\n // Get current user SID, set DACL to owner-only\n}\n```\n\nCall it after every file write on Windows (same locations where `#[cfg(unix)]` sets `0o600`).\n\n### Option B: Raw Win32 via `windows` crate\nUse `SetNamedSecurityInfoW` directly. More control but more boilerplate.\n\n### Option C: Interim warning\nPrint a warning on Windows when falling back to file storage:\n```\n\u26a0\ufe0f Warning: Encryption key stored without access controls. \n Run 'icacls %USERPROFILE%\\.config\\gws /inheritance:r /grant:r %USERNAME%:F' to restrict access.\n```\n\n## Files to Modify\n\n- `Cargo.toml` \u2014 add Windows-only dependency\n- `src/credential_store.rs` \u2014 add `#[cfg(windows)]` ACL blocks at all 3 write locations + directory creation\n- Optionally: extract a `set_secure_permissions(path)` helper that dispatches to `chmod 0o600` on Unix and ACL on Windows\n\n## Verification\n\n- Windows: `icacls ~/.config/gws/.encryption_key` should show only current user\n- Windows: `icacls ~/.config/gws/credentials.*.enc` should show only current user\n- Unix: behavior unchanged (`ls -la` confirms `0o600`)\n- CI: Windows test runner should exercise the keyring fallback path\n\n## Workaround\n\nUse Windows Credential Manager (the primary keyring path). There is currently no way to force this from the CLI. If keyring works, the file fallback is never used.\n", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPRQg", - "name": "area: auth", - "description": "", - "color": "1d76db" - } - ], - "number": 153, - "title": "[Security] Windows: encryption key written to plaintext file with no access controls", - "updatedAt": "2026-03-07T16:08:16Z" - }, - { - "assignees": [], - "body": "gws version: 0.4.4\nOS: macOS (Apple Silicon, MacBook Pro)\nAccount type: Personal @gmail.com\nDescription\nAfter authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects, all Google Drive API calls work correctly but all Apps Script API calls fail with 401 \"invalid authentication credentials.\"\nSteps to Reproduce\n\nInstall gws via npm: npm install -g @googleworkspace/cli\nPlace custom client_secret.json in ~/.config/gws/\nEnable both Google Drive API and Apps Script API in GCP project\nEnable Apps Script API at https://script.google.com/home/usersettings\nAuthenticate:\n\nbashgws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects\n\nExport unmasked credentials (required due to separate Keychain issue \u2014 see below):\n\nbashgws auth export --unmasked > ~/.config/gws/credentials.json\n\nVerify auth status shows correct scopes:\n\nbashgws auth status\n# Shows scope_count: 2, token_valid: true, both drive and script.projects scopes listed\n\nTest Drive API \u2014 works:\n\nbashgws drive files list --params '{\"pageSize\": 5, \"fields\": \"files(id,name,mimeType)\"}' --format table\n# Returns file listing successfully\n\nTest Apps Script API \u2014 fails:\n\nbashgws apps-script projects getContent --params '{\"scriptId\": \"VALID_SCRIPT_ID\"}'\n# Returns 401 error\nExpected Behavior\ngws apps-script projects getContent should return the script project content, since the credentials include the script.projects scope and the Apps Script API is enabled.\nActual Behavior\njson{\n \"error\": {\n \"code\": 401,\n \"message\": \"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\",\n \"reason\": \"unknown\"\n }\n}\nAdditional Context\nScope issue required revoking app access\nInitially, after adding script.projects to an existing auth session, the API returned 403 \"insufficient authentication scopes\" even though gws auth status showed the scope was present. This was resolved by revoking app access at https://myaccount.google.com/permissions and re-authenticating from scratch. After revoking, the error changed from 403 to 401.\nSeparate issue: encrypted credentials not usable for API calls\nOn macOS, gws auth login saves encrypted credentials via OS Keyring, but API calls (e.g., gws drive files list) fail with 401 \"No credentials provided\" unless you first run gws auth export --unmasked > ~/.config/gws/credentials.json to create a plaintext credentials file. The gws auth status command can read the encrypted credentials fine, but the API call code path cannot. This workaround resolves Drive API calls but does not resolve the Apps Script 401.", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPRQg", - "name": "area: auth", - "description": "", - "color": "1d76db" - } - ], - "number": 205, - "title": "Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials", - "updatedAt": "2026-03-06T01:20:33Z" - }, - { - "assignees": [], - "body": "This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication:\n\n\"Image\"\n\nUnderstandably, this may just be a limitation of Advanced Protection, though it's always a bit of a confusing threat model that I can grant Claude connectors extensive access to my emails and Gdrive, but not read-only scopes for my own app.", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPRQg", - "name": "area: auth", - "description": "", - "color": "1d76db" - } - ], - "number": 197, - "title": "Doesn't support Advanced Protection Program users", - "updatedAt": "2026-03-14T00:53:12Z" - }, - { - "assignees": [], - "body": "## Summary\nWhen authenticating with read-only scopes using `gws auth login --readonly`, exporting credentials with:\n\n```bash\ngws auth export --unmasked > credentials.json\n```\n\nand then using those credentials on another machine (OpenClaw), the resulting access appears to still allow full/write operations.\n\n## Reproduction\n1. Run:\n ```bash\n gws auth login --readonly\n ```\n2. Export credentials:\n ```bash\n gws auth export --unmasked > credentials.json\n ```\n3. Move `credentials.json` to an external machine/environment (OpenClaw).\n4. Use the exported credentials with `gws` there.\n5. Attempt a write operation (for example, Gmail send).\n\n## Actual behavior\nWrite/full-access behavior is still possible on the external machine, even though login was done with `--readonly`.\n\n## Expected behavior\nRead-only scope enforcement should persist with exported credentials and be enforced by Google-issued token scopes, not only by client-side command restrictions.\n\n## Questions\n- Are `--readonly` scopes currently enforced only in the local client flow, rather than in the OAuth token/scopes themselves?\n- If so, what is the recommended way to ensure exported credentials are truly read-only on external machines?\n- Can `--readonly` become the default behavior in the installer/setup flow (or at least a strongly guided default), not just a local/client-side option?\n\n## Why this matters\nUsers exporting credentials for remote execution expect least-privilege guarantees to carry over across machines/environments.\n", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPRQg", - "name": "area: auth", - "description": "", - "color": "1d76db" - } - ], - "number": 168, - "title": "gws auth login --readonly + auth export --unmasked appears to allow full access on external machine", - "updatedAt": "2026-03-12T12:31:16Z" - }, - { - "assignees": [], - "body": "I can't get `npx -y @googleworkspace/cli@latest auth login` to work properly. I triple checked the settings of my auth:\n* Status: testing\n* User type: external\n* Test user: includes myself\n* Client ID: Desktop\n\nThe only \"strange\" thing is that I can't set a redirect URI, which according to `auth setup` is necessary.\n\n`auth login` shows an URL, after browsing to it and approving all scopes on the \"... wants to access your Google Account\" page, I get this on a page https://accounts.google.com/info/unknownerror?...:\n\n\"Image\"", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPRQg", - "name": "area: auth", - "description": "", - "color": "1d76db" - } - ], - "number": 157, - "title": "\"Something went wrong\" in oauth screen", - "updatedAt": "2026-03-05T20:37:48Z" - }, - { - "assignees": [], - "body": "## Problem\n\nWhen an AI agent tries to help a user unsubscribe from mailing lists using `gws`, there is no guided path. The agent has to:\n\n1. Search for emails containing \"unsubscribe\"\n2. Fetch each message with `format: full` to extract `List-Unsubscribe` headers\n3. Guess whether to use the HTTPS URL or mailto address\n4. Attempt `curl` GET/POST requests to unsubscribe URLs (which mostly load confirmation pages requiring a browser)\n5. Fall back to sending emails to `mailto:` unsubscribe addresses via `gws gmail users messages send`\n\nThis entire process is trial-and-error, fragile, and often fails.\n\nMeanwhile, **Gmail natively supports one-click unsubscribe via RFC 8058**. Since June 2024, Google requires bulk senders to include `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers. The correct unsubscribe mechanism is a simple `POST` to the `List-Unsubscribe` HTTPS URL with the body `List-Unsubscribe=One-Click` \u2014 no browser needed, no mailto, no guessing.\n\n## Proposal\n\nAdd a `gws gmail +unsubscribe` helper skill that:\n\n1. **Lists candidates** \u2014 Scans recent emails for `List-Unsubscribe` and `List-Unsubscribe-Post` headers, groups by sender, and shows frequency/volume\n2. **Executes RFC 8058 one-click unsubscribe** \u2014 Sends the correct `POST` request with `List-Unsubscribe=One-Click` body to the HTTPS unsubscribe URL\n3. **Falls back gracefully** \u2014 For senders without RFC 8058 support, shows the mailto address or URL so the agent/user can decide\n\n### Example UX\n\n```bash\n# List subscription candidates with volume\ngws gmail +unsubscribe --list --max 200\n\n# Unsubscribe from a specific sender\ngws gmail +unsubscribe --from \"noreply@mail.michaelpage.com.sg\"\n\n# Dry run\ngws gmail +unsubscribe --from \"team@mail.cerebralvalley.ai\" --dry-run\n```\n\n## Why this matters for agents\n\nThe existing `gws-gmail-triage` skill is great for reading, but there's no skill for the common \"clean up my inbox subscriptions\" workflow. Without a dedicated skill, agents waste many API calls fumbling through message formats, header extraction, and URL guessing \u2014 exactly the kind of boilerplate `gws` is designed to eliminate.\n\n## References\n\n- [RFC 8058 \u2014 Signaling One-Click Functionality for List Email Headers](https://datatracker.ietf.org/doc/html/rfc8058)\n- [Gmail bulk sender requirements (June 2024)](https://support.google.com/a/answer/81126)", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPPVw", - "name": "area: skills", - "description": "", - "color": "7057ff" - } - ], - "number": 114, - "title": "Add a gmail +unsubscribe skill for one-click list unsubscribe (RFC 8058)", - "updatedAt": "2026-03-06T07:16:56Z" - }, - { - "assignees": [], - "body": "Hi! I love skills. But I also hate how they create issues for agents.\n\nContext pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem with this is that the top level skills all get loaded into , and 40 of them, added to any other skills that people use, will make for a really bad experience.\n\nObviously, one might say that not all of them need to be installed at once, but with how heavy the cross-references are, basically none of them are skippable.\n\nProposal:\n\nEven if it costs some duplication, bundle the granular skills in a top level structure, with /references/ folder.\n\nI believe this will save both the users and the devs a lot of headaches, and it will be easier for users to manage a situation where they might only need parts of the included skills.\n\n", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPPVw", - "name": "area: skills", - "description": "", - "color": "7057ff" - } - ], - "number": 82, - "title": "Optimisation: 40 skills => less skills + references", - "updatedAt": "2026-03-06T01:20:35Z" - }, - { - "assignees": [], - "body": "I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back.\n\nRelease 0.7.0 says that multiple accounts were removed. With no reason why.\n\nMultiple accounts can be handy but ESPECIALLY in the age of AI agents. I need to be able to assign my agent account (created in Google) to be able to take actions. And by using the agent account credentials, it becomes auditable. \n\nAnd access can be switched off at a moments notice.\n\nI would say that being able to have multiple agents is a security concern, because it enables proper separation of permissions.\n\nPlease bring it back", - "labels": [], - "number": 439, - "title": "Please restore multiple accounts being able to be used", - "updatedAt": "2026-03-13T00:31:55Z" - }, - { - "assignees": [], - "body": "Hi, \nAfter upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory API on my GCP project and on my auth scopes\n\n\"Image\"", - "labels": [], - "number": 430, - "title": "Missing admin directory SDK on the newest version?", - "updatedAt": "2026-03-14T11:13:10Z" - }, - { - "assignees": [], - "body": "Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: \nsrc/fs_util.rs\n, \nsrc/credential_store.rs\n, \nsrc/oauth_config.rs\n\nDetails: The \ngws\n CLI uses \natomic_write\n and \natomic_write_async\n in \nsrc/fs_util.rs\n to persist sensitive information like client_secret.json and encrypted OAuth tokens (credentials.enc).\n\nCurrently, these functions write the data to a temporary file (e.g., client_secret.json.tmp) using the default system umask (often 0644 or 0666), and then rename it into place. The permissions are only tightened to 0600 after the file has been renamed (e.g., in oauth_config.rs:88).\n\nImpact: A local attacker running a script that watches the ~/.config/gws/ directory via inotify can open the temporary file or the renamed file before the chmod 0o600 is applied. This allows a low-privileged user on a shared system to exfiltrate the victim's Google Workspace plaintext OAuth client secrets or encrypted credential material.\n\nProposed Fix: The temporary file must be created with secure 0o600 permissions atomically at creation time using std::os::unix::fs::OpenOptionsExt, completely eliminating the TOCTOU window. I have a patch ready to submit", - "labels": [], - "number": 401, - "title": "TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage", - "updatedAt": "2026-03-11T11:04:41Z" - }, - { - "assignees": [], - "body": "## Summary\n\nThe repository currently has no issue templates (only a PR template). As the project grows, issue templates would help contributors file consistent, actionable reports for bugs, feature requests, and docs improvements \u2014 reducing back-and-forth for maintainers.\n\n## Suggested templates\n\n- **Bug report** \u2014 reproduction steps, expected vs actual behavior, OS/version info\n- **Feature request** \u2014 problem description, proposed solution\n- **Docs improvement** \u2014 which doc, what's missing or wrong\n\nThese could live in `.github/ISSUE_TEMPLATE/` as standard GitHub issue forms (YAML) or plain markdown templates.\n\n## Bonus: structured input for AI agents\n\nGiven that this project explicitly targets AI agents as users, consistent issue structure also creates a reliable input format for automation. Skills could be built around issue triage, reproduction, or even implementation \u2014 but only if issues follow a predictable schema. Issue templates are a low-effort way to enable that.", - "labels": [], - "number": 400, - "title": "meta: add GitHub issue templates", - "updatedAt": "2026-03-11T10:15:47Z" - }, - { - "assignees": [], - "body": "This package contains a lot of skills. \n\nConsider implementing support for a skill.json file to make them more manageable and discoverable.\n\nsee https://github.com/automaton-labs/skill.json", - "labels": [], - "number": 356, - "title": "Consider making skills more discoverable with skill.json.", - "updatedAt": "2026-03-10T00:21:46Z" - }, - { - "assignees": [], - "body": "I see the agents flail around trying to find a document in a shared drive and I often have to stop it and say \"the data is in a shared drive\". Not sure if that can be a note in the skills document to mention \"if file is not found, try `supportsAllDrives: true`\"", - "labels": [], - "number": 327, - "title": "update skills document to mention shared drives (supportsAllDrives: true)", - "updatedAt": "2026-03-09T14:58:51Z" - }, - { - "assignees": [], - "body": "## Why\n\n`gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering available services/resources/methods without referring to `--help` each time.\n\n## What\n\nAdd shell completion generation for major shells:\n- bash\n- zsh\n- fish\n\nIdeally via a subcommand like `gws completion ` that outputs the completion script to stdout, following the common CLI pattern (e.g., `gh completion`, `kubectl completion`).\n\n## How\n\n- Top-level services (`drive`, `sheets`, `gmail`, `calendar`, etc.)\n- Resources per service (`files`, `spreadsheets`, `messages`, etc.)\n- Methods per resource (`list`, `get`, `create`, `update`, `delete`, etc.)\n- Global flags (`--params`, `--json`, `--format`, `--page-all`, etc.)", - "labels": [], - "number": 323, - "title": "feat: Add shell completion support (bash, zsh, fish)", - "updatedAt": "2026-03-09T13:24:20Z" - }, - { - "assignees": [], - "body": "gws auth login \r\n \"message\": \"Authentication successful. Encrypted credentials saved.\",\r\n \"scopes\": [\r\n \"https://www.googleapis.com/auth/drive\",\r\n \"https://www.googleapis.com/auth/spreadsheets\",\r\n \"https://www.googleapis.com/auth/gmail.modify\",\r\n \"https://www.googleapis.com/auth/calendar\",\r\n \"https://www.googleapis.com/auth/documents\",\r\n \"https://www.googleapis.com/auth/presentations\",\r\n \"https://www.googleapis.com/auth/tasks\",\r\n \"https://www.googleapis.com/auth/pubsub\",\r\n \"https://www.googleapis.com/auth/cloud-platform\",\r\n \"openid\",\r\n \"https://www.googleapis.com/auth/userinfo.email\"\r\n ],\r\n \"status\": \"success\"\r\n\r\nbut \r\ngws drive files list --params '{\"pageSize\": 5}'\r\n{\r\n \"error\": {\r\n \"code\": 500,\r\n \"message\": \"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found (tried both standard and $discovery URLs)\",\r\n \"reason\": \"discoveryError\"\r\n }\r\n}\r\n\r\n_Originally posted by @kaijung in https://github.com/googleworkspace/cli/discussions/257_", - "labels": [], - "number": 314, - "title": "Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found", - "updatedAt": "2026-03-09T18:29:01Z" - }, - { - "assignees": [], - "body": "I installed the cli and then ran the `gws auth setup` and it returned\n\n\"Image\"\n\nis it expected?", - "labels": [], - "number": 309, - "title": "bug: Error on doing the steps in readme", - "updatedAt": "2026-03-08T10:39:06Z" - }, - { - "assignees": [], - "body": "**Environment**\n- OS: Windows\n- Node.js: compatible with the package requirement (>=18)\n- npm: global install\n\n**What I ran**\n```\nnpm uninstall -g @googleworkspace/cli\nnpm cache clean --force\nnpm install -g @googleworkspace/cli\ngws auth setup\n```\n\n**What happened**\nThe command fails with:\n\n```\nError: spawnSync C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe ENOENT\n at Object.spawnSync (node:internal/child_process:1119:20)\n at spawnSync (node:child_process:911:24)\n at C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\binary-install.js:197:24\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {\n errno: -4058,\n code: 'ENOENT',\n syscall: 'spawnSync C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n path: 'C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n spawnargs: [ 'auth', 'setup' ]\n}\n```\n\n**Verification**\nI checked whether the expected binary exists:\n\n```\nTest-Path \"C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe\"\n```\n\nResult:\n```\nFalse\n```\n\nSo the package appears to install successfully via npm, but the actual Windows binary is missing from the location the wrapper expects.\n\n**Expected behavior**\n`npm install -g @googleworkspace/cli` should install or place `gws.exe` in the expected path so `gws auth setup` can run.\n\n**Notes**\nThis looks like a packaging/install-script issue specific to Windows, rather than a Node version issue.", - "labels": [], - "number": 306, - "title": "Windows install is missing `gws.exe`, causing `gws auth setup` to fail with ENOENT", - "updatedAt": "2026-03-08T07:27:47Z" - }, - { - "assignees": [], - "body": "```\nnpm install @googleworkspace/cli@0.7.0\nnpm error code 1\nnpm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli\nnpm error command failed\nnpm error command sh -c node ./install.js\nnpm error Your glibc isn't compatible; trying static musl binary instead\nnpm error Downloading release from https://github.com/googleworkspace/cli/releases/download/v0.7.0/gws-x86_64-unknown-linux-musl.tar.gz\nnpm error Error fetching release: spawnSync tar ENOENT\nnpm error A complete log of this run can be found in: /tmp/cache/_logs/2026-03-06T22_08_26_142Z-debug-0.log\n```\n\nThis appears to be using musl when it should be using glibc.\n\nThis was running on Amazon Linux 2023.", - "labels": [], - "number": 284, - "title": "npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT", - "updatedAt": "2026-03-09T21:31:38Z" - }, - { - "assignees": [], - "body": "Hi,\n\nThanks for this project!\n\nI just tried to auth with all scopes and got\n\nSome requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of XXXXXXXXXX, see error details. Error 400: invalid_scope\n\nI'm not sure if the scope is invalid or it's because my gcloud project is missing this API or because I'm on enterprise account and do not have super admin permission.\n\n", - "labels": [], - "number": 258, - "title": "Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope", - "updatedAt": "2026-03-13T17:42:39Z" - }, - { - "assignees": [], - "body": "## Problem\n\nThere's no way to send a Gmail message with an attachment using `gws`. Two approaches fail:\n\n### 1. `--json` with base64url-encoded raw MIME \u2014 hits OS argument length limit\n\nThe documented approach for sending raw MIME (with `threadId`, `In-Reply-To`, `References` headers, and attachments) is to base64url-encode the entire message and pass it via `--json '{\"raw\": \"...\"}'`. But even a modest ~577KB PDF attachment produces a ~1MB base64 payload, which exceeds macOS's ~256KB `ARG_MAX` for command-line arguments:\n\n```\ngws-behav gmail users messages send --params '{\"userId\": \"me\"}' --json \"{\\\"threadId\\\": \\\"...\\\", \\\"raw\\\": \\\"$RAW\\\"}\"\n# => zsh: argument list too long: gws\n```\n\n### 2. `--upload` with raw MIME file \u2014 wrong content type\n\nGmail's `/upload/gmail/v1/users/me/messages/send` endpoint accepts `message/rfc822` for media uploads. But `--upload` wraps the file in `multipart/related` with an auto-detected content type, which Gmail rejects:\n\n```\ngws gmail users messages send --params '{\"userId\": \"me\", \"uploadType\": \"media\"}' --upload message.eml\n# => Media type 'multipart/related; boundary=gws_boundary_...' is not supported.\n```\n\n## Suggested fixes\n\nAny of these would unblock the use case:\n\n1. **`--json-file `** or **`--json @`** \u2014 read JSON body from a file (like `curl -d @file.json`)\n2. **`--json -`** \u2014 read JSON body from stdin\n3. **`--upload-type `** \u2014 let users override the content type for `--upload` (e.g. `--upload-type message/rfc822`)\n4. **Auto-detect `.eml` \u2192 `message/rfc822`** in `--upload` content type detection\n\nOptions 1 or 2 are the most general and would help any API method with large request bodies, not just Gmail.\n\n## Environment\n\n- gws 0.4.4\n- macOS Darwin 25.3.0 (arm64)\n\n## Related\n\n- #88 \u2014 first-class reply/forward support would also solve this at a higher level\n- #244 \u2014 OOM on large file uploads (related large-payload issue)", - "labels": [], - "number": 247, - "title": "Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)", - "updatedAt": "2026-03-12T01:03:30Z" - }, - { - "assignees": [], - "body": "Does this project plan to support browsing Google Photos on the CLI? It would be nice to do analyses on photos using LLMs.", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTQH5w", - "name": "area: discovery", - "description": "", - "color": "006b75" - } - ], - "number": 163, - "title": "Any plan to support Google Photos?", - "updatedAt": "2026-03-05T20:48:15Z" - }, - { - "assignees": [], - "body": "Add an option to make HITL / requests gateway so those of us that are using agents and want to gatekeep them for oauth and write actions can do it", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTPT1Q", - "name": "area: mcp", - "description": "", - "color": "c46210" - } - ], - "number": 158, - "title": "Add built in HITL that the agents can't edit in code", - "updatedAt": "2026-03-05T20:38:09Z" - }, - { - "assignees": [], - "body": "It would be convenient to be able to install this tool using Homebrew \ud83d\ude04 ", - "labels": [ - { - "id": "LA_kwDORcxyRs8AAAACaTP5NQ", - "name": "area: distribution", - "description": "", - "color": "e07be0" - } - ], - "number": 90, - "title": "Feature request: Homebrew Formula", - "updatedAt": "2026-03-05T20:38:12Z" - } -] diff --git a/deep_analysis_results.json b/deep_analysis_results.json deleted file mode 100644 index 1be18b88..00000000 --- a/deep_analysis_results.json +++ /dev/null @@ -1,151 +0,0 @@ -[ - { - "number": 439, - "title": "Please restore multiple accounts being able to be used", - "labels": [], - "updatedAt": "2026-03-13T00:31:55Z", - "body_snippet": "I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back. Release 0.7.0 says that multiple accounts were removed. With no reason why...." - }, - { - "number": 430, - "title": "Missing admin directory SDK on the newest version?", - "labels": [], - "updatedAt": "2026-03-14T11:13:10Z", - "body_snippet": "Hi, After upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory ..." - }, - { - "number": 401, - "title": "TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage", - "labels": [], - "updatedAt": "2026-03-11T11:04:41Z", - "body_snippet": "Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: src/fs_util.rs , src/credential_store.rs , src/oauth_config.rs Details: The gws CL..." - }, - { - "number": 356, - "title": "Consider making skills more discoverable with skill.json.", - "labels": [], - "updatedAt": "2026-03-10T00:21:46Z", - "body_snippet": "This package contains a lot of skills. Consider implementing support for a skill.json file to make them more manageable and discoverable. see https://github.com/automaton-labs/skill.json..." - }, - { - "number": 323, - "title": "feat: Add shell completion support (bash, zsh, fish)", - "labels": [], - "updatedAt": "2026-03-09T13:24:20Z", - "body_snippet": "## Why `gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering..." - }, - { - "number": 314, - "title": "Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found", - "labels": [], - "updatedAt": "2026-03-09T18:29:01Z", - "body_snippet": "gws auth login \r \"message\": \"Authentication successful. Encrypted credentials saved.\",\r \"scopes\": [\r \"https://www.googleapis.com/auth/drive\",\r \"https://www.googleapis.com/auth/spreadsheets..." - }, - { - "number": 309, - "title": "bug: Error on doing the steps in readme", - "labels": [], - "updatedAt": "2026-03-08T10:39:06Z", - "body_snippet": "I installed the cli and then ran the `gws auth setup` and it returned \"Image\"=18) - npm: global install **What I ran** ``` npm uninstall -g @googleworkspace/cli npm cache clean --force npm inst..." - }, - { - "number": 284, - "title": "npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT", - "labels": [], - "updatedAt": "2026-03-09T21:31:38Z", - "body_snippet": "``` npm install @googleworkspace/cli@0.7.0 npm error code 1 npm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli npm error command failed npm error command sh -c node ...." - }, - { - "number": 258, - "title": "Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope", - "labels": [], - "updatedAt": "2026-03-13T17:42:39Z", - "body_snippet": "Hi, Thanks for this project! I just tried to auth with all scopes and got Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of X..." - }, - { - "number": 247, - "title": "Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)", - "labels": [], - "updatedAt": "2026-03-12T01:03:30Z", - "body_snippet": "## Problem There's no way to send a Gmail message with an attachment using `gws`. Two approaches fail: ### 1. `--json` with base64url-encoded raw MIME \u2014 hits OS argument length limit The documented..." - }, - { - "number": 205, - "title": "Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials", - "labels": [ - "area: auth" - ], - "updatedAt": "2026-03-06T01:20:33Z", - "body_snippet": "gws version: 0.4.4 OS: macOS (Apple Silicon, MacBook Pro) Account type: Personal @gmail.com Description After authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://..." - }, - { - "number": 197, - "title": "Doesn't support Advanced Protection Program users", - "labels": [ - "area: auth" - ], - "updatedAt": "2026-03-14T00:53:12Z", - "body_snippet": "This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication: less skills + references", - "labels": [ - "area: skills" - ], - "updatedAt": "2026-03-06T01:20:35Z", - "body_snippet": "Hi! I love skills. But I also hate how they create issues for agents. Context pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem wi..." - } -] diff --git a/issues.json b/issues.json deleted file mode 100644 index 9d817f30..00000000 --- a/issues.json +++ /dev/null @@ -1 +0,0 @@ -[{"assignees":[],"body":"## Summary\n\nThe Admin SDK Directory API (`admin:directory_v1`) is one of the most commonly used Google Workspace APIs for managing users, groups, and organizational units. Currently, accessing it through `gws` requires using the `admin-reports` alias with a version override:\n\n```bash\ngws admin-reports --api-version directory_v1 groups list --params '{\"domain\":\"example.com\"}'\n```\n\nThis works but is unintuitive — `admin-reports` implies audit/usage reporting, not user/group management. A built-in `admin-directory` alias would make the CLI more discoverable and self-documenting.\n\n## Proposed Change\n\nAdd a new `ServiceEntry` to `src/services.rs`:\n\n```rust\nServiceEntry {\n aliases: &[\"admin-directory\"],\n api_name: \"admin\",\n version: \"directory_v1\",\n description: \"Manage users, groups, and organizational units\",\n},\n```\n\nThis would enable:\n\n```bash\ngws admin-directory groups list --params '{\"domain\":\"example.com\"}'\ngws admin-directory members list --params '{\"groupKey\":\"group@example.com\"}'\ngws admin-directory users get --params '{\"userKey\":\"user@example.com\"}'\n```\n\n## Context\n\n- The `admin-reports` alias already maps to `admin:reports_v1` — same API name, different version\n- The `--api-version directory_v1` workaround works correctly today\n- Adding this alias is backward-compatible (no existing aliases change)\n- The Directory API is core to Workspace administration and used heavily for RBAC, onboarding/offboarding, and compliance workflows","labels":[],"number":473,"title":"Add admin-directory as a built-in service alias for Admin SDK Directory API","updatedAt":"2026-03-13T16:17:37Z"},{"assignees":[],"body":"Hi I have defined to auto-add hangout in the ui, yet when creating a calendar event with the CLI it doesn't add the hangout. \nIs it possible to add support for respecting the parameter in the UI? Or at least provide the option in the CLI to add a Google Meet hangout. \nThanks! ","labels":[],"number":461,"title":"Calendar event is created without Hangout","updatedAt":"2026-03-13T09:20:24Z"},{"assignees":[],"body":"I know that this request has been made by others, but they are not in this list of issues any more. So I'm putting it back.\n\nRelease 0.7.0 says that multiple accounts were removed. With no reason why.\n\nMultiple accounts can be handy but ESPECIALLY in the age of AI agents. I need to be able to assign my agent account (created in Google) to be able to take actions. And by using the agent account credentials, it becomes auditable. \n\nAnd access can be switched off at a moments notice.\n\nI would say that being able to have multiple agents is a security concern, because it enables proper separation of permissions.\n\nPlease bring it back","labels":[],"number":439,"title":"Please restore multiple accounts being able to be used","updatedAt":"2026-03-13T00:31:55Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent needs to read the content of Gmail messages (e.g., to triage an inbox, summarize emails, or decide on actions), it must use the raw API:\n\n```bash\ngws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"...\", \"format\": \"full\"}'\n```\n\nThis returns the entire message payload including:\n- All email headers (Received, DKIM-Signature, ARC chains, SPF, etc.)\n- Base64-encoded MIME parts\n- Multipart boundaries and nested structure\n- Inline image attachments\n\nFor a typical email, this produces 10–60KB of JSON, most of which is noise. The agent then has to locate the correct MIME part, base64-decode it, and extract readable text — all of which is error-prone and wastes context window.\n\nBy contrast, `+triage` cleanly abstracts listing messages, but there is no equivalent for *reading* a single message body.\n\n## Proposal\n\nAdd a `gws gmail +read` helper that extracts the human-readable body from a message:\n\n```bash\n# Read a message body as plain text\ngws gmail +read --id \n\n# Read with metadata (from, to, subject, date)\ngws gmail +read --id --headers\n\n# JSON output for agent consumption\ngws gmail +read --id --format json\n```\n\nThe helper would:\n1. Fetch the message via `users.messages.get`\n2. Walk the MIME tree to find the `text/plain` part (or fall back to `text/html` → stripped text)\n3. Base64-decode and return clean, readable content\n4. Optionally include key headers (From, To, Subject, Date, Cc)\n\n### Example JSON output\n\n```json\n{\n \"id\": \"19ce2251577a09ea\",\n \"from\": \"Jira automation \",\n \"to\": \"gvilches@squarespace.com\",\n \"cc\": \"bkimmel@squarespace.com, tlong@squarespace.com\",\n \"subject\": \"[ACTION REQUIRED] Your COE violates 14-Day Incident SLA\",\n \"date\": \"Thu, 12 Mar 2026 13:03:25 +0000\",\n \"body\": \"George Vilches,\\n\\nAs the current Assignee for the Incident ticket COE-5367...\"\n}\n```\n\n## Why this matters for agents\n\n`+triage` answers \"what's in my inbox?\" but agents immediately need to follow up with \"what does this email say?\" to make decisions (e.g., does this need a reply? is this actionable?). Today that requires a raw API call that returns massive payloads full of transport headers and encoded content. This is exactly the kind of complexity that helpers are designed to abstract — similar to how `+send` abstracts RFC 2822 encoding.\n\nThe helpers README states helpers should be added when they \"abstract away significant complexity\" — MIME tree walking and base64 decoding for readable text extraction qualifies.","labels":[],"number":438,"title":"Feature request: Gmail +read helper for extracting message body as plain text","updatedAt":"2026-03-12T18:15:41Z"},{"assignees":[],"body":"Hi, \nAfter upgrading to the latest version, which finally resolved my issues with macOS keychain, I seem to now miss the admin directory services from the gws cli despite enabling the admin directory API on my GCP project and on my auth scopes\n\n\"Image\"","labels":[],"number":430,"title":"Missing admin directory SDK on the newest version?","updatedAt":"2026-03-14T11:13:10Z"},{"assignees":[],"body":"Please consider adding a Gmail draft-only mode/policy to `gws`.\n\nDesired behavior:\n- allow reading Gmail as configured\n- allow creating/updating drafts\n- block all send paths\n\nIn particular, this mode should prevent:\n- `users.messages.send`\n- `users.drafts.send`\n- reply / reply-all / forward actions that result in sending\n- any equivalent helper/tool aliases\n\nA common agent workflow is: let the agent prepare Gmail drafts for human review, but guarantee that it cannot actually send mail.\n\nThat “draft yes, send no” boundary would be very useful for agent integrations.\n\nNotes:\n\nI understand this may depend on Gmail/OAuth/API limitations. If true Google-enforced draft-only permission is not currently possible, a `gws`-side policy/tool filter would still be very valuable.\n\nIf this request belongs in another repo/team, please feel free to redirect it.","labels":[],"number":424,"title":"Feature request: Gmail draft-only mode for CLI+agent workflows","updatedAt":"2026-03-12T06:28:52Z"},{"assignees":[],"body":"## Description\n\n`gws` commands fail in environments requiring HTTP proxy (e.g., users in China who need proxy to access Google APIs).\n\n## Problem\n\nThe underlying `yup-oauth2` and `hyper` HTTP clients don't respect standard proxy environment variables (`http_proxy`, `https_proxy`, `ALL_PROXY`).\n\n**Affected operations:**\n1. `gws auth login` - OAuth token exchange fails\n2. All API calls - token refresh fails\n\n**Error message:**\n```\n{\n \"error\": {\n \"code\": 401,\n \"message\": \"Authentication failed: Failed to get token: Connection failure: Hyper error: client error (Connect): tcp connect error: Operation timed out (os error 60)\",\n \"reason\": \"authError\"\n }\n}\n```\n\n## Environment\n- macOS 14.x (Sonoma)\n- gws 0.11.1\n- Proxy: http://127.0.0.1:7890\n\n## Current Workaround\n\n1. Manually exchange OAuth code using curl with proxy\n2. Save credentials to `~/.config/gws/credentials.json`\n3. Use `GOOGLE_WORKSPACE_CLI_TOKEN` env var with manually refreshed access token\n\nThis is cumbersome as tokens expire every hour.\n\n## Suggested Fix\n\n**Option 1:** Use `reqwest` with default features (includes proxy support):\n```toml\n# Cargo.toml - current\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\"], default-features = false }\n\n# Suggested - enable proxy support\nreqwest = { version = \"0.12\", features = [\"json\", \"stream\", \"rustls-tls-native-roots\", \"socks\"] }\n```\n\n**Option 2:** Pass a custom `hyper` client with proxy support to `yup-oauth2` using `.with_hyper_client()`.\n\n**Option 3:** Add explicit `--proxy` flag or `GWS_PROXY` / `GWS_HTTPS_PROXY` environment variable.\n\n## References\n- reqwest proxy support: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html\n- yup-oauth2 custom client support","labels":[],"number":422,"title":"Support HTTP proxy environment variables (http_proxy/https_proxy)","updatedAt":"2026-03-12T04:31:47Z"},{"assignees":[],"body":"## Problem\n\n`SKILL.md` files are auto-generated by `gws generate-skills` and contain API method listings. However, some services have non-obvious usage patterns that require hand-written guidance — particularly around:\n\n- **Google Docs tabs**: reading specific tabs (`includeTabsContent`), creating tabs (`addDocumentTab` vs the invalid `createTab`), writing to specific tabs (`tabId` in `location`/`range`)\n- **Formatted content insertion**: the insert-then-style pattern with `insertText` + `updateParagraphStyle`\n- **`+write` limitations**: no tab targeting, plain text only\n\nThese patterns are hard to discover from `gws schema` output alone and typically require trial-and-error.\n\n## Proposal\n\nAdd a `COOKBOOK.md` file alongside `SKILL.md` in skill directories that need hand-written recipes:\n\n\\`\\`\\`\nskills/gws-docs/\n SKILL.md ← auto-generated (unchanged)\n COOKBOOK.md ← hand-written recipes (new)\n\\`\\`\\`\n\nBenefits:\n- **Safe from regeneration** — `gws generate-skills` won't overwrite it\n- **Composable** — other skills can adopt the same pattern as needed\n- **Discoverable** — agents can check for COOKBOOK.md alongside SKILL.md\n\nOptional follow-up: teach `generate_skills.rs` to append a \"See also: COOKBOOK.md\" link when one exists.\n\n## Scope\n\nStarting with `gws-docs` as a proof of concept. The cookbook covers:\n1. Tab CRUD operations (read, create, write, rename, delete)\n2. Formatted content insertion (headings, bold/italic, paragraph styles)\n3. Valid `batchUpdate` request types reference table\n4. `+write` helper limitations and workarounds","labels":[],"number":420,"title":"docs(skills): add COOKBOOK.md pattern for hand-written recipes alongside generated SKILL.md","updatedAt":"2026-03-12T02:00:15Z"},{"assignees":[],"body":"Implement the ability to send meeting invites with video conferencing details.","labels":[],"number":419,"title":"Feature: Add video conferencing to meeting insert","updatedAt":"2026-03-11T22:49:33Z"},{"assignees":[],"body":"Vulnerability Type: Time-of-Check to Time-of-Use (TOCTOU) Race Condition / Insecure File Permissions Component: \nsrc/fs_util.rs\n, \nsrc/credential_store.rs\n, \nsrc/oauth_config.rs\n\nDetails: The \ngws\n CLI uses \natomic_write\n and \natomic_write_async\n in \nsrc/fs_util.rs\n to persist sensitive information like client_secret.json and encrypted OAuth tokens (credentials.enc).\n\nCurrently, these functions write the data to a temporary file (e.g., client_secret.json.tmp) using the default system umask (often 0644 or 0666), and then rename it into place. The permissions are only tightened to 0600 after the file has been renamed (e.g., in oauth_config.rs:88).\n\nImpact: A local attacker running a script that watches the ~/.config/gws/ directory via inotify can open the temporary file or the renamed file before the chmod 0o600 is applied. This allows a low-privileged user on a shared system to exfiltrate the victim's Google Workspace plaintext OAuth client secrets or encrypted credential material.\n\nProposed Fix: The temporary file must be created with secure 0o600 permissions atomically at creation time using std::os::unix::fs::OpenOptionsExt, completely eliminating the TOCTOU window. I have a patch ready to submit","labels":[],"number":401,"title":"TOCTOU Race Condition in fs_util::atomic_write Leads to Local Plaintext Secret Leakage","updatedAt":"2026-03-11T11:04:41Z"},{"assignees":[],"body":"## Summary\n\nThe repository currently has no issue templates (only a PR template). As the project grows, issue templates would help contributors file consistent, actionable reports for bugs, feature requests, and docs improvements — reducing back-and-forth for maintainers.\n\n## Suggested templates\n\n- **Bug report** — reproduction steps, expected vs actual behavior, OS/version info\n- **Feature request** — problem description, proposed solution\n- **Docs improvement** — which doc, what's missing or wrong\n\nThese could live in `.github/ISSUE_TEMPLATE/` as standard GitHub issue forms (YAML) or plain markdown templates.\n\n## Bonus: structured input for AI agents\n\nGiven that this project explicitly targets AI agents as users, consistent issue structure also creates a reliable input format for automation. Skills could be built around issue triage, reproduction, or even implementation — but only if issues follow a predictable schema. Issue templates are a low-effort way to enable that.","labels":[],"number":400,"title":"meta: add GitHub issue templates","updatedAt":"2026-03-11T10:15:47Z"},{"assignees":[],"body":"## Summary\n\nExpose a focused lib.rs so gws can be used as a Rust library dependency for programmatic access to Google Workspace APIs. This enables tools like CI integrations and custom automation to reuse the core logic without shelling out to the binary.\n\nRelated: #211, #376\n\n## Design Principles\n\n1. **Minimal surface area** — only expose modules that are stable and useful outside the CLI\n2. **No CLI coupling** — library consumers should not need clap, ratatui, or interactive OAuth flows\n3. **Facade over internals** — provide a clean execute() function rather than exposing executor.rs internals\n\n## Proposed Public API\n\n### Modules to expose (pub mod)\n\n| Module | Purpose |\n|---|---|\n| discovery | RestDescription, RestMethod, RestResource — introspect Google APIs |\n| error | GwsError — unified error type |\n| config | config_dir() — resolve config directory |\n| services | resolve_service(), SERVICES, select_scope() — service name to API mapping |\n| validate | validate_api_identifier(), validate_gcp_location() — input safety |\n| client | build_client(), send_with_retry() — HTTP with retry |\n\n### Modules to keep internal (pub(crate))\n\n| Module | Why |\n|---|---|\n| auth, auth_commands | CLI-specific OAuth flow (browser redirect, token caching) |\n| executor | Takes ArgMatches — CLI orchestration |\n| helpers/* | Each helper parses ArgMatches directly |\n| formatter | Presentation concern (JSON/CSV output) |\n| setup, setup_tui | Interactive TUI |\n| credential_store | Internal key storage detail |\n\n### Programmatic execution facade (future)\n\nFor users who need to execute API methods programmatically:\n\n```rust\npub async fn execute(\n service: &str,\n resource_path: &[&str], // [\"files\", \"list\"]\n method: &str, // \"list\", \"get\", \"create\"\n params: &HashMap,\n body: Option,\n auth: AuthToken,\n) -> Result\n```\n\nThis keeps CLI internals hidden while providing a clean interface. The AuthToken wrapper would accept a pre-obtained token string, decoupling from the CLI's OAuth flow.\n\n## Non-goals\n\n- Exposing the TUI or interactive setup flows\n- Guaranteeing API stability for internal modules\n- Replacing the binary — the library is a complement, not a fork\n\n## Prior art\n\n- PR #376 by @geofflittle — good first attempt but exposes too many CLI-specific modules. The SSRF validation (validate_gcp_location) from that PR is independently valuable and should be extracted.\n- Issue #211 — original feature request","labels":[],"number":386,"title":"feat: expose minimal library crate for programmatic API access","updatedAt":"2026-03-10T20:11:26Z"},{"assignees":[],"body":"This package contains a lot of skills. \n\nConsider implementing support for a skill.json file to make them more manageable and discoverable.\n\nsee https://github.com/automaton-labs/skill.json","labels":[],"number":356,"title":"Consider making skills more discoverable with skill.json.","updatedAt":"2026-03-10T00:21:46Z"},{"assignees":[],"body":"I see the agents flail around trying to find a document in a shared drive and I often have to stop it and say \"the data is in a shared drive\". Not sure if that can be a note in the skills document to mention \"if file is not found, try `supportsAllDrives: true`\"","labels":[],"number":327,"title":"update skills document to mention shared drives (supportsAllDrives: true)","updatedAt":"2026-03-09T14:58:51Z"},{"assignees":[],"body":"## Why\n\n`gws` has a rich set of services, resources, and methods, but there's no shell completion support yet. Tab completion would significantly improve the CLI experience, especially for discovering available services/resources/methods without referring to `--help` each time.\n\n## What\n\nAdd shell completion generation for major shells:\n- bash\n- zsh\n- fish\n\nIdeally via a subcommand like `gws completion ` that outputs the completion script to stdout, following the common CLI pattern (e.g., `gh completion`, `kubectl completion`).\n\n## How\n\n- Top-level services (`drive`, `sheets`, `gmail`, `calendar`, etc.)\n- Resources per service (`files`, `spreadsheets`, `messages`, etc.)\n- Methods per resource (`list`, `get`, `create`, `update`, `delete`, etc.)\n- Global flags (`--params`, `--json`, `--format`, `--page-all`, etc.)","labels":[],"number":323,"title":"feat: Add shell completion support (bash, zsh, fish)","updatedAt":"2026-03-09T13:24:20Z"},{"assignees":[],"body":"The OAuth scopes are not properly ordered in the list. \n\nIt looks like it is ordered by risk levels. Maybe we neeed to provide options to reorder this - product-wise, access-wise (read / write / modify / delete), and then risk-wise. \n\n\"Image\"\n\n","labels":[],"number":319,"title":"OAuth scopes are not properly ordered","updatedAt":"2026-03-09T05:42:41Z"},{"assignees":[],"body":"## Problem\n\nCurrently all error output from `gws` is raw JSON, which is hard to read in an interactive terminal session:\n\n```json\n{\"error\":{\"code\":404,\"message\":\"Not Found\",\"reason\":\"notFound\"}}\n```\n\n## Proposal\n\nDetect whether stdout/stderr is a TTY and display human-friendly colored messages when running interactively:\n\n```\n✗ notFound (404): Not Found ← red bold\n⚠️ Model Armor: injection detected ← yellow bold\n💡 API not enabled for your project ← yellow bold\n```\n\nWhen piped or redirected, keep the existing JSON output for backward compatibility.\n\n## Implementation notes\n\n- Use `std::io::IsTerminal` (stable since Rust 1.70) for TTY detection\n- `colored` crate (or alternatively `anstream`/`anstyle` which are already indirect deps via clap) for ANSI colors\n- Error output goes to stderr; JSON pipe output stays on stdout\n- Zero behavior change for scripted/piped usage","labels":[],"number":317,"title":"feat: Display colored error/warning messages in TTY terminals","updatedAt":"2026-03-09T04:43:30Z"},{"assignees":[],"body":"gws auth login \r\n \"message\": \"Authentication successful. Encrypted credentials saved.\",\r\n \"scopes\": [\r\n \"https://www.googleapis.com/auth/drive\",\r\n \"https://www.googleapis.com/auth/spreadsheets\",\r\n \"https://www.googleapis.com/auth/gmail.modify\",\r\n \"https://www.googleapis.com/auth/calendar\",\r\n \"https://www.googleapis.com/auth/documents\",\r\n \"https://www.googleapis.com/auth/presentations\",\r\n \"https://www.googleapis.com/auth/tasks\",\r\n \"https://www.googleapis.com/auth/pubsub\",\r\n \"https://www.googleapis.com/auth/cloud-platform\",\r\n \"openid\",\r\n \"https://www.googleapis.com/auth/userinfo.email\"\r\n ],\r\n \"status\": \"success\"\r\n\r\nbut \r\ngws drive files list --params '{\"pageSize\": 5}'\r\n{\r\n \"error\": {\r\n \"code\": 500,\r\n \"message\": \"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found (tried both standard and $discovery URLs)\",\r\n \"reason\": \"discoveryError\"\r\n }\r\n}\r\n\r\n_Originally posted by @kaijung in https://github.com/googleworkspace/cli/discussions/257_","labels":[],"number":314,"title":"Failed to fetch Discovery Document for drive/v3: HTTP 404 Not Found","updatedAt":"2026-03-09T18:29:01Z"},{"assignees":[],"body":"I installed the cli and then ran the `gws auth setup` and it returned\n\n\"Image\"\n\nis it expected?","labels":[],"number":309,"title":"bug: Error on doing the steps in readme","updatedAt":"2026-03-08T10:39:06Z"},{"assignees":[],"body":"**Environment**\n- OS: Windows\n- Node.js: compatible with the package requirement (>=18)\n- npm: global install\n\n**What I ran**\n```\nnpm uninstall -g @googleworkspace/cli\nnpm cache clean --force\nnpm install -g @googleworkspace/cli\ngws auth setup\n```\n\n**What happened**\nThe command fails with:\n\n```\nError: spawnSync C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe ENOENT\n at Object.spawnSync (node:internal/child_process:1119:20)\n at spawnSync (node:child_process:911:24)\n at C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\binary-install.js:197:24\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {\n errno: -4058,\n code: 'ENOENT',\n syscall: 'spawnSync C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n path: 'C:\\\\Users\\\\Hanekawa\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\@googleworkspace\\\\cli\\\\node_modules\\\\.bin_real\\\\gws.exe',\n spawnargs: [ 'auth', 'setup' ]\n}\n```\n\n**Verification**\nI checked whether the expected binary exists:\n\n```\nTest-Path \"C:\\Users\\Hanekawa\\AppData\\Roaming\\npm\\node_modules\\@googleworkspace\\cli\\node_modules\\.bin_real\\gws.exe\"\n```\n\nResult:\n```\nFalse\n```\n\nSo the package appears to install successfully via npm, but the actual Windows binary is missing from the location the wrapper expects.\n\n**Expected behavior**\n`npm install -g @googleworkspace/cli` should install or place `gws.exe` in the expected path so `gws auth setup` can run.\n\n**Notes**\nThis looks like a packaging/install-script issue specific to Windows, rather than a Node version issue.","labels":[],"number":306,"title":"Windows install is missing `gws.exe`, causing `gws auth setup` to fail with ENOENT","updatedAt":"2026-03-08T07:27:47Z"},{"assignees":[],"body":"```\nnpm install @googleworkspace/cli@0.7.0\nnpm error code 1\nnpm error path /tmp/tmp-package0.36392758024749083/node_modules/@googleworkspace/cli\nnpm error command failed\nnpm error command sh -c node ./install.js\nnpm error Your glibc isn't compatible; trying static musl binary instead\nnpm error Downloading release from https://github.com/googleworkspace/cli/releases/download/v0.7.0/gws-x86_64-unknown-linux-musl.tar.gz\nnpm error Error fetching release: spawnSync tar ENOENT\nnpm error A complete log of this run can be found in: /tmp/cache/_logs/2026-03-06T22_08_26_142Z-debug-0.log\n```\n\nThis appears to be using musl when it should be using glibc.\n\nThis was running on Amazon Linux 2023.","labels":[],"number":284,"title":"npm error Your glibc isn't compatible; trying static musl binary instead spawnSync tar ENOENT","updatedAt":"2026-03-09T21:31:38Z"},{"assignees":[],"body":"Hi team — I found a UX bug while trying the CLI:\n\n## Repro\n\n```bash\ngws auth setup --help\n```\n\nOn machines without `gcloud`, this executes setup and fails with:\n\n```json\n{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n}\n```\n\nExpected behavior is to print setup help/usage and exit successfully.\n\n## Proposed fix\n\nEarly-return from `run_setup()` when `-h/--help` is present, before any setup checks.\n\nI prepared a patch in my fork/branch:\n\n- Branch: `stablegenius49/cli:fix-auth-setup-help-flag`\n- Compare URL: https://github.com/googleworkspace/cli/compare/main...stablegenius49:cli:fix-auth-setup-help-flag\n\nI also added unit tests for the help flag detection.\n\nI couldn't open a PR directly because this repo currently blocks PR creation for non-collaborators (`CreatePullRequest` permission error), so sharing via issue + compare link.\n","labels":[],"number":280,"title":"{\n \"error\": {\n \"code\": 400,\n \"message\": \"gcloud CLI not found. Install it from https://cloud.google.com/sdk/docs/install\",\n \"reason\": \"validationError\"\n }\n} executes setup instead of showing help","updatedAt":"2026-03-06T20:16:56Z"},{"assignees":[],"body":"Hi,\n\nThanks for this project!\n\nI just tried to auth with all scopes and got\n\nSome requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices] If you are a developer of XXXXXXXXXX, see error details. Error 400: invalid_scope\n\nI'm not sure if the scope is invalid or it's because my gcloud project is missing this API or because I'm on enterprise account and do not have super admin permission.\n\n","labels":[],"number":258,"title":"Some requested scopes cannot be shown: [https://www.googleapis.com/auth/cloud-identity.devices]. Error 400: invalid_scope","updatedAt":"2026-03-13T17:42:39Z"},{"assignees":[],"body":"## Problem\n\nThere's no way to send a Gmail message with an attachment using `gws`. Two approaches fail:\n\n### 1. `--json` with base64url-encoded raw MIME — hits OS argument length limit\n\nThe documented approach for sending raw MIME (with `threadId`, `In-Reply-To`, `References` headers, and attachments) is to base64url-encode the entire message and pass it via `--json '{\"raw\": \"...\"}'`. But even a modest ~577KB PDF attachment produces a ~1MB base64 payload, which exceeds macOS's ~256KB `ARG_MAX` for command-line arguments:\n\n```\ngws-behav gmail users messages send --params '{\"userId\": \"me\"}' --json \"{\\\"threadId\\\": \\\"...\\\", \\\"raw\\\": \\\"$RAW\\\"}\"\n# => zsh: argument list too long: gws\n```\n\n### 2. `--upload` with raw MIME file — wrong content type\n\nGmail's `/upload/gmail/v1/users/me/messages/send` endpoint accepts `message/rfc822` for media uploads. But `--upload` wraps the file in `multipart/related` with an auto-detected content type, which Gmail rejects:\n\n```\ngws gmail users messages send --params '{\"userId\": \"me\", \"uploadType\": \"media\"}' --upload message.eml\n# => Media type 'multipart/related; boundary=gws_boundary_...' is not supported.\n```\n\n## Suggested fixes\n\nAny of these would unblock the use case:\n\n1. **`--json-file `** or **`--json @`** — read JSON body from a file (like `curl -d @file.json`)\n2. **`--json -`** — read JSON body from stdin\n3. **`--upload-type `** — let users override the content type for `--upload` (e.g. `--upload-type message/rfc822`)\n4. **Auto-detect `.eml` → `message/rfc822`** in `--upload` content type detection\n\nOptions 1 or 2 are the most general and would help any API method with large request bodies, not just Gmail.\n\n## Environment\n\n- gws 0.4.4\n- macOS Darwin 25.3.0 (arm64)\n\n## Related\n\n- #88 — first-class reply/forward support would also solve this at a higher level\n- #244 — OOM on large file uploads (related large-payload issue)","labels":[],"number":247,"title":"Gmail: cannot send emails with attachments (--json arg limit, --upload wrong content type)","updatedAt":"2026-03-12T01:03:30Z"},{"assignees":[],"body":"gws version: 0.4.4\nOS: macOS (Apple Silicon, MacBook Pro)\nAccount type: Personal @gmail.com\nDescription\nAfter authenticating with gws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects, all Google Drive API calls work correctly but all Apps Script API calls fail with 401 \"invalid authentication credentials.\"\nSteps to Reproduce\n\nInstall gws via npm: npm install -g @googleworkspace/cli\nPlace custom client_secret.json in ~/.config/gws/\nEnable both Google Drive API and Apps Script API in GCP project\nEnable Apps Script API at https://script.google.com/home/usersettings\nAuthenticate:\n\nbashgws auth login --scopes https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/script.projects\n\nExport unmasked credentials (required due to separate Keychain issue — see below):\n\nbashgws auth export --unmasked > ~/.config/gws/credentials.json\n\nVerify auth status shows correct scopes:\n\nbashgws auth status\n# Shows scope_count: 2, token_valid: true, both drive and script.projects scopes listed\n\nTest Drive API — works:\n\nbashgws drive files list --params '{\"pageSize\": 5, \"fields\": \"files(id,name,mimeType)\"}' --format table\n# Returns file listing successfully\n\nTest Apps Script API — fails:\n\nbashgws apps-script projects getContent --params '{\"scriptId\": \"VALID_SCRIPT_ID\"}'\n# Returns 401 error\nExpected Behavior\ngws apps-script projects getContent should return the script project content, since the credentials include the script.projects scope and the Apps Script API is enabled.\nActual Behavior\njson{\n \"error\": {\n \"code\": 401,\n \"message\": \"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\",\n \"reason\": \"unknown\"\n }\n}\nAdditional Context\nScope issue required revoking app access\nInitially, after adding script.projects to an existing auth session, the API returned 403 \"insufficient authentication scopes\" even though gws auth status showed the scope was present. This was resolved by revoking app access at https://myaccount.google.com/permissions and re-authenticating from scratch. After revoking, the error changed from 403 to 401.\nSeparate issue: encrypted credentials not usable for API calls\nOn macOS, gws auth login saves encrypted credentials via OS Keyring, but API calls (e.g., gws drive files list) fail with 401 \"No credentials provided\" unless you first run gws auth export --unmasked > ~/.config/gws/credentials.json to create a plaintext credentials file. The gws auth status command can read the encrypted credentials fine, but the API call code path cannot. This workaround resolves Drive API calls but does not resolve the Apps Script 401.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":205,"title":"Apps Script API returns 401 \"invalid authentication credentials\" while Drive API works with same credentials","updatedAt":"2026-03-06T01:20:33Z"},{"assignees":[],"body":"This may be out of scope, this app doesn't support [Advanced Protection Program](https://landing.google.com/intl/en_in/advancedprotection/) users, with a failure during authentication:\n\n\"Image\"\n\nUnderstandably, this may just be a limitation of Advanced Protection, though it's always a bit of a confusing threat model that I can grant Claude connectors extensive access to my emails and Gdrive, but not read-only scopes for my own app.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":197,"title":"Doesn't support Advanced Protection Program users","updatedAt":"2026-03-14T00:53:12Z"},{"assignees":[],"body":"## Description\n\nWhen creating a comment on a Google Docs file using `gws drive comments create` with an `anchor` field, the comment is posted successfully (HTTP 200) but appears in the Google Docs UI as **\"Original content deleted\"** instead of being anchored to the specified text.\n\n## Steps to Reproduce\n\n```bash\ngws drive comments create \\\n --params '{\"fileId\": \"\", \"fields\": \"id,content,anchor,quotedFileContent\"}' \\\n --json '{\n \"content\": \"Hello\",\n \"quotedFileContent\": {\n \"mimeType\": \"text/plain\",\n \"value\": \"Overview\"\n },\n \"anchor\": \"{\\\"r\\\":\\\"head\\\",\\\"a\\\":[{\\\"lt\\\":\\\"b\\\",\\\"lv\\\":\\\"Overview\\\"}]}\"\n }'\n```\n\n## Expected Behavior\n\nThe comment should be anchored to the matching text in the document, or the CLI/docs should clearly indicate that anchored comments are not supported for Google Docs files via the API.\n\n## Actual Behavior\n\nThe API returns a 200 with the anchor saved, but the comment appears in the Google Docs UI as **\"Original content deleted\"** — no text is highlighted and the anchor has no visible effect.\n\n## Additional Context\n\n- This appears to be a known Google Drive API limitation: for Google Workspace editor files (Docs, Sheets, Slides), the `anchor` field is saved but ignored by the editor UI. See the [official docs](https://developers.google.com/workspace/drive/api/v3/manage-comments#add_an_anchored_comment_to_the_latest_revision_of_a_document): *\"The anchor is saved and returned when retrieving the comment, however Google Workspace editor apps treat these comments as un-anchored comments.\"*\n- It would be helpful if `gws` surfaced a warning when using `anchor` on a `application/vnd.google-apps.kix` (Google Docs) file, since the result is misleading.\n- `gws` version: `0.4.4`","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":169,"title":"drive comments create: anchor field results in 'Original content deleted' in Google Docs","updatedAt":"2026-03-05T20:38:24Z"},{"assignees":[],"body":"## Summary\nWhen authenticating with read-only scopes using `gws auth login --readonly`, exporting credentials with:\n\n```bash\ngws auth export --unmasked > credentials.json\n```\n\nand then using those credentials on another machine (OpenClaw), the resulting access appears to still allow full/write operations.\n\n## Reproduction\n1. Run:\n ```bash\n gws auth login --readonly\n ```\n2. Export credentials:\n ```bash\n gws auth export --unmasked > credentials.json\n ```\n3. Move `credentials.json` to an external machine/environment (OpenClaw).\n4. Use the exported credentials with `gws` there.\n5. Attempt a write operation (for example, Gmail send).\n\n## Actual behavior\nWrite/full-access behavior is still possible on the external machine, even though login was done with `--readonly`.\n\n## Expected behavior\nRead-only scope enforcement should persist with exported credentials and be enforced by Google-issued token scopes, not only by client-side command restrictions.\n\n## Questions\n- Are `--readonly` scopes currently enforced only in the local client flow, rather than in the OAuth token/scopes themselves?\n- If so, what is the recommended way to ensure exported credentials are truly read-only on external machines?\n- Can `--readonly` become the default behavior in the installer/setup flow (or at least a strongly guided default), not just a local/client-side option?\n\n## Why this matters\nUsers exporting credentials for remote execution expect least-privilege guarantees to carry over across machines/environments.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":168,"title":"gws auth login --readonly + auth export --unmasked appears to allow full access on external machine","updatedAt":"2026-03-12T12:31:16Z"},{"assignees":[],"body":"Does this project plan to support browsing Google Photos on the CLI? It would be nice to do analyses on photos using LLMs.","labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":163,"title":"Any plan to support Google Photos?","updatedAt":"2026-03-05T20:48:15Z"},{"assignees":[],"body":"Add an option to make HITL / requests gateway so those of us that are using agents and want to gatekeep them for oauth and write actions can do it","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPT1Q","name":"area: mcp","description":"","color":"c46210"}],"number":158,"title":"Add built in HITL that the agents can't edit in code","updatedAt":"2026-03-05T20:38:09Z"},{"assignees":[],"body":"I can't get `npx -y @googleworkspace/cli@latest auth login` to work properly. I triple checked the settings of my auth:\n* Status: testing\n* User type: external\n* Test user: includes myself\n* Client ID: Desktop\n\nThe only \"strange\" thing is that I can't set a redirect URI, which according to `auth setup` is necessary.\n\n`auth login` shows an URL, after browsing to it and approving all scopes on the \"... wants to access your Google Account\" page, I get this on a page https://accounts.google.com/info/unknownerror?...:\n\n\"Image\"","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":157,"title":"\"Something went wrong\" in oauth screen","updatedAt":"2026-03-05T20:37:48Z"},{"assignees":[],"body":"## Description\n\nOn Windows, the `.encryption_key` fallback path in `credential_store.rs` writes the AES-256-GCM encryption key as a plaintext file with no access controls applied.\n\nOn Unix, the key file is explicitly created with mode `0o600` (user-read/write only):\n\n```rust\n#[cfg(unix)]\n{\n use std::os::unix::fs::OpenOptionsExt;\n let mut options = std::fs::OpenOptions::new();\n options.write(true).create(true).truncate(true).mode(0o600);\n ...\n}\n#[cfg(not(unix))]\n{\n let _ = std::fs::write(&key_file, &b64_key); // no permission hardening\n}\n```\n\nOn Windows, `std::fs::write` is used with no equivalent ACL restrictions, meaning the key file inherits default directory permissions and is readable by other users or processes on the same machine.\n\nThis key decrypts `credentials..enc`, which contains the OAuth refresh token. An attacker with local read access can decrypt the token and gain long-lived Google account access.\n\n## Affected Code\n\nThree locations in `src/credential_store.rs` write sensitive files without Windows ACLs:\n\n1. **Line 112-115** — `.encryption_key` file (keyring `NoEntry` fallback)\n2. **Line 172-175** — `.encryption_key` file (keyring failure fallback)\n3. **Lines 246, 305** — `credentials.*.enc` files (via `atomic_write`)\n\nAdditionally, the config **directory** itself (`~/.config/gws/`) gets `0o700` on Unix (lines 89-99, 150-159) but no ACL on Windows.\n\n## Fix Plan\n\n### Option A: `windows-acl` crate (recommended)\n```toml\n[target.'cfg(windows)'.dependencies]\nwindows-acl = \"0.3\"\n```\n\nCreate a helper function:\n```rust\n#[cfg(windows)]\nfn set_owner_only_acl(path: &Path) -> std::io::Result<()> {\n use windows_acl::acl::ACL;\n // Get current user SID, set DACL to owner-only\n}\n```\n\nCall it after every file write on Windows (same locations where `#[cfg(unix)]` sets `0o600`).\n\n### Option B: Raw Win32 via `windows` crate\nUse `SetNamedSecurityInfoW` directly. More control but more boilerplate.\n\n### Option C: Interim warning\nPrint a warning on Windows when falling back to file storage:\n```\n⚠️ Warning: Encryption key stored without access controls. \n Run 'icacls %USERPROFILE%\\.config\\gws /inheritance:r /grant:r %USERNAME%:F' to restrict access.\n```\n\n## Files to Modify\n\n- `Cargo.toml` — add Windows-only dependency\n- `src/credential_store.rs` — add `#[cfg(windows)]` ACL blocks at all 3 write locations + directory creation\n- Optionally: extract a `set_secure_permissions(path)` helper that dispatches to `chmod 0o600` on Unix and ACL on Windows\n\n## Verification\n\n- Windows: `icacls ~/.config/gws/.encryption_key` should show only current user\n- Windows: `icacls ~/.config/gws/credentials.*.enc` should show only current user\n- Unix: behavior unchanged (`ls -la` confirms `0o600`)\n- CI: Windows test runner should exercise the keyring fallback path\n\n## Workaround\n\nUse Windows Credential Manager (the primary keyring path). There is currently no way to force this from the CLI. If keyring works, the file fallback is never used.\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":153,"title":"[Security] Windows: encryption key written to plaintext file with no access controls","updatedAt":"2026-03-07T16:08:16Z"},{"assignees":[],"body":"## Problem\n\nWhen an AI agent tries to help a user unsubscribe from mailing lists using `gws`, there is no guided path. The agent has to:\n\n1. Search for emails containing \"unsubscribe\"\n2. Fetch each message with `format: full` to extract `List-Unsubscribe` headers\n3. Guess whether to use the HTTPS URL or mailto address\n4. Attempt `curl` GET/POST requests to unsubscribe URLs (which mostly load confirmation pages requiring a browser)\n5. Fall back to sending emails to `mailto:` unsubscribe addresses via `gws gmail users messages send`\n\nThis entire process is trial-and-error, fragile, and often fails.\n\nMeanwhile, **Gmail natively supports one-click unsubscribe via RFC 8058**. Since June 2024, Google requires bulk senders to include `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers. The correct unsubscribe mechanism is a simple `POST` to the `List-Unsubscribe` HTTPS URL with the body `List-Unsubscribe=One-Click` — no browser needed, no mailto, no guessing.\n\n## Proposal\n\nAdd a `gws gmail +unsubscribe` helper skill that:\n\n1. **Lists candidates** — Scans recent emails for `List-Unsubscribe` and `List-Unsubscribe-Post` headers, groups by sender, and shows frequency/volume\n2. **Executes RFC 8058 one-click unsubscribe** — Sends the correct `POST` request with `List-Unsubscribe=One-Click` body to the HTTPS unsubscribe URL\n3. **Falls back gracefully** — For senders without RFC 8058 support, shows the mailto address or URL so the agent/user can decide\n\n### Example UX\n\n```bash\n# List subscription candidates with volume\ngws gmail +unsubscribe --list --max 200\n\n# Unsubscribe from a specific sender\ngws gmail +unsubscribe --from \"noreply@mail.michaelpage.com.sg\"\n\n# Dry run\ngws gmail +unsubscribe --from \"team@mail.cerebralvalley.ai\" --dry-run\n```\n\n## Why this matters for agents\n\nThe existing `gws-gmail-triage` skill is great for reading, but there's no skill for the common \"clean up my inbox subscriptions\" workflow. Without a dedicated skill, agents waste many API calls fumbling through message formats, header extraction, and URL guessing — exactly the kind of boilerplate `gws` is designed to eliminate.\n\n## References\n\n- [RFC 8058 — Signaling One-Click Functionality for List Email Headers](https://datatracker.ietf.org/doc/html/rfc8058)\n- [Gmail bulk sender requirements (June 2024)](https://support.google.com/a/answer/81126)","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":114,"title":"Add a gmail +unsubscribe skill for one-click list unsubscribe (RFC 8058)","updatedAt":"2026-03-06T07:16:56Z"},{"assignees":[],"body":"It would be convenient to be able to install this tool using Homebrew 😄 ","labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"}],"number":90,"title":"Feature request: Homebrew Formula","updatedAt":"2026-03-05T20:38:12Z"},{"assignees":[],"body":"Hi! I love skills. But I also hate how they create issues for agents.\n\nContext pollution is real and I can already see that some of skills included in the repo cross-reference other skills. Problem with this is that the top level skills all get loaded into , and 40 of them, added to any other skills that people use, will make for a really bad experience.\n\nObviously, one might say that not all of them need to be installed at once, but with how heavy the cross-references are, basically none of them are skippable.\n\nProposal:\n\nEven if it costs some duplication, bundle the granular skills in a top level structure, with /references/ folder.\n\nI believe this will save both the users and the devs a lot of headaches, and it will be easier for users to manage a situation where they might only need parts of the included skills.\n\n","labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":82,"title":"Optimisation: 40 skills => less skills + references","updatedAt":"2026-03-06T01:20:35Z"}] diff --git a/merged_prs.json b/merged_prs.json deleted file mode 100644 index 53ee5768..00000000 --- a/merged_prs.json +++ /dev/null @@ -1 +0,0 @@ -[{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7x3doo","number":404,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/404"}],"number":482,"title":"fix(gmail): RFC 2047 encode non-ASCII display names in address headers","updatedAt":"2026-03-13T23:22:00Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":481,"title":"chore: release versions","updatedAt":"2026-03-13T23:29:21Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":480,"title":"feat(timezone): use Google account timezone for day-boundary calculations","updatedAt":"2026-03-13T22:42:12Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":479,"title":"chore: release versions","updatedAt":"2026-03-13T21:31:40Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":478,"title":"feat: add opt-in structured HTTP request logging via tracing","updatedAt":"2026-03-13T21:29:07Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wUIxh","number":244,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/244"}],"number":477,"title":"fix: stream multipart uploads to avoid OOM on large files","updatedAt":"2026-03-13T20:58:27Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":475,"title":"chore: release versions","updatedAt":"2026-03-13T21:02:32Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7x1K7_","number":399,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/399"}],"number":467,"title":"docs: add Homebrew as an installation option in README","updatedAt":"2026-03-13T19:42:59Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":454,"title":"chore: sync skills with Discovery API","updatedAt":"2026-03-13T19:39:40Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":453,"title":"chore: release versions","updatedAt":"2026-03-13T20:01:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":451,"title":"fix(docs): correct broken flag names in recipes","updatedAt":"2026-03-13T00:15:15Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":449,"title":"chore: fix maintainer email typo in flake.nix and harden coverage.sh","updatedAt":"2026-03-13T00:18:58Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":445,"title":"test(gmail): add unit tests for +triage argument parsing","updatedAt":"2026-03-13T19:35:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":444,"title":"fix(security): validate space name in chat +send","updatedAt":"2026-03-13T19:57:27Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wVZBB","number":259,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/259"}],"number":443,"title":"fix(calendar): use local timezone for agenda day boundaries","updatedAt":"2026-03-13T19:17:55Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7x8C7Y","number":408,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/408"}],"number":441,"title":"fix(auth): validate --subscription in gmail +watch and deduplicate PUBSUB_API_BASE","updatedAt":"2026-03-12T20:28:27Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":440,"title":"chore: release versions","updatedAt":"2026-03-12T20:35:43Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":437,"title":"ci: avoid scope creep in gemini code assist reviews","updatedAt":"2026-03-12T18:43:20Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":436,"title":"chore: release versions","updatedAt":"2026-03-12T18:06:27Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xprae","number":389,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/389"}],"number":435,"title":"fix(auth): auto-recover from stale encrypted credentials after upgrade","updatedAt":"2026-03-12T17:59:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":434,"title":"chore: release versions","updatedAt":"2026-03-12T16:16:32Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":433,"title":"chore: release versions","updatedAt":"2026-03-12T16:07:07Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":429,"title":"feat: add --upload-content-type flag and smart MIME inference for uploads","updatedAt":"2026-03-13T23:10:34Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":428,"title":"feat(error): add structured exit codes for scriptable error handling","updatedAt":"2026-03-12T16:03:18Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":427,"title":"docs: document helper commands and the + prefix convention","updatedAt":"2026-03-12T16:06:14Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":417,"title":"feat(gmail): add --html flag for HTML email composition","updatedAt":"2026-03-12T18:34:10Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7w0s3N","number":300,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/300"}],"number":415,"title":"fix(schema): expose repeated field and expand array query params","updatedAt":"2026-03-13T19:14:58Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7w38zb","number":310,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/310"},{"id":"I_kwDORcxyRs7w_cbk","number":316,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/316"}],"number":414,"title":"fix(auth): map People service to contacts/directory scope prefixes","updatedAt":"2026-03-13T19:30:10Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wYcZh","number":268,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/268"}],"number":412,"title":"docs: warn about zsh ! history expansion in sheet range examples","updatedAt":"2026-03-12T16:16:07Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7w49i3","number":311,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/311"}],"number":410,"title":"fix(sheets): preserve multi-row structure in +append --json-values","updatedAt":"2026-03-13T19:13:58Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xq8wj","number":392,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/392"}],"number":407,"title":"fix(auth): refresh OAuth2 tokens in long-running watch/subscribe loops","updatedAt":"2026-03-12T20:16:11Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":390,"title":"chore: release versions","updatedAt":"2026-03-11T00:49:04Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":388,"title":"chore: Update workflow to use `GOOGLEWORKSPACE_BOT_TOKEN` and `googleworkspace-bot` identity for operations.","updatedAt":"2026-03-10T20:25:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":387,"title":"chore: release versions","updatedAt":"2026-03-10T20:18:05Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":385,"title":"fix(ci): check remote tags in tag-release.sh","updatedAt":"2026-03-10T20:06:17Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":384,"title":"ci: use bot token for /gemini review comment","updatedAt":"2026-03-10T19:23:55Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":373,"title":"fix(auth): enable native keyring backends on top of #359","updatedAt":"2026-03-12T15:59:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":362,"title":"feat(gmail): add recipient management flags and extract shared message builder","updatedAt":"2026-03-11T04:25:49Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":359,"title":"feat(credential_store): add GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND env var","updatedAt":"2026-03-11T17:18:29Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":353,"title":"fix(gmail): bring +forward behavior in line with Gmail web UI","updatedAt":"2026-03-10T00:32:12Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wTXZE","number":236,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/236"}],"number":352,"title":"fix(auth): format and deduplicate dynamic scope fallback","updatedAt":"2026-03-09T23:42:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":351,"title":"chore: release versions","updatedAt":"2026-03-10T18:53:13Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":350,"title":"chore: release versions","updatedAt":"2026-03-09T23:24:04Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":349,"title":"ci: run smoketest immediately after linux build, gate other builds on it","updatedAt":"2026-03-09T22:04:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":348,"title":"chore: release versions","updatedAt":"2026-03-09T23:17:45Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":347,"title":"fix(ci): add missing tool input to install-action for cargo-llvm-cov","updatedAt":"2026-03-09T21:41:39Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":346,"title":"ci: make sccache setup resilient to cache API outages","updatedAt":"2026-03-09T21:00:12Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7xRA_M","number":344,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/344"}],"number":345,"title":"fix(credential_store): stop persisting encryption key file when keyring is available","updatedAt":"2026-03-09T23:11:46Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":341,"title":"ci: pin all actions to a sha","updatedAt":"2026-03-09T19:52:51Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":339,"title":"ci: ignore sccache errors when GitHub down","updatedAt":"2026-03-09T18:54:08Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":331,"title":"build(ci): optimize nix and windows cache layers","updatedAt":"2026-03-09T17:54:17Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":330,"title":"chore: release versions","updatedAt":"2026-03-09T19:16:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":329,"title":"fix(ci): use default GITHUB_TOKEN for PR reviews","updatedAt":"2026-03-09T17:07:07Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":326,"title":"chore: Synced file(s) with googleworkspace/.github","updatedAt":"2026-03-09T17:47:49Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXIUW","number":266,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/266"}],"number":322,"title":"fix: RFC 2047 encode non-ASCII email subjects in +send helper","updatedAt":"2026-03-09T18:29:48Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":313,"title":"chore: release versions","updatedAt":"2026-03-09T00:30:40Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wXH30","number":265,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/265"}],"number":304,"title":"fix: use gmail.readonly scope in +triage to avoid metadata scope 403","updatedAt":"2026-03-09T23:08:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":302,"title":"fix: use native OS certificate store for TLS","updatedAt":"2026-03-09T17:40:59Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":295,"title":"fix(auth): prioritize local project configuration over global ADC for quota attribution","updatedAt":"2026-03-09T00:21:02Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wlYBe","number":283,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/283"}],"number":288,"title":"fix: handle array-of-arrays in CSV formatter","updatedAt":"2026-03-11T00:46:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":277,"title":"chore: release versions","updatedAt":"2026-03-07T01:31:57Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":275,"title":"BREAKING CHANGE: Remove MCP server mode","updatedAt":"2026-03-10T00:11:58Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":254,"title":"chore: Remove a subset of skills and recipes along with their service entries and registry references","updatedAt":"2026-03-06T03:55:59Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":253,"title":"feat!: remove multi-account, DWD, and impersonation support","updatedAt":"2026-03-06T03:43:52Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":250,"title":"chore: Remove dwd support","updatedAt":"2026-03-06T03:26:28Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":243,"title":"chore: release versions","updatedAt":"2026-03-06T03:55:18Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":242,"title":"fix(client): Move x-goog-user-project header to API requests only","updatedAt":"2026-03-06T02:03:44Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wCYOn","number":152,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/152"}],"number":240,"title":"fix: use .zip for Windows npm installer to fix Git Bash install failure","updatedAt":"2026-03-06T02:02:31Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":228,"title":"chore: release versions","updatedAt":"2026-03-06T01:20:03Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wKfRa","number":181,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/181"},{"id":"I_kwDORcxyRs7wR6BO","number":221,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/221"}],"number":223,"title":"fix: respect account selection in MCP server and CLI --account flag","updatedAt":"2026-03-06T20:59:39Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wINp6","number":171,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/171"}],"number":222,"title":"docs: document all environment variables and enable CONFIG_DIR override","updatedAt":"2026-03-06T00:43:34Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":218,"title":"chore: release versions","updatedAt":"2026-03-05T23:35:37Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":217,"title":"fix: clean up nits from PR #175 auth fix","updatedAt":"2026-03-05T23:32:50Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":216,"title":"ci: add path filtering to skip expensive jobs on non-Rust changes","updatedAt":"2026-03-05T22:28:47Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":215,"title":"fix(client): For ADC, send x-goog-user-project header","updatedAt":"2026-03-05T23:45:28Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wQkNd","number":212,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/212"}],"number":213,"title":"fix(mcp): conditionally include body/upload in tool schemas, drop empty body on execution","updatedAt":"2026-03-06T00:30:41Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":209,"title":"ci: auto-trigger Gemini Code Assist review on PR push","updatedAt":"2026-03-05T21:54:05Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wPmsr","number":206,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/206"}],"number":208,"title":"fix: isolate flaky auth tests from host ADC credentials","updatedAt":"2026-03-05T23:00:24Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":207,"title":"chore: release versions","updatedAt":"2026-03-05T23:35:43Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":204,"title":"chore: auto triage and fmt","updatedAt":"2026-03-05T21:07:45Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":203,"title":"chore: use labels","updatedAt":"2026-03-05T20:49:46Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wJPN0","number":174,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/174"}],"number":200,"title":"fix: prevent gmail.metadata scope from blocking query parameters","updatedAt":"2026-03-05T23:34:50Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":199,"title":"chore: release versions","updatedAt":"2026-03-05T21:03:48Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":193,"title":"fix: log token cache errors instead of silently swallowing them","updatedAt":"2026-03-05T23:35:46Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":192,"title":"fix: replace strip_suffix(\".readonly\").unwrap() with unwrap_or","updatedAt":"2026-03-05T23:46:20Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":186,"title":"fix(setup): drain stale keypresses between TUI screen transitions","updatedAt":"2026-03-05T17:45:35Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":185,"title":"chore: steps to reduce contributor friction","updatedAt":"2026-03-05T17:08:53Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wK1fc","number":182,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/182"}],"number":183,"title":"fix(executor): add Content-Length: 0 for body-less POST requests","updatedAt":"2026-03-05T21:11:37Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":180,"title":"chore: release versions","updatedAt":"2026-03-05T17:56:18Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7v-Ciy","number":119,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/119"},{"id":"I_kwDORcxyRs7wAXev","number":138,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/138"}],"number":177,"title":"fix: allow services flag for login and improve default list for consumer accounts","updatedAt":"2026-03-05T16:16:09Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":176,"title":"chore(docs): easier cmd to install from source","updatedAt":"2026-03-05T16:22:23Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":175,"title":"Fix auth failures when accounts.json registry is missing","updatedAt":"2026-03-05T23:15:26Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":172,"title":"feat(mcp): add compact tool mode to reduce context window usage","updatedAt":"2026-03-05T16:23:45Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":154,"title":"fix: migrate to new Darwin SDK pattern in nix flake","updatedAt":"2026-03-06T02:22:25Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":147,"title":"chore: release versions","updatedAt":"2026-03-05T09:14:56Z"},{"baseRefName":"main","closingIssuesReferences":[{"id":"I_kwDORcxyRs7wAisd","number":139,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/139"}],"number":146,"title":"fix: use reverse video for TUI highlight style","updatedAt":"2026-03-05T09:12:40Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":142,"title":"chore: release versions","updatedAt":"2026-03-05T08:53:10Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":140,"title":"chore: release versions","updatedAt":"2026-03-05T08:44:54Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":136,"title":"docs: improve readme especially auth which is complex no matter what","updatedAt":"2026-03-05T08:48:50Z"},{"baseRefName":"main","closingIssuesReferences":[],"number":134,"title":"fix: use ~/.config/gws on all platforms for consistent config path","updatedAt":"2026-03-05T08:31:03Z"}] diff --git a/open_prs.json b/open_prs.json deleted file mode 100644 index e26bdb88..00000000 --- a/open_prs.json +++ /dev/null @@ -1 +0,0 @@ -[{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"},{"id":"LA_kwDORcxyRs8AAAACaTQCGw","name":"area: docs","description":"","color":"0052cc"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":488,"title":"feat(credential_store): default keyring backend to OS-only, add keyring-with-file option","updatedAt":"2026-03-14T09:51:54Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wHXwu","number":169,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/169"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":487,"title":"fix(drive): warn when anchor field targets Workspace editor files","updatedAt":"2026-03-14T05:59:31Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQFcg","name":"area: http","description":"","color":"d876e3"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":486,"title":"feat(http): add --verbose / -v flag for request/response diagnostics","updatedAt":"2026-03-14T06:13:57Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":485,"title":"fix(http): route error and empty-result messages to stderr","updatedAt":"2026-03-14T05:48:20Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":484,"title":"fix(validate): reject dangerous Unicode characters in input validation","updatedAt":"2026-03-14T05:44:11Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7ytu2a","number":473,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/473"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"}],"number":476,"title":"Add admin-directory as built-in service alias for Admin SDK Directory API","updatedAt":"2026-03-13T20:22:40Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQCGw","name":"area: docs","description":"","color":"0052cc"}],"number":468,"title":"Revise OAuth login fix and consent screen instructions","updatedAt":"2026-03-13T11:18:18Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xABA3","number":317,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/317"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":466,"title":"feat(error): display colored error labels on TTY stderr","updatedAt":"2026-03-13T20:40:56Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yYHMc","number":438,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/438"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":465,"title":"feat(gmail): add +read helper for extracting message body as plain text","updatedAt":"2026-03-13T20:42:39Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yD8x1","number":419,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/419"},{"id":"I_kwDORcxyRs7yliQV","number":461,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/461"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":464,"title":"feat(calendar): add --conference flag to +insert for Google Meet links","updatedAt":"2026-03-13T20:42:52Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":463,"title":"fix(sheets): report error for invalid --json-values instead of silent empty append","updatedAt":"2026-03-13T20:43:08Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yJP70","number":424,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/424"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":460,"title":"feat: add `--draft-only` mode for agent workflows","updatedAt":"2026-03-14T05:48:40Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":457,"title":"feat: add --dry-run support to events helper commands","updatedAt":"2026-03-13T09:57:32Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTQFcg","name":"area: http","description":"","color":"d876e3"}],"number":448,"title":"fix(security): cap Retry-After sleep and sanitize mimeType in uploads","updatedAt":"2026-03-13T20:43:43Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":447,"title":"fix(security): validate --upload and --output paths against traversal","updatedAt":"2026-03-13T20:43:57Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":446,"title":"fix: report auth errors instead of silently proceeding unauthenticated","updatedAt":"2026-03-13T20:44:09Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7wkDt1","number":280,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/280"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":442,"title":"fix: `gws auth setup --help` prints usage instead of running wizard","updatedAt":"2026-03-13T20:44:23Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTQCGw","name":"area: docs","description":"","color":"0052cc"},{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":431,"title":"feat: support GOOGLE_WORKSPACE_CLI_API_BASE to override Google API base URL","updatedAt":"2026-03-12T16:00:46Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":426,"title":"feat(formatter): add --format tsv output format","updatedAt":"2026-03-13T04:24:40Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yHkZX","number":422,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/422"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"},{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"}],"number":423,"title":"feat: add HTTP proxy support via environment variables","updatedAt":"2026-03-14T05:54:47Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7yFvFe","number":420,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/420"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPPVw","name":"area: skills","description":"","color":"7057ff"}],"number":421,"title":"docs(skills): add COOKBOOK.md for gws-docs with tab and formatting recipes","updatedAt":"2026-03-12T02:11:56Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xAq2T","number":319,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/319"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"}],"number":413,"title":"feat(auth): sort OAuth scopes by service, then access level","updatedAt":"2026-03-13T20:36:06Z"},{"closingIssuesReferences":[{"id":"I_kwDORcxyRs7xourR","number":386,"repository":{"id":"R_kgDORcxyRg","name":"cli","owner":{"id":"MDEyOk9yZ2FuaXphdGlvbjM3MDgzNTk=","login":"googleworkspace"}},"url":"https://github.com/googleworkspace/cli/issues/386"}],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTPRQg","name":"area: auth","description":"","color":"1d76db"},{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTQH5w","name":"area: discovery","description":"","color":"006b75"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":397,"title":"feat: expose minimal library crate for programmatic API access","updatedAt":"2026-03-11T08:40:41Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":395,"title":"feat(gmail): add --attachment flag to +send, +reply, +reply-all, +forward","updatedAt":"2026-03-11T14:09:01Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTP5NQ","name":"area: distribution","description":"","color":"e07be0"},{"id":"LA_kwDORcxyRs8AAAACaTQFcg","name":"area: http","description":"","color":"d876e3"},{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":374,"title":"fix(security): validate --upload and --output file paths","updatedAt":"2026-03-11T03:57:06Z"},{"closingIssuesReferences":[],"labels":[{"id":"LA_kwDORcxyRs8AAAACaTcdhw","name":"area: core","description":"Core CLI parsing, commands, error handling, utilities","color":"5319e7"}],"number":318,"title":"[Night Shift] Fix +append --json-values multi-row bug","updatedAt":"2026-03-12T00:57:59Z"}] From 937c6bf1e2c1547a95b32b9ab03332f06742ab3c Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Sun, 15 Mar 2026 23:20:50 +0530 Subject: [PATCH 7/7] fix(auth): improve robustness of prompt=consent injection --- src/auth_commands.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/auth_commands.rs b/src/auth_commands.rs index 17da082f..1b979099 100644 --- a/src/auth_commands.rs +++ b/src/auth_commands.rs @@ -213,8 +213,12 @@ impl yup_oauth2::authenticator_delegate::InstalledFlowDelegate for CliFlowDelega .map(|i| pos + i) .unwrap_or(display_url.len()); let current_prompt = &display_url[pos + 7..end_pos]; - if !current_prompt.contains("consent") { - let new_prompt = format!("{}%20consent", current_prompt); + if !current_prompt.split("%20").any(|p| p == "consent") { + let new_prompt = if current_prompt.is_empty() { + "consent".to_string() + } else { + format!("{}%20consent", current_prompt) + }; display_url.replace_range(pos + 7..end_pos, &new_prompt); } } else if display_url.contains('?') {