# go-easy 🟢

> 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)

## Installation

```bash
# 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 primary
```

Requires **Node.js ≥ 20**.

## Auth Setup

go-easy manages its own OAuth2 tokens in `~/.go-easy/`.

### Prerequisites

1. Create a project in [Google Cloud Console](https://console.cloud.google.com/)
2. Enable the Gmail, Drive, and Calendar APIs
3. Create OAuth2 credentials (Desktop application type)
4. Save credentials to `~/.go-easy/credentials.json`:

```json
{
  "clientId": "YOUR_CLIENT_ID.apps.googleusercontent.com",
  "clientSecret": "YOUR_CLIENT_SECRET"
}
```

### Add an account

```bash
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.

### Manage accounts

```bash
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 account
```

## Quick Start

```ts
import { 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'],
});
```

## Gateway CLIs

All gateway CLIs output JSON to stdout and work via `npx`:

```bash
# 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.

## Services

| 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 |

### Gmail

| 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` | ⚠️ DESTRUCTIVE | Send a new email (supports `markdown` option) |
| `reply` | ⚠️ DESTRUCTIVE | Reply / reply-all to a message |
| `forward` | WRITE / ⚠️ DESTRUCTIVE | Forward as draft (default) or send (`sendNow`). Attachment filtering. |
| `sendDraft` | ⚠️ DESTRUCTIVE | Send an existing draft |

### Drive

| 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` | ⚠️ DESTRUCTIVE | Trash a file |
| `shareFile` | ⚠️ DESTRUCTIVE* | Share a file (*public sharing only; user/group is WRITE) |
| `unshareFile` | ⚠️ DESTRUCTIVE | Remove a sharing permission |

### Calendar

| 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` | ⚠️ DESTRUCTIVE | Delete an event (warns about attendee cancellation) |

## Safety Model

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.

## Module Structure

go-easy uses **subpath exports** — import only what you need:

```ts
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 |

## Development

```bash
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 mode
```

## License

MIT
