Google APIs made easy — Gmail, Drive & Calendar. For AI agents and humans.
Thin TypeScript wrappers over Google's individual @googleapis/* packages with:
- Own auth — unified OAuth2 with combined tokens, agent-compatible two-phase flow
- Agent-friendly types — structured
GmailMessage,DriveFile,CalendarEvent - Safety guards — destructive operations (send, share, delete) require explicit confirmation
- JSON gateways — CLI tools that always output structured JSON
- File-based body — email bodies read from files, not CLI args (no shell escaping issues)
# As a library
npm install @marcfargas/go-easy
# As CLI tools (no install needed)
npx go-gmail you@example.com search "is:unread"
npx go-drive you@example.com ls
npx go-calendar you@example.com events primaryRequires Node.js ≥ 20.
go-easy manages its own OAuth2 tokens in ~/.go-easy/.
- Create a project in Google Cloud Console
- Enable the Gmail, Drive, and Calendar APIs
- Create OAuth2 credentials (Desktop application type)
- Save credentials to
~/.go-easy/credentials.json:
{
"clientId": "YOUR_CLIENT_ID.apps.googleusercontent.com",
"clientSecret": "YOUR_CLIENT_SECRET"
}npx go-easy auth add you@example.com
# → { "status": "started", "authUrl": "https://accounts.google.com/..." }
# Open the URL, authorize, then poll:
npx go-easy auth add you@example.com
# → { "status": "complete", "email": "you@example.com", "scopes": ["gmail", "drive", "calendar"] }One combined token covers Gmail + Drive + Calendar. The flow is agent-compatible — two separate CLI calls (start + poll), no streaming stdout needed.
npx go-easy auth list # List configured accounts
npx go-easy auth add you@example.com # Add or upgrade account
npx go-easy auth remove you@example.com --confirm # Remove accountimport { getAuth } from '@marcfargas/go-easy/auth';
import { search, send } from '@marcfargas/go-easy/gmail';
import { setSafetyContext } from '@marcfargas/go-easy';
const auth = await getAuth('gmail', 'you@example.com');
// Search (READ — no safety gate)
const results = await search(auth, { query: 'is:unread from:client' });
console.log(results.items);
// Send (DESTRUCTIVE — requires safety context)
setSafetyContext({
confirm: async (op) => {
console.log(`⚠️ ${op.description}`);
return true; // or prompt the user
},
});
await send(auth, {
to: 'client@example.com',
subject: 'Invoice attached',
markdown: '# Invoice\n\nPlease find attached.',
attachments: ['./invoice.pdf'],
});All gateway CLIs output JSON to stdout and work via npx:
# Gmail
npx go-gmail you@example.com search "is:unread" --max=10
npx go-gmail you@example.com get <messageId>
npx go-gmail you@example.com reply <messageId> --body-text-file=reply.txt --confirm
npx go-gmail you@example.com send --to=x@y.com --subject="Hi" --body-text-file=body.txt --confirm
# Drive
npx go-drive you@example.com ls
npx go-drive you@example.com search "quarterly report"
npx go-drive you@example.com upload ./file.pdf --folder=<folderId>
# Calendar
npx go-calendar you@example.com events primary --from=2026-02-01T00:00:00Z
npx go-calendar you@example.com create primary --summary="Meeting" --start=... --end=...
npx go-calendar you@example.com freebusy primary --from=... --to=...Body content is always read from files (--body-text-file, --body-html-file, --body-md-file), never passed inline.
Destructive operations require --confirm. Without it, they show what would happen and exit with code 2.
| Service | Module | Gateway | Status |
|---|---|---|---|
| Gmail | @marcfargas/go-easy/gmail |
npx go-gmail |
✅ Ready |
| Drive | @marcfargas/go-easy/drive |
npx go-drive |
✅ Ready |
| Calendar | @marcfargas/go-easy/calendar |
npx go-calendar |
✅ Ready |
| Function | Safety | Description |
|---|---|---|
search |
READ | Search messages by Gmail query |
getMessage |
READ | Get a single message with parsed fields |
getThread |
READ | Get a full conversation thread |
listLabels |
READ | List all labels |
getAttachmentContent |
READ | Download an attachment as Buffer |
getProfile |
READ | Get the authenticated email address |
createDraft |
WRITE | Create a draft (no send) |
listDrafts |
READ | List existing drafts |
batchModifyLabels |
WRITE | Add/remove labels on multiple messages |
markdownToHtml |
— | Convert Markdown to email-safe HTML |
send |
Send a new email (supports markdown option) |
|
reply |
Reply / reply-all to a message | |
forward |
WRITE / |
Forward as draft (default) or send (sendNow). Attachment filtering. |
sendDraft |
Send an existing draft |
| Function | Safety | Description |
|---|---|---|
listFiles |
READ | List folder contents or query by metadata |
searchFiles |
READ | Full-text search inside file contents |
getFile |
READ | Get file metadata by ID |
downloadFile |
READ | Download binary files as Buffer |
exportFile |
READ | Export Workspace files (Docs → pdf/docx, Sheets → xlsx/csv, etc.) |
listPermissions |
READ | List sharing permissions on a file |
uploadFile |
WRITE | Upload a local file |
createFolder |
WRITE | Create a folder |
moveFile |
WRITE | Move a file to a different folder |
renameFile |
WRITE | Rename a file |
copyFile |
WRITE | Copy a file |
trashFile |
Trash a file | |
shareFile |
Share a file (*public sharing only; user/group is WRITE) | |
unshareFile |
Remove a sharing permission |
| Function | Safety | Description |
|---|---|---|
listCalendars |
READ | List all calendars for the account |
listEvents |
READ | List events with time range, search, pagination |
getEvent |
READ | Get a single event by ID |
queryFreeBusy |
READ | Check availability across calendars |
createEvent |
WRITE | Create an event (with attendees, all-day, location, OOO, focus time) |
updateEvent |
WRITE | Update an existing event (full replace) |
deleteEvent |
Delete an event (warns about attendee cancellation) |
Operations are classified into three levels:
| Level | Gate | Examples |
|---|---|---|
| READ | None | search, getMessage, listLabels |
| WRITE | Logged | createDraft, batchModifyLabels, upload |
| DESTRUCTIVE | Blocked unless confirmed | send, reply, forward, share, delete |
Set up a SafetyContext at startup to handle confirmation prompts.
Without one, all destructive operations are blocked by default.
go-easy uses subpath exports — import only what you need:
import { getAuth } from '@marcfargas/go-easy/auth';
import { search, send } from '@marcfargas/go-easy/gmail';
import { listFiles, uploadFile } from '@marcfargas/go-easy/drive';
import { listEvents, createEvent } from '@marcfargas/go-easy/calendar';
import { setSafetyContext } from '@marcfargas/go-easy';| Import path | What's in it |
|---|---|
@marcfargas/go-easy |
Safety context, errors, plus gmail/drive/calendar as namespaces |
@marcfargas/go-easy/auth |
getAuth, listAccounts, listAllAccounts, clearAuthCache |
@marcfargas/go-easy/auth-store |
readAccountStore, writeAccountStore, findAccount, etc. |
@marcfargas/go-easy/scopes |
SCOPES, ALL_SCOPES, scopeToService |
@marcfargas/go-easy/gmail |
All Gmail operations |
@marcfargas/go-easy/drive |
All Drive operations |
@marcfargas/go-easy/calendar |
All Calendar operations |
npm install # install deps
npm run build # compile TypeScript
npm test # run tests (vitest)
npm run lint # type-check without emitting
npm run dev # watch modeMIT