openclaw/extensions/nylas/index.ts
Qasim 4c74b1134d feat(nylas): add Nylas API v3 plugin for email, calendar, and contacts
Add a new plugin extension that integrates with Nylas API v3 to provide
email, calendar, and contacts functionality for Clawdbot agents.

## Features

### Tools (16 total)
- Discovery: nylas_discover_grants (auto-discover authenticated accounts)
- Email: list_emails, get_email, send_email, create_draft, list_threads, list_folders
- Calendar: list_calendars, list_events, get_event, create_event, update_event,
  delete_event, check_availability
- Contacts: list_contacts, get_contact

### CLI Commands
- `clawdbot nylas status` - Check API connection and auto-discover grants
- `clawdbot nylas discover` - List all authenticated accounts
- `clawdbot nylas test` - Test API with sample operations
- `clawdbot nylas grants` - Show configured grants

### Gateway Methods
- 8 RPC methods for programmatic access to Nylas functionality

## Technical Details

- Uses official Nylas Node SDK (nylas@^7.5.2)
- TypeBox schemas for tool parameters
- Zod for configuration validation
- Multi-account support via named grants
- Proper error handling with NylasApiError wrapper

## Configuration

```yaml
plugins:
  entries:
    nylas:
      config:
        apiKey: "nyk_v0_..."
        defaultGrantId: "grant-id"
        grants:
          work: "work-grant-id"
          personal: "personal-grant-id"
```

## Testing

- 104 unit tests (100% coverage on core logic)
- 19 integration tests against live Nylas API
- Run with: `bun test extensions/nylas/`
- Live tests: `LIVE=1 bun test extensions/nylas/index.live.test.ts`

## Documentation

- README with setup instructions, tool reference, and troubleshooting
- Support contacts: limitless@nylas.com, support@nylas.com
2026-01-26 18:49:28 -05:00

214 lines
6.6 KiB
TypeScript

import { NylasConfigSchema, parseNylasConfig, validateNylasConfig, type NylasConfig } from "./src/config.js";
import { NylasClient, NylasApiError } from "./src/client.js";
import { nylasTools } from "./src/tools/index.js";
import { registerNylasCli } from "./src/cli.js";
const nylasConfigSchema = {
parse(value: unknown): NylasConfig {
return parseNylasConfig(value);
},
uiHints: {
apiKey: {
label: "API Key",
help: "Nylas API key from dashboard.nylas.com",
sensitive: true,
},
apiUri: {
label: "API URI",
help: "Use https://api.us.nylas.com (US) or https://api.eu.nylas.com (EU)",
placeholder: "https://api.us.nylas.com",
},
defaultGrantId: {
label: "Default Grant ID",
help: "Primary email account's grant ID from Nylas dashboard",
},
defaultTimezone: {
label: "Default Timezone",
help: "Timezone for date/time operations (e.g., America/New_York)",
placeholder: "America/New_York",
},
grants: {
label: "Named Grants",
help: "Map of named grants for multi-account access",
advanced: true,
},
},
};
const nylasPlugin = {
id: "nylas",
name: "Nylas",
description: "Email, calendar, and contacts integration via Nylas API v3",
configSchema: nylasConfigSchema,
register(api) {
const config = nylasConfigSchema.parse(api.pluginConfig);
const validation = validateNylasConfig(config);
// Capture validation errors early for use in getClient closure
const validationErrors = !validation.valid ? validation.errors : null;
// Create client lazily
let client: NylasClient | null = null;
const getClient = () => {
if (!client) {
if (!config.enabled) {
throw new Error("Nylas plugin is disabled");
}
if (validationErrors) {
throw new Error(`Nylas configuration error: ${validationErrors.join("; ")}`);
}
client = new NylasClient({
config,
logger: api.logger,
});
}
return client;
};
// Register all tools
for (const toolDef of nylasTools) {
api.registerTool({
name: toolDef.name,
label: toolDef.label,
description: toolDef.description,
parameters: toolDef.parameters,
async execute(_toolCallId, params) {
const json = (payload: unknown) => ({
content: [
{ type: "text", text: JSON.stringify(payload, null, 2) },
],
details: payload,
});
try {
const result = await toolDef.execute(getClient(), params);
return json(result);
} catch (err) {
if (err instanceof NylasApiError) {
return json({
error: err.message,
status_code: err.statusCode,
error_type: err.errorType,
request_id: err.requestId,
});
}
return json({
error: err instanceof Error ? err.message : String(err),
});
}
},
});
}
// Register CLI commands
api.registerCli(
({ program, logger }) =>
registerNylasCli({
program,
config,
logger,
}),
{ commands: ["nylas"] },
);
// Register gateway methods for programmatic access
api.registerGatewayMethod("nylas.discoverGrants", async ({ params, respond }) => {
try {
const c = getClient();
const result = await c.listGrants(params ?? {});
respond(true, {
grants: result.data.map((g) => ({
id: g.id,
email: g.email,
provider: g.provider,
status: g.grant_status,
scopes: g.scope,
})),
});
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.listEmails", async ({ params, respond }) => {
try {
const c = getClient();
const result = await c.listMessages(params ?? {});
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.getMessage", async ({ params, respond }) => {
try {
const c = getClient();
const messageId = typeof params?.messageId === "string" ? params.messageId : "";
const grant = typeof params?.grant === "string" ? params.grant : undefined;
if (!messageId) {
respond(false, { error: "messageId required" });
return;
}
const result = await c.getMessage(messageId, grant);
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.sendMessage", async ({ params, respond }) => {
try {
const c = getClient();
const result = await c.sendMessage(params ?? {});
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.listCalendars", async ({ params, respond }) => {
try {
const c = getClient();
const grant = typeof params?.grant === "string" ? params.grant : undefined;
const result = await c.listCalendars(grant);
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.listEvents", async ({ params, respond }) => {
try {
const c = getClient();
const result = await c.listEvents(params ?? {});
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.createEvent", async ({ params, respond }) => {
try {
const c = getClient();
const result = await c.createEvent(params as Parameters<NylasClient["createEvent"]>[0]);
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
api.registerGatewayMethod("nylas.listContacts", async ({ params, respond }) => {
try {
const c = getClient();
const result = await c.listContacts(params ?? {});
respond(true, result);
} catch (err) {
respond(false, { error: err instanceof Error ? err.message : String(err) });
}
});
},
};
export default nylasPlugin;