How to Build a RAG Assistant with Google Workspace CLI and Gemini
By Braincuber Team
Published on April 18, 2026
Google Workspace CLI (gws) is an AI-optimized command-line tool that provides structured, scriptable access to your entire Google Workspace including Drive, Gmail, Calendar, Sheets, Docs, and Chat. Written in Rust (99.3%) but distributed via npm, it gives you dynamic command discovery, agent-native design, and native MCP server support.
What You'll Learn:
- Install and verify Google Workspace CLI
- Set up GCP project and OAuth credentials
- Authenticate the CLI against your Google account
- Enable Google Drive API
- Install Python dependencies for RAG pipeline
- Set up Gemini API key
- Build and run the RAG assistant
What Is the Google Workspace CLI?
The Google Workspace CLI (gws) is an AI-optimized command-line tool for your entire Google Workspace ecosystem. It stands out for several key features:
Dynamic Command Discovery
gws reads Google's Discovery Service at runtime. When Google adds new API endpoints, the CLI picks them up automatically without a version bump.
Agent-Native Design
100+ pre-built agent skills and 50 curated recipes. Every output is structured JSON, so LLMs can consume it directly without parsing.
Native MCP Server Support
Run gws as a local MCP server and give your AI agent full Workspace tool access without writing API clients.
Model Armor Integration
Pipe API responses through Google Cloud Model Armor to sanitize against prompt injection for production agents.
How the RAG Pipeline Works
At a high level, the RAG assistant follows this architecture:
| Component | Purpose |
|---|---|
| gws CLI | Fetches Drive files (Docs, Sheets, text) |
| Sentence Transformers | Embeds document chunks locally |
| ChromaDB | Persists vector index to disk |
| Gemini 2.5 Flash | Generates grounded responses |
Step 1: Install the CLI
The CLI is distributed as an npm package wrapping pre-built Rust binaries. No Rust toolchain is needed.
npm install -g @googleworkspace/cli
gws --version
which gws
Path Note
If gws is not found after installation, make sure your npm global bin directory is on your PATH.
Step 2: Create a GCP Project
The CLI authenticates through a GCP project's OAuth client. You need to create a project before logging in.
Create Project
Go to console.cloud.google.com, click the project dropdown, select New Project, and give it a name.
Create OAuth Credentials
Navigate to APIs & Services > Credentials, click Create Credentials, and select OAuth 2.0 Client ID.
Choose Application Type
Select Desktop app as the application type. Note the Client ID and Client Secret.
Web App vs Desktop App
Desktop app clients handle arbitrary localhost ports automatically. Web application clients require you to manually add every http://localhost:PORT to authorized redirect URIs.
Step 3: Authenticate
Export your OAuth credentials and run the setup wizard.
export GOOGLE_WORKSPACE_CLI_CLIENT_ID="CLIENT_ID"
export GOOGLE_WORKSPACE_CLI_CLIENT_SECRET="CLIENT_SECRET"
gws auth setup --project YOUR_PROJECT_ID --login
The setup wizard will ask which Google account to use, open a browser for OAuth consent, and request scopes (Drive, Gmail, Calendar, etc.).
gws auth status
Step 4: Test Drive Access
Quick sanity check to confirm the CLI can talk to your Drive.
gws drive files list --params '{"pageSize": 5}'
403 Error Fix
If you see a "403 serviceusage.services.use" error, your OAuth credentials are pointed at a different GCP project. Re-run setup with the correct project ID.
Step 5: Enable Google Drive API
GCP separates authentication from API activation. Even with valid OAuth credentials, you must explicitly enable the Drive API.
gcloud services enable drive.googleapis.com --project YOUR_PROJECT_ID
Or enable via the browser at https://console.developers.google.com/apis/api/drive.googleapis.com/overview?project=[PROJECT_ID]
Step 6: Install Python Dependencies
Create a virtual environment and install the RAG pipeline dependencies.
cd /path/to/your/project
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
The requirements.txt includes:
chromadb==1.5.5
google-genai==1.70.0
langchain-text-splitters==1.1.1
sentence-transformers==5.3.0
ChromaDB
Local vector database that stores embeddings to ./chroma_db on disk. Loads on subsequent runs without re-embedding.
google-genai
Official Python SDK for Gemini. Streams responses token-by-token via generate_content_stream.
Step 7: Set Up Gemini API Key
Get your API key from Google AI Studio and export it.
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
echo 'export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"' >> ~/.zshrc
source ~/.zshrc
Model Note
The app defaults to gemini-2.5-flash. Override with GEMINI_MODEL="gemini-2.5-pro" for stronger reasoning. Note that gemini-2.0-flash has been retired.
Step 8: Build the RAG Pipeline
The pipeline consists of four files: fetcher.py, vector_store.py, main.py, and requirements.txt.
fetcher.py - Drive Access via gws
This module bridges the gws CLI and Python. All Drive interactions go through subprocess calls to gws, and structured JSON output is parsed into Python dicts.
import subprocess
import json
import tempfile
import os
def check_auth() -> bool:
cmd = ["gws", "drive", "about", "get", "--params", '{"fields": "user"}']
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
def fetch_document_list(limit=10) -> list[dict]:
q = (
"(mimeType='text/plain' or mimeType='application/vnd.google-apps.document' "
"or mimeType='application/vnd.google-apps.spreadsheet') and trashed=false"
)
cmd = [
"gws", "drive", "files", "list",
"--params",
json.dumps({"q": q, "pageSize": limit, "orderBy": "modifiedTime desc"}),
]
result = subprocess.run(cmd, capture_output=True, text=True)
data = json.loads(result.stdout)
return data.get("files", [])
def download_document(file_id: str, mime_type: str) -> str:
fd, out_path = tempfile.mkstemp(prefix="gws_", suffix=".bin")
os.close(fd)
try:
if mime_type == "application/vnd.google-apps.document":
cmd = ["gws", "drive", "files", "export",
"--params", json.dumps({"fileId": file_id, "mimeType": "text/plain"}),
"-o", out_path]
elif mime_type == "application/vnd.google-apps.spreadsheet":
cmd = ["gws", "drive", "files", "export",
"--params", json.dumps({"fileId": file_id, "mimeType": "text/csv"}),
"-o", out_path]
subprocess.run(cmd, capture_output=True)
with open(out_path, "rb") as f:
return f.read().decode("utf-8", errors="replace")
finally:
os.unlink(out_path)
vector_store.py - Local Embeddings with ChromaDB
This module chunks text, generates embeddings locally using Sentence Transformers, persists to ChromaDB, and exposes a query() method.
import chromadb
from chromadb.api import embedding_functions
from langchain_text_splitters import RecursiveCharacterTextSplitter
class VectorStore:
def __init__(self, persist_directory="./chroma_db"):
self.client = chromadb.PersistentClient(path=persist_directory)
self.collection = self.client.get_or_create_collection(
name="drive_documents",
embedding_function=embedding_functions.DefaultEmbeddingFunction()
)
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
def add_document(self, file_id: str, filename: str, text: str):
chunks = self.text_splitter.split_text(text)
ids = [f"{file_id}_{i}" for i in range(len(chunks))]
metadatas = [{"source": filename, "file_id": file_id} for _ in chunks]
self.collection.upsert(documents=chunks, metadatas=metadatas, ids=ids)
def document_exists(self, file_id: str) -> bool:
try:
results = self.collection.get(where={"file_id": file_id}, limit=1)
return len(results["ids"]) > 0
except Exception:
return False
def query(self, query_text: str, n_results: int = 3):
return self.collection.query(
query_texts=[query_text],
n_results=n_results
)
main.py - Ingestion and Chat Loop
Orchestrates auth checks, ingestion, Gemini client initialization, and the interactive chat loop.
import os
import google.genai as genai
from fetcher import check_auth, fetch_document_list, download_document
from vector_store import VectorStore
def setup_gemini():
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
print("Please enter your GEMINI_API_KEY:")
api_key = input("Key: ").strip()
return genai.Client(api_key=api_key)
def ingest_documents(store, limit=10):
files = fetch_document_list(limit)
for file in files:
file_id = file.get("id")
if store.document_exists(file_id):
print(f"Skipping {file.get('name')}...")
continue
content = download_document(file_id, file.get("mimeType"))
if content:
store.add_document(file_id, file.get("name"), content)
print(f"Added {file.get('name')}")
def chat_loop(model, store):
while True:
query = input("
You: ")
results = store.query(query, n_results=3)
documents = results.get("documents", [[]])[0]
metadatas = results.get("metadatas", [[]])[0]
context_parts = []
for doc, meta in zip(documents, metadatas):
source = meta.get("source", "Unknown")
context_parts.append(f"Source: {source}
Text:
{doc}
")
full_context = "
---
".join(context_parts)
prompt = f"""Answer based ONLY on the following context. If you cannot answer, say "I don't know."
Context:
{full_context}
Question: {query}"""
model_name = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
response = model.models.generate_content_stream(model=model_name, contents=prompt)
for chunk in response:
if chunk.text:
print(chunk.text, end="", flush=True)
print()
if __name__ == "__main__":
if not check_auth():
print("Auth error. Run: gws auth setup --project YOUR_PROJECT_ID --login")
exit(1)
store = VectorStore()
model = setup_gemini()
ingest_documents(store)
chat_loop(model, store)
Step 9: Running the App
Start the app and chat with your Drive files.
python main.py
On first run, the app loads the embedding model, fetches Drive files, embeds them, and drops you into the chat loop. Subsequent runs load the persisted index from disk and skip already-ingested files.
rm -rf ./chroma_db
python main.py
Conclusion
The RAG assistant we built is deliberately minimal: gws fetches, Sentence Transformers embeds locally, ChromaDB stores, and Gemini streams the answer. Four files, zero cloud embedding costs, and incremental ingestion on every run.
The architecture scales cleanly if you want to add Gmail and Calendar as sources, swap in a larger Gemini model, or replace the chat loop with a LangGraph agent. The foundation stays the same.
Frequently Asked Questions
Do I need a Rust toolchain to install gws?
No. The CLI ships as an npm package with pre-built binaries. npm install -g @googleworkspace/cli is the entire process.
Why do I need a GCP project?
OAuth for developer access to Google APIs requires a GCP project to hold OAuth credentials and track API quota.
How do I add Gmail as a document source?
Run gws gmail messages list to get structured JSON. Add new fetch functions in fetcher.py and call them from ingest_documents() in main.py.
Can I use this with a Google Workspace organization account?
Yes, but your Workspace admin may need to approve the OAuth app. Check with your admin about allowlisting the OAuth client.
Why am I getting a 403 serviceusage.services.use error?
Your OAuth credentials point to a different GCP project than the one with billing. Re-run gws auth setup --project YOUR_PROJECT_ID.
Need Help Building RAG Assistants?
Our AI experts can help you build RAG pipelines, integrate with Google Workspace, and optimize your embeddings for production.
