* feat: add Telegram channel with agent swarm support Add Telegram as a messaging channel that can run alongside WhatsApp or standalone (TELEGRAM_ONLY mode). Includes bot pool support for agent swarms where each subagent appears as a different bot identity in the group. - Add grammy dependency for Telegram Bot API - Route messages through tg: JID prefix convention - Add storeMessageDirect for non-Baileys channels - Add sender field to IPC send_message for swarm identity - Support TELEGRAM_BOT_TOKEN, TELEGRAM_ONLY, TELEGRAM_BOT_POOL config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add index.ts refactor plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract channel abstraction, IPC, and router from index.ts Break the 1088-line monolith into focused modules: - src/channels/whatsapp.ts: WhatsAppChannel class implementing Channel interface - src/ipc.ts: IPC watcher and task processing with dependency injection - src/router.ts: message formatting, outbound routing, channel lookup - src/types.ts: Channel interface, OnInboundMessage, OnChatMetadata types Also adds regression test suite (98 tests), updates all documentation and skill files to reflect the new architecture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add test workflow for PRs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove accidentally committed pool-bot assets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): remove grammy from base dependencies Grammy is installed by the /add-telegram skill, not a base dependency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
731 lines
19 KiB
Markdown
731 lines
19 KiB
Markdown
---
|
|
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:
|
|
|
|
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
|
|
|
|
## Initial Questions
|
|
|
|
Ask the user:
|
|
|
|
> How do you want to use Gmail with NanoClaw?
|
|
>
|
|
> **Option 1: Tool Mode**
|
|
> - Agent can read and send emails when you ask it to
|
|
> - Triggered only from WhatsApp (e.g., "@Andy check my email" or "@Andy send an email to...")
|
|
> - Simpler setup, no email polling
|
|
>
|
|
> **Option 2: Channel Mode**
|
|
> - Everything in Tool Mode, plus:
|
|
> - Emails to a specific address/label trigger the agent
|
|
> - Agent replies via email (not WhatsApp)
|
|
> - Can schedule tasks via email
|
|
> - Requires email polling infrastructure
|
|
|
|
Store their choice and proceed to the appropriate section.
|
|
|
|
---
|
|
|
|
## Prerequisites (Both Modes)
|
|
|
|
### 1. Check Existing Gmail Setup
|
|
|
|
First, check if Gmail is already configured:
|
|
|
|
```bash
|
|
ls -la ~/.gmail-mcp/ 2>/dev/null || echo "No Gmail config found"
|
|
```
|
|
|
|
If `credentials.json` exists, skip to "Verify Gmail Access" below.
|
|
|
|
### 2. Create Gmail Config Directory
|
|
|
|
```bash
|
|
mkdir -p ~/.gmail-mcp
|
|
```
|
|
|
|
### 3. GCP Project Setup
|
|
|
|
**USER ACTION REQUIRED**
|
|
|
|
Tell the user:
|
|
|
|
> I need you to set up Google Cloud OAuth credentials. I'll walk you through it:
|
|
>
|
|
> 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`
|
|
>
|
|
> Where did you save the file? (Give me the full path, or just paste the file contents here)
|
|
|
|
If user provides a path, copy it:
|
|
|
|
```bash
|
|
cp "/path/user/provided/gcp-oauth.keys.json" ~/.gmail-mcp/gcp-oauth.keys.json
|
|
```
|
|
|
|
If user pastes the JSON content, write it directly:
|
|
|
|
```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**
|
|
|
|
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.
|
|
|
|
Run the authorization:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
timeout 60 npx -y @gongrzhe/server-gmail-autoauth-mcp || true
|
|
```
|
|
|
|
Tell user:
|
|
> Complete the authorization in your browser. The window should close automatically when done. Let me know when you've authorized.
|
|
|
|
### 5. Verify Gmail Access
|
|
|
|
Check that credentials were saved:
|
|
|
|
```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
|
|
```
|
|
|
|
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:
|
|
|
|
```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:
|
|
|
|
```bash
|
|
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
|
|
```
|
|
|
|
Check that it started:
|
|
|
|
```bash
|
|
sleep 2 && launchctl list | grep nanoclaw
|
|
```
|
|
|
|
### Step 5: Test Gmail Integration
|
|
|
|
Tell the user:
|
|
|
|
> Gmail integration is set up! Test it by sending this message in your WhatsApp main channel:
|
|
>
|
|
> `@Andy check my recent emails`
|
|
>
|
|
> Or:
|
|
>
|
|
> `@Andy list my Gmail labels`
|
|
|
|
Watch the logs for any errors:
|
|
|
|
```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
|
|
|
|
Ask the user:
|
|
|
|
> How should the agent be triggered from email?
|
|
>
|
|
> **Option A: Specific Label**
|
|
> - Create a Gmail label (e.g., "NanoClaw")
|
|
> - Emails with this label trigger the agent
|
|
> - You manually label emails or set up Gmail filters
|
|
>
|
|
> **Option B: Email Address Pattern**
|
|
> - Emails to a specific address pattern (e.g., andy+task@gmail.com)
|
|
> - Uses Gmail's plus-addressing feature
|
|
>
|
|
> **Option C: Subject Prefix**
|
|
> - Emails with a subject starting with a keyword (e.g., "[Andy]")
|
|
> - Anyone can trigger the agent by using the prefix
|
|
|
|
Also ask:
|
|
|
|
> How should email conversations be grouped?
|
|
>
|
|
> **Option A: Per Email Thread**
|
|
> - Each email thread gets its own conversation context
|
|
> - Agent remembers the thread history
|
|
>
|
|
> **Option B: Per Sender**
|
|
> - All emails from the same sender share context
|
|
> - Agent remembers all interactions with that person
|
|
>
|
|
> **Option C: Single Context**
|
|
> - All emails share the main group context
|
|
> - Like an additional input to the main channel
|
|
|
|
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
|
|
```
|
|
|
|
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
|
|
```bash
|
|
# Test Gmail MCP directly
|
|
npx -y @gongrzhe/server-gmail-autoauth-mcp
|
|
```
|
|
|
|
### OAuth token expired
|
|
```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
|
|
- Check container logs: `cat groups/main/logs/container-*.log | tail -50`
|
|
|
|
---
|
|
|
|
## Removing Gmail Integration
|
|
|
|
To remove Gmail entirely:
|
|
|
|
1. Remove from `container/agent-runner/src/index.ts`:
|
|
- Delete `gmail` from `mcpServers`
|
|
- Remove `mcp__gmail__*` from `allowedTools`
|
|
|
|
2. Remove from `src/container-runner.ts`:
|
|
- Delete the `~/.gmail-mcp` mount block
|
|
|
|
3. Remove from `src/index.ts` (Channel Mode only):
|
|
- Delete `startEmailLoop()` call
|
|
- Delete email-related imports
|
|
|
|
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
|
|
```
|