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
214 lines
6.6 KiB
TypeScript
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;
|