refactor: restructure add-gmail skill for new skill architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Fritzzzz
2026-02-24 22:29:34 +02:00
parent 1c31726c63
commit 41e2424856
14 changed files with 2714 additions and 611 deletions

View File

@@ -1,111 +1,142 @@
---
name: add-gmail
description: Add Gmail integration to NanoClaw. Can be configured as a tool (agent reads/sends emails when triggered from WhatsApp) or as a full channel (emails can trigger the agent, schedule tasks, and receive replies). Guides through GCP OAuth setup and implements the integration.
---
# Add Gmail Integration
This skill adds Gmail capabilities to NanoClaw. It can be configured in two modes:
This skill adds Gmail support to NanoClaw — either as a tool (read, send, search, draft) or as a full channel that polls the inbox.
1. **Tool Mode** - Agent can read/send emails, but only when triggered from WhatsApp
2. **Channel Mode** - Emails can trigger the agent, schedule tasks, and receive email replies
## Phase 1: Pre-flight
## Initial Questions
### Check if already applied
Use `AskUserQuestion` to determine the configuration:
Read `.nanoclaw/state.yaml`. If `gmail` is in `applied_skills`, skip to Phase 3 (Setup). The code changes are already in place.
AskUserQuestion: How do you want to use Gmail with NanoClaw?
- **Tool Mode** - Agent can read/send emails when triggered from WhatsApp (simpler setup)
- **Channel Mode** - Emails can trigger the agent, schedule tasks, and receive email replies (requires polling)
### Ask the user
Store their choice and proceed to the appropriate section.
Use `AskUserQuestion`:
---
AskUserQuestion: Should incoming emails be able to trigger the agent?
## Prerequisites (Both Modes)
- **Yes** — Full channel mode: the agent listens on Gmail and responds to incoming emails automatically
- **No** — Tool-only: the agent gets full Gmail tools (read, send, search, draft) but won't monitor the inbox. No channel code is added.
### 1. Check Existing Gmail Setup
## Phase 2: Apply Code Changes
First, check if Gmail is already configured:
### Initialize skills system (if needed)
If `.nanoclaw/` directory doesn't exist yet:
```bash
npx tsx scripts/apply-skill.ts --init
```
### Path A: Tool-only (user chose "No")
Do NOT run the full apply script. Only two source files need changes. This avoids adding dead code (`gmail.ts`, `gmail.test.ts`, config flags, index.ts channel logic, routing tests, `googleapis` dependency).
#### 1. Mount Gmail credentials in container
Apply the changes described in `modify/src/container-runner.ts.intent.md` to `src/container-runner.ts`: import `os`, add a conditional read-write mount of `~/.gmail-mcp` to `/home/node/.gmail-mcp` in `buildVolumeMounts()` after the session mounts.
#### 2. Add Gmail MCP server to agent runner
Apply the changes described in `modify/container/agent-runner/src/index.ts.intent.md` to `container/agent-runner/src/index.ts`: add `gmail` MCP server (`npx -y @gongrzhe/server-gmail-autoauth-mcp`) and `'mcp__gmail__*'` to `allowedTools`.
#### 3. Record in state
Add `gmail` to `.nanoclaw/state.yaml` under `applied_skills` with `mode: tool-only`.
#### 4. Validate
```bash
npm run build
```
Build must be clean before proceeding. Skip to Phase 3.
### Path B: Channel mode (user chose "Yes")
Run the full skills engine to apply all code changes:
```bash
npx tsx scripts/apply-skill.ts .claude/skills/add-gmail
```
This deterministically:
- Adds `src/channels/gmail.ts` (GmailChannel class implementing Channel interface)
- Adds `src/channels/gmail.test.ts` (unit tests)
- Three-way merges Gmail support into `src/index.ts` (conditional GmailChannel creation)
- Three-way merges Gmail config into `src/config.ts` (`GMAIL_CHANNEL_ENABLED`)
- Three-way merges Gmail credentials mount into `src/container-runner.ts` (~/.gmail-mcp -> /home/node/.gmail-mcp)
- Three-way merges Gmail MCP server into `container/agent-runner/src/index.ts` (@gongrzhe/server-gmail-autoauth-mcp)
- Three-way merges Gmail JID tests into `src/routing.test.ts`
- Installs the `googleapis` npm dependency
- Updates `.env.example` with `GMAIL_CHANNEL_ENABLED`
- Records the application in `.nanoclaw/state.yaml`
If the apply reports merge conflicts, read the intent files:
- `modify/src/index.ts.intent.md` — what changed and invariants for index.ts
- `modify/src/config.ts.intent.md` — what changed for config.ts
- `modify/src/container-runner.ts.intent.md` — what changed for container-runner.ts
- `modify/container/agent-runner/src/index.ts.intent.md` — what changed for agent-runner
#### Add email handling instructions
Append the following to `groups/main/CLAUDE.md` (before the formatting section):
```markdown
## Email Notifications
When you receive an email notification (messages starting with `[Email from ...`), inform the user about it but do NOT reply to the email unless specifically asked. You have Gmail tools available — use them only when the user explicitly asks you to reply, forward, or take action on an email.
```
#### Validate
```bash
npm test
npm run build
```
All tests must pass (including the new gmail tests) and build must be clean before proceeding.
## Phase 3: Setup
### Check existing Gmail credentials
```bash
ls -la ~/.gmail-mcp/ 2>/dev/null || echo "No Gmail config found"
```
If `credentials.json` exists, skip to "Verify Gmail Access" below.
If `credentials.json` already exists, skip to "Configure environment" below.
### 2. Create Gmail Config Directory
```bash
mkdir -p ~/.gmail-mcp
```
### 3. GCP Project Setup
**USER ACTION REQUIRED**
### GCP Project Setup
Tell the user:
> I need you to set up Google Cloud OAuth credentials. I'll walk you through it:
> I need you to set up Google Cloud OAuth credentials:
>
> 1. Open https://console.cloud.google.com in your browser
> 2. Create a new project (or select existing) - click the project dropdown at the top
Wait for user confirmation, then continue:
> 3. Now enable the Gmail API:
> - In the left sidebar, go to **APIs & Services → Library**
> - Search for "Gmail API"
> - Click on it, then click **Enable**
Wait for user confirmation, then continue:
> 4. Now create OAuth credentials:
> - Go to **APIs & Services → Credentials** (in the left sidebar)
> - Click **+ CREATE CREDENTIALS** at the top
> - Select **OAuth client ID**
> - If prompted for consent screen, choose "External", fill in app name (e.g., "NanoClaw"), your email, and save
> - For Application type, select **Desktop app**
> - Name it anything (e.g., "NanoClaw Gmail")
> - Click **Create**
Wait for user confirmation, then continue:
> 5. Download the credentials:
> - Click **DOWNLOAD JSON** on the popup (or find it in the credentials list and click the download icon)
> - Save it as `gcp-oauth.keys.json`
> 1. Open https://console.cloud.google.com — create a new project or select existing
> 2. Go to **APIs & Services > Library**, search "Gmail API", click **Enable**
> 3. Go to **APIs & Services > Credentials**, click **+ CREATE CREDENTIALS > OAuth client ID**
> - If prompted for consent screen: choose "External", fill in app name and email, save
> - Application type: **Desktop app**, name: anything (e.g., "NanoClaw Gmail")
> 4. Click **DOWNLOAD JSON** and save as `gcp-oauth.keys.json`
>
> Where did you save the file? (Give me the full path, or just paste the file contents here)
> Where did you save the file? (Give me the full path, or paste the file contents here)
If user provides a path, copy it:
```bash
mkdir -p ~/.gmail-mcp
cp "/path/user/provided/gcp-oauth.keys.json" ~/.gmail-mcp/gcp-oauth.keys.json
```
If user pastes the JSON content, write it directly:
If user pastes JSON content, write it to `~/.gmail-mcp/gcp-oauth.keys.json`.
```bash
cat > ~/.gmail-mcp/gcp-oauth.keys.json << 'EOF'
{paste the JSON here}
EOF
```
Verify the file is valid JSON:
```bash
cat ~/.gmail-mcp/gcp-oauth.keys.json | head -5
```
### 4. OAuth Authorization
**USER ACTION REQUIRED**
### OAuth Authorization
Tell the user:
> I'm going to run the Gmail authorization. A browser window will open asking you to sign in to Google and grant access.
>
> **Important:** If you see a warning that the app isn't verified, click "Advanced" then "Go to [app name] (unsafe)" - this is normal for personal OAuth apps.
> I'm going to run Gmail authorization. A browser window will open — sign in and grant access. If you see an "app isn't verified" warning, click "Advanced" then "Go to [app name] (unsafe)" — this is normal for personal OAuth apps.
Run the authorization:
@@ -113,591 +144,116 @@ Run the authorization:
npx -y @gongrzhe/server-gmail-autoauth-mcp auth
```
If that doesn't work (some versions don't have an auth subcommand), run it and let it prompt:
If that fails (some versions don't have an auth subcommand), try `timeout 60 npx -y @gongrzhe/server-gmail-autoauth-mcp || true`. Verify with `ls ~/.gmail-mcp/credentials.json`.
### Configure environment
**Channel mode only** — add to `.env`:
```
GMAIL_CHANNEL_ENABLED=true
```
Sync to container environment:
```bash
timeout 60 npx -y @gongrzhe/server-gmail-autoauth-mcp || true
mkdir -p data/env && cp .env data/env/env
```
Tell user:
> Complete the authorization in your browser. The window should close automatically when done. Let me know when you've authorized.
### Build and restart
### 5. Verify Gmail Access
Check that credentials were saved:
Clear stale per-group agent-runner copies (they only get re-created if missing, so existing copies won't pick up the new Gmail server):
```bash
if [ -f ~/.gmail-mcp/credentials.json ]; then
echo "Gmail authorization successful!"
ls -la ~/.gmail-mcp/
else
echo "ERROR: credentials.json not found - authorization may have failed"
fi
rm -r data/sessions/*/agent-runner-src 2>/dev/null || true
```
Test the connection by listing labels (quick sanity check):
```bash
echo '{"method": "tools/list"}' | timeout 10 npx -y @gongrzhe/server-gmail-autoauth-mcp 2>/dev/null | head -20 || echo "MCP responded (check output above)"
```
If everything works, proceed to implementation.
---
## Tool Mode Implementation
For Tool Mode, integrate Gmail MCP into the agent runner. Execute these changes directly.
### Step 1: Add Gmail MCP to Agent Runner
Read `container/agent-runner/src/index.ts` and find the `mcpServers` config in the `query()` call.
Add `gmail` to the `mcpServers` object:
```typescript
gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] }
```
Find the `allowedTools` array and add Gmail tools:
```typescript
'mcp__gmail__*'
```
The result should look like:
```typescript
mcpServers: {
nanoclaw: ipcMcp,
gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] }
},
allowedTools: [
'Bash',
'Read', 'Write', 'Edit', 'Glob', 'Grep',
'WebSearch', 'WebFetch',
'mcp__nanoclaw__*',
'mcp__gmail__*'
],
```
### Step 2: Mount Gmail Credentials in Container
Read `src/container-runner.ts` and find the `buildVolumeMounts` function.
Add this mount block (after the `.claude` mount is a good location):
```typescript
// Gmail credentials directory
const gmailDir = path.join(homeDir, '.gmail-mcp');
if (fs.existsSync(gmailDir)) {
mounts.push({
hostPath: gmailDir,
containerPath: '/home/node/.gmail-mcp',
readonly: false // MCP may need to refresh tokens
});
}
```
### Step 3: Update Group Memory
Append to `groups/CLAUDE.md` (the global memory file):
```markdown
## Email (Gmail)
You have access to Gmail via MCP tools:
- `mcp__gmail__search_emails` - Search emails with query
- `mcp__gmail__get_email` - Get full email content by ID
- `mcp__gmail__send_email` - Send an email
- `mcp__gmail__draft_email` - Create a draft
- `mcp__gmail__list_labels` - List available labels
Example: "Check my unread emails from today" or "Send an email to john@example.com about the meeting"
```
Also append the same section to `groups/main/CLAUDE.md`.
### Step 4: Rebuild and Restart
Run these commands:
Rebuild the container (agent-runner changed):
```bash
cd container && ./build.sh
```
Wait for container build to complete, then:
```bash
cd .. && npm run build
```
Wait for TypeScript compilation, then restart the service:
Then compile and restart:
```bash
npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS
# Linux: systemctl --user restart nanoclaw
```
Check that it started:
## Phase 4: Verify
```bash
sleep 2 && launchctl list | grep nanoclaw # macOS
# Linux: systemctl --user status nanoclaw
```
### Step 5: Test Gmail Integration
### Test tool access (both modes)
Tell the user:
> Gmail integration is set up! Test it by sending this message in your WhatsApp main channel:
> Gmail is connected! Send this in your WhatsApp main channel:
>
> `@Andy check my recent emails`
>
> Or:
>
> `@Andy list my Gmail labels`
> `@Andy check my recent emails` or `@Andy list my Gmail labels`
Watch the logs for any errors:
### Test channel mode (Channel mode only)
Tell the user to send themselves a test email. The agent should pick it up within a minute. Monitor: `tail -f logs/nanoclaw.log | grep -iE "(gmail|email)"`.
Once verified, offer filter customization via `AskUserQuestion` — by default, only emails in the Primary inbox trigger the agent (Promotions, Social, Updates, and Forums are excluded). The user can keep this default or narrow further by sender, label, or keywords. No code changes needed for filters.
### Check logs if needed
```bash
tail -f logs/nanoclaw.log
```
---
## Channel Mode Implementation
Channel Mode includes everything from Tool Mode, plus email polling and routing.
### Additional Questions for Channel Mode
Use `AskUserQuestion` to configure email triggering:
AskUserQuestion: How should the agent be triggered from email?
- **Specific Label** - Create a Gmail label (e.g., "NanoClaw"), emails with this label trigger the agent
- **Email Address Pattern** - Emails to a specific address pattern (e.g., andy+task@gmail.com) via plus-addressing
- **Subject Prefix** - Emails with a subject starting with a keyword (e.g., "[Andy]")
AskUserQuestion: How should email conversations be grouped?
- **Per Email Thread** - Each email thread gets its own conversation context
- **Per Sender** - All emails from the same sender share context
- **Single Context** - All emails share the main group context
Store their choices for implementation.
### Step 1: Complete Tool Mode First
Complete all Tool Mode steps above before continuing. Verify Gmail tools work by having the user test `@Andy check my recent emails`.
### Step 2: Add Email Polling Configuration
Read `src/types.ts` and add this interface:
```typescript
export interface EmailChannelConfig {
enabled: boolean;
triggerMode: 'label' | 'address' | 'subject';
triggerValue: string; // Label name, address pattern, or subject prefix
contextMode: 'thread' | 'sender' | 'single';
pollIntervalMs: number;
replyPrefix?: string; // Optional prefix for replies
}
```
Read `src/config.ts` and add this configuration (customize values based on user's earlier answers):
```typescript
export const EMAIL_CHANNEL: EmailChannelConfig = {
enabled: true,
triggerMode: 'label', // or 'address' or 'subject'
triggerValue: 'NanoClaw', // the label name, address pattern, or prefix
contextMode: 'thread',
pollIntervalMs: 60000, // Check every minute
replyPrefix: '[Andy] '
};
```
### Step 3: Add Email State Tracking
Read `src/db.ts` and add these functions for tracking processed emails:
```typescript
// Track processed emails to avoid duplicates
export function initEmailTable(): void {
db.exec(`
CREATE TABLE IF NOT EXISTS processed_emails (
message_id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL,
sender TEXT NOT NULL,
subject TEXT,
processed_at TEXT NOT NULL,
response_sent INTEGER DEFAULT 0
)
`);
}
export function isEmailProcessed(messageId: string): boolean {
const row = db.prepare('SELECT 1 FROM processed_emails WHERE message_id = ?').get(messageId);
return !!row;
}
export function markEmailProcessed(messageId: string, threadId: string, sender: string, subject: string): void {
db.prepare(`
INSERT OR REPLACE INTO processed_emails (message_id, thread_id, sender, subject, processed_at)
VALUES (?, ?, ?, ?, ?)
`).run(messageId, threadId, sender, subject, new Date().toISOString());
}
export function markEmailResponded(messageId: string): void {
db.prepare('UPDATE processed_emails SET response_sent = 1 WHERE message_id = ?').run(messageId);
}
```
Also find the `initDatabase()` function in `src/db.ts` and add a call to `initEmailTable()`.
### Step 4: Create Email Channel Module
Create a new file `src/email-channel.ts` with this content:
```typescript
import { EMAIL_CHANNEL } from './config.js';
import { isEmailProcessed, markEmailProcessed, markEmailResponded } from './db.js';
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: { target: 'pino-pretty', options: { colorize: true } }
});
interface EmailMessage {
id: string;
threadId: string;
from: string;
subject: string;
body: string;
date: string;
}
// Gmail MCP client functions (call via subprocess or import the MCP directly)
// These would invoke the Gmail MCP tools
export async function checkForNewEmails(): Promise<EmailMessage[]> {
// Build query based on trigger mode
let query: string;
switch (EMAIL_CHANNEL.triggerMode) {
case 'label':
query = `label:${EMAIL_CHANNEL.triggerValue} is:unread`;
break;
case 'address':
query = `to:${EMAIL_CHANNEL.triggerValue} is:unread`;
break;
case 'subject':
query = `subject:${EMAIL_CHANNEL.triggerValue} is:unread`;
break;
}
// This requires calling Gmail MCP's search_emails tool
// Implementation depends on how you want to invoke MCP from Node
// Option 1: Use @anthropic-ai/claude-agent-sdk with just gmail MCP
// Option 2: Run npx gmail MCP as subprocess and parse output
// Option 3: Import gmail-autoauth-mcp directly
// Placeholder - implement based on preference
return [];
}
export async function sendEmailReply(
threadId: string,
to: string,
subject: string,
body: string
): Promise<void> {
// Call Gmail MCP's send_email tool with in_reply_to for threading
// Prefix subject with replyPrefix if configured
const replySubject = subject.startsWith('Re:')
? subject
: `Re: ${subject}`;
const prefixedBody = EMAIL_CHANNEL.replyPrefix
? `${EMAIL_CHANNEL.replyPrefix}${body}`
: body;
// Implementation: invoke Gmail MCP send_email
}
export function getContextKey(email: EmailMessage): string {
switch (EMAIL_CHANNEL.contextMode) {
case 'thread':
return `email-thread-${email.threadId}`;
case 'sender':
return `email-sender-${email.from.toLowerCase()}`;
case 'single':
return 'email-main';
}
}
```
### Step 5: Add Email Polling to Main Loop
Read `src/index.ts` and add the email polling infrastructure. First, add these imports at the top:
```typescript
import { checkForNewEmails, sendEmailReply, getContextKey } from './email-channel.js';
import { EMAIL_CHANNEL } from './config.js';
import { isEmailProcessed, markEmailProcessed, markEmailResponded } from './db.js';
```
Then add the `startEmailLoop` function:
```typescript
async function startEmailLoop(): Promise<void> {
if (!EMAIL_CHANNEL.enabled) {
logger.info('Email channel disabled');
return;
}
logger.info(`Email channel running (trigger: ${EMAIL_CHANNEL.triggerMode}:${EMAIL_CHANNEL.triggerValue})`);
while (true) {
try {
const emails = await checkForNewEmails();
for (const email of emails) {
if (isEmailProcessed(email.id)) continue;
logger.info({ from: email.from, subject: email.subject }, 'Processing email');
markEmailProcessed(email.id, email.threadId, email.from, email.subject);
// Determine which group/context to use
const contextKey = getContextKey(email);
// Build prompt with email content
const prompt = `<email>
<from>${email.from}</from>
<subject>${email.subject}</subject>
<body>${email.body}</body>
</email>
Respond to this email. Your response will be sent as an email reply.`;
// Run agent with email context
// You'll need to create a registered group for email or use a special handler
const response = await runEmailAgent(contextKey, prompt, email);
if (response) {
await sendEmailReply(email.threadId, email.from, email.subject, response);
markEmailResponded(email.id);
logger.info({ to: email.from }, 'Email reply sent');
}
}
} catch (err) {
logger.error({ err }, 'Error in email loop');
}
await new Promise(resolve => setTimeout(resolve, EMAIL_CHANNEL.pollIntervalMs));
}
}
```
Then add `startEmailLoop()` in the `main()` function, after `startMessageLoop()`:
```typescript
// In main(), after startMessageLoop():
startEmailLoop();
```
### Step 6: Implement Email Agent Runner
Add this function to `src/index.ts` (or create a separate `src/email-agent.ts` if preferred):
```typescript
async function runEmailAgent(
contextKey: string,
prompt: string,
email: EmailMessage
): Promise<string | null> {
// Email uses either:
// 1. A dedicated "email" group folder
// 2. Or dynamic folders per thread/sender
const groupFolder = EMAIL_CHANNEL.contextMode === 'single'
? 'main' // Use main group context
: `email/${contextKey}`; // Isolated email context
// Ensure folder exists
const groupDir = path.join(GROUPS_DIR, groupFolder);
fs.mkdirSync(groupDir, { recursive: true });
// Create minimal registered group for email
const emailGroup: RegisteredGroup = {
name: contextKey,
folder: groupFolder,
trigger: '', // No trigger for email
added_at: new Date().toISOString()
};
// Use existing runContainerAgent
const output = await runContainerAgent(emailGroup, {
prompt,
sessionId: sessions[groupFolder],
groupFolder,
chatJid: `email:${email.from}`, // Use email: prefix for JID
isMain: false,
isScheduledTask: false
});
if (output.newSessionId) {
sessions[groupFolder] = output.newSessionId;
setSession(groupFolder, output.newSessionId);
}
return output.status === 'success' ? output.result : null;
}
```
### Step 7: Update IPC for Email Responses (Optional)
If you want the agent to be able to send emails proactively from within a session, read `container/agent-runner/src/ipc-mcp.ts` and add this tool:
```typescript
// Add to the MCP tools
{
name: 'send_email_reply',
description: 'Send an email reply in the current thread',
inputSchema: {
type: 'object',
properties: {
body: { type: 'string', description: 'Email body content' }
},
required: ['body']
}
}
```
Then add handling in `src/ipc.ts` in the `processTaskIpc` function or create a new IPC handler for email actions.
### Step 8: Create Email Group Memory
Create the email group directory and memory file:
```bash
mkdir -p groups/email
```
Write `groups/email/CLAUDE.md`:
```markdown
# Email Channel
You are responding to emails. Your responses will be sent as email replies.
## Guidelines
- Be professional and clear
- Keep responses concise but complete
- Use proper email formatting (greetings, sign-off)
- If the email requires action you can't take, explain what the user should do
## Context
Each email thread or sender (depending on configuration) has its own conversation history.
```
### Step 9: Rebuild and Test
Rebuild the container (required since agent-runner changed):
```bash
cd container && ./build.sh
```
Wait for build to complete, then compile TypeScript:
```bash
cd .. && npm run build
```
Restart the service:
```bash
launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS
# Linux: systemctl --user restart nanoclaw
```
Verify it started and check for email channel startup message:
```bash
sleep 3 && tail -20 logs/nanoclaw.log | grep -i email
```
Tell the user:
> Email channel is now active! Test it by sending an email that matches your trigger:
> - **Label mode:** Apply the "${triggerValue}" label to any email
> - **Address mode:** Send an email to ${triggerValue}
> - **Subject mode:** Send an email with subject starting with "${triggerValue}"
>
> The agent should process it within a minute and send a reply.
Monitor for the test:
```bash
tail -f logs/nanoclaw.log | grep -E "(email|Email)"
```
---
## Troubleshooting
### Gmail MCP not responding
### Gmail connection not responding
Test directly:
```bash
# Test Gmail MCP directly
npx -y @gongrzhe/server-gmail-autoauth-mcp
```
### OAuth token expired
Re-authorize:
```bash
# Re-authorize
rm ~/.gmail-mcp/credentials.json
npx -y @gongrzhe/server-gmail-autoauth-mcp
```
### Emails not being detected
- Check the trigger configuration matches your test email
- Verify the label exists (for label mode)
- Check `processed_emails` table for already-processed emails
### Container can't access Gmail
- Verify `~/.gmail-mcp` is mounted in container
- Verify `~/.gmail-mcp` is mounted: check `src/container-runner.ts` for the `.gmail-mcp` mount
- Check container logs: `cat groups/main/logs/container-*.log | tail -50`
---
### Emails not being detected (Channel Mode only)
## Removing Gmail Integration
- Check `GMAIL_CHANNEL_ENABLED=true` in `.env` AND `data/env/env`
- By default, the channel polls unread Primary inbox emails (`is:unread category:primary`)
- Check logs for Gmail polling errors
To remove Gmail entirely:
## Removal
1. Remove from `container/agent-runner/src/index.ts`:
- Delete `gmail` from `mcpServers`
- Remove `mcp__gmail__*` from `allowedTools`
### Tool-only mode
2. Remove from `src/container-runner.ts`:
- Delete the `~/.gmail-mcp` mount block
1. Remove `~/.gmail-mcp` mount from `src/container-runner.ts`
2. Remove `gmail` MCP server and `mcp__gmail__*` from `container/agent-runner/src/index.ts`
3. Remove `gmail` from `.nanoclaw/state.yaml`
4. Clear stale agent-runner copies: `rm -r data/sessions/*/agent-runner-src 2>/dev/null || true`
5. Rebuild: `cd container && ./build.sh && cd .. && npm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `systemctl --user restart nanoclaw` (Linux)
3. Remove from `src/index.ts` (Channel Mode only):
- Delete `startEmailLoop()` call
- Delete email-related imports
### Channel mode
4. Delete `src/email-channel.ts` (if created)
5. Remove Gmail sections from `groups/*/CLAUDE.md`
6. Rebuild:
```bash
cd container && ./build.sh && cd ..
npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS
# Linux: systemctl --user restart nanoclaw
```
1. Delete `src/channels/gmail.ts` and `src/channels/gmail.test.ts`
2. Remove `GmailChannel` import and creation from `src/index.ts`
3. Remove Gmail config (`GMAIL_CHANNEL_ENABLED`) from `src/config.ts`
4. Remove `~/.gmail-mcp` mount from `src/container-runner.ts`
5. Remove `gmail` MCP server and `mcp__gmail__*` from `container/agent-runner/src/index.ts`
6. Remove Gmail JID tests from `src/routing.test.ts`
7. Uninstall: `npm uninstall googleapis`
8. Remove env var from `.env` and `data/env/env`
9. Remove `gmail` from `.nanoclaw/state.yaml`
10. Clear stale agent-runner copies: `rm -r data/sessions/*/agent-runner-src 2>/dev/null || true`
11. Rebuild: `cd container && ./build.sh && cd .. && npm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `systemctl --user restart nanoclaw` (Linux)