@agentforge-io/connectors-calendly 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ import type { ConnectorDefinition } from '@agentforge-io/core';
2
+ export interface CalendlyConnectorOptions {
3
+ clientId: string;
4
+ clientSecret: string;
5
+ /** Optional override of the logo asset URL shown in the Directory. */
6
+ iconUrl?: string;
7
+ }
8
+ /**
9
+ * Build the Calendly `ConnectorDefinition` to feed into
10
+ * `ConnectorRegistryService.register(...)`. The host owns the
11
+ * `clientId` / `clientSecret` pair — typically resolved from the platform
12
+ * vault under `CALENDLY_OAUTH_CLIENT_ID` / `CALENDLY_OAUTH_CLIENT_SECRET`,
13
+ * with an optional per-tenant override (mirrors HubSpot / Slack pattern).
14
+ *
15
+ * OAuth notes:
16
+ * - Calendly does NOT use granular scopes. The grant is all-or-nothing on
17
+ * the connected account, so we leave `scopes: []` and the registry will
18
+ * omit the param from the authorize URL.
19
+ * - Access tokens TTL is 7200s (2h). Refresh tokens ROTATE on every
20
+ * refresh — the core registry already persists the new refresh_token
21
+ * when one comes back (see `buildAuthFields`), so no extra wiring
22
+ * needed here.
23
+ * - PKCE is supported and recommended by Calendly. The registry defaults
24
+ * `usePkce` to true; we keep that.
25
+ */
26
+ export declare function calendlyConnector(opts: CalendlyConnectorOptions): ConnectorDefinition;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyConnector = calendlyConnector;
4
+ const get_user_1 = require("./tools/get-user");
5
+ const list_event_types_1 = require("./tools/list-event-types");
6
+ const list_scheduled_events_1 = require("./tools/list-scheduled-events");
7
+ const get_event_1 = require("./tools/get-event");
8
+ const list_invitees_1 = require("./tools/list-invitees");
9
+ const cancel_event_1 = require("./tools/cancel-event");
10
+ /**
11
+ * Build the Calendly `ConnectorDefinition` to feed into
12
+ * `ConnectorRegistryService.register(...)`. The host owns the
13
+ * `clientId` / `clientSecret` pair — typically resolved from the platform
14
+ * vault under `CALENDLY_OAUTH_CLIENT_ID` / `CALENDLY_OAUTH_CLIENT_SECRET`,
15
+ * with an optional per-tenant override (mirrors HubSpot / Slack pattern).
16
+ *
17
+ * OAuth notes:
18
+ * - Calendly does NOT use granular scopes. The grant is all-or-nothing on
19
+ * the connected account, so we leave `scopes: []` and the registry will
20
+ * omit the param from the authorize URL.
21
+ * - Access tokens TTL is 7200s (2h). Refresh tokens ROTATE on every
22
+ * refresh — the core registry already persists the new refresh_token
23
+ * when one comes back (see `buildAuthFields`), so no extra wiring
24
+ * needed here.
25
+ * - PKCE is supported and recommended by Calendly. The registry defaults
26
+ * `usePkce` to true; we keep that.
27
+ */
28
+ function calendlyConnector(opts) {
29
+ return {
30
+ id: 'calendly',
31
+ name: 'Calendly',
32
+ description: 'Read Calendly event types, list scheduled meetings, fetch invitee ' +
33
+ 'details, and cancel events from agent conversations.',
34
+ category: 'Scheduling',
35
+ iconUrl: opts.iconUrl,
36
+ oauth: {
37
+ authorizeUrl: 'https://auth.calendly.com/oauth/authorize',
38
+ tokenUrl: 'https://auth.calendly.com/oauth/token',
39
+ clientId: opts.clientId,
40
+ clientSecret: opts.clientSecret,
41
+ scopes: [],
42
+ usePkce: true,
43
+ },
44
+ tools: [
45
+ get_user_1.calendlyGetUserTool,
46
+ list_event_types_1.calendlyListEventTypesTool,
47
+ list_scheduled_events_1.calendlyListScheduledEventsTool,
48
+ get_event_1.calendlyGetEventTool,
49
+ list_invitees_1.calendlyListInviteesTool,
50
+ cancel_event_1.calendlyCancelEventTool,
51
+ ],
52
+ };
53
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Thin wrapper around `fetch` for calling Calendly v2 APIs with the
3
+ * tenant's access token. Kept dep-free on purpose — the official
4
+ * `@calendly/api-client` is geared at the booking widget, not server
5
+ * automation, and carries an auth layer we already own.
6
+ *
7
+ * Three transparent retries on top of `fetch`:
8
+ * - 429: honors `Retry-After` (seconds), up to 2 retries. Calendly's
9
+ * default rate limit is generous (1k req/min per token) but bursts
10
+ * during agent runs can blow past it.
11
+ * - 5xx: one retry with ~500ms jitter.
12
+ * - 401: NOT retried here — the registry catches it, refreshes the
13
+ * token, and the agent runner re-dispatches the tool call. Retrying
14
+ * here with the same expired token would just burn another HTTP.
15
+ */
16
+ export declare class CalendlyApiError extends Error {
17
+ readonly status: number;
18
+ readonly statusText: string;
19
+ readonly body: string;
20
+ constructor(status: number, statusText: string, body: string);
21
+ }
22
+ export interface CalendlyGetOptions {
23
+ accessToken: string;
24
+ url: string;
25
+ query?: Record<string, string | number | boolean | undefined>;
26
+ }
27
+ export declare function calendlyGet<T>(opts: CalendlyGetOptions): Promise<T>;
28
+ export interface CalendlyPostOptions {
29
+ accessToken: string;
30
+ url: string;
31
+ body: unknown;
32
+ }
33
+ export declare function calendlyPost<T>(opts: CalendlyPostOptions): Promise<T>;
package/dist/http.js ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ /**
3
+ * Thin wrapper around `fetch` for calling Calendly v2 APIs with the
4
+ * tenant's access token. Kept dep-free on purpose — the official
5
+ * `@calendly/api-client` is geared at the booking widget, not server
6
+ * automation, and carries an auth layer we already own.
7
+ *
8
+ * Three transparent retries on top of `fetch`:
9
+ * - 429: honors `Retry-After` (seconds), up to 2 retries. Calendly's
10
+ * default rate limit is generous (1k req/min per token) but bursts
11
+ * during agent runs can blow past it.
12
+ * - 5xx: one retry with ~500ms jitter.
13
+ * - 401: NOT retried here — the registry catches it, refreshes the
14
+ * token, and the agent runner re-dispatches the tool call. Retrying
15
+ * here with the same expired token would just burn another HTTP.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.CalendlyApiError = void 0;
19
+ exports.calendlyGet = calendlyGet;
20
+ exports.calendlyPost = calendlyPost;
21
+ class CalendlyApiError extends Error {
22
+ constructor(status, statusText, body) {
23
+ super(`Calendly API ${status} ${statusText}: ${body.slice(0, 500)}`);
24
+ this.status = status;
25
+ this.statusText = statusText;
26
+ this.body = body;
27
+ }
28
+ }
29
+ exports.CalendlyApiError = CalendlyApiError;
30
+ const MAX_429_RETRIES = 2;
31
+ const MAX_5XX_RETRIES = 1;
32
+ async function send(method, opts) {
33
+ const headers = {
34
+ authorization: `Bearer ${opts.accessToken}`,
35
+ accept: 'application/json',
36
+ };
37
+ let body;
38
+ if (opts.body !== undefined && method !== 'GET') {
39
+ headers['content-type'] = 'application/json';
40
+ body = JSON.stringify(opts.body);
41
+ }
42
+ let attempt429 = 0;
43
+ let attempt5xx = 0;
44
+ while (true) {
45
+ const res = await fetch(opts.url, { method, headers, body });
46
+ if (res.status === 429 && attempt429 < MAX_429_RETRIES) {
47
+ const retryAfter = Number(res.headers.get('retry-after') ?? '1');
48
+ const delayMs = (Number.isFinite(retryAfter) ? retryAfter : 1) * 1000;
49
+ await sleep(delayMs);
50
+ attempt429++;
51
+ continue;
52
+ }
53
+ if (res.status >= 500 && res.status < 600 && attempt5xx < MAX_5XX_RETRIES) {
54
+ await sleep(400 + Math.floor(Math.random() * 200));
55
+ attempt5xx++;
56
+ continue;
57
+ }
58
+ return res;
59
+ }
60
+ }
61
+ function sleep(ms) {
62
+ return new Promise((r) => setTimeout(r, ms));
63
+ }
64
+ async function calendlyGet(opts) {
65
+ const url = new URL(opts.url);
66
+ for (const [k, v] of Object.entries(opts.query ?? {})) {
67
+ if (v === undefined || v === null)
68
+ continue;
69
+ url.searchParams.set(k, String(v));
70
+ }
71
+ const res = await send('GET', { accessToken: opts.accessToken, url: url.toString() });
72
+ if (!res.ok) {
73
+ throw new CalendlyApiError(res.status, res.statusText, await res.text().catch(() => ''));
74
+ }
75
+ return (await res.json());
76
+ }
77
+ async function calendlyPost(opts) {
78
+ const res = await send('POST', opts);
79
+ if (!res.ok) {
80
+ throw new CalendlyApiError(res.status, res.statusText, await res.text().catch(() => ''));
81
+ }
82
+ // Calendly's cancellation endpoint returns 201 with the cancelled
83
+ // event payload; the meeting-create endpoints return 201. We never
84
+ // see a 204 from this API in practice, but guard against it anyway
85
+ // so we don't surface a confusing "Unexpected end of JSON input" if
86
+ // they ever add one.
87
+ if (res.status === 204)
88
+ return undefined;
89
+ return (await res.json());
90
+ }
@@ -0,0 +1,8 @@
1
+ export { calendlyConnector, type CalendlyConnectorOptions } from './connector';
2
+ export { calendlyGetUserTool } from './tools/get-user';
3
+ export { calendlyListEventTypesTool } from './tools/list-event-types';
4
+ export { calendlyListScheduledEventsTool } from './tools/list-scheduled-events';
5
+ export { calendlyGetEventTool } from './tools/get-event';
6
+ export { calendlyListInviteesTool } from './tools/list-invitees';
7
+ export { calendlyCancelEventTool } from './tools/cancel-event';
8
+ export { CalendlyApiError } from './http';
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ // Public API of @agentforge-io/connectors-calendly.
3
+ //
4
+ // Usage in a host's bootstrap:
5
+ //
6
+ // import { calendlyConnector } from '@agentforge-io/connectors-calendly';
7
+ // connectorRegistry.register(calendlyConnector({
8
+ // clientId: env.CALENDLY_OAUTH_CLIENT_ID,
9
+ // clientSecret: env.CALENDLY_OAUTH_CLIENT_SECRET,
10
+ // }));
11
+ //
12
+ // Tools registered (per-tenant, after a workspace admin has OAuth'd):
13
+ // calendly_get_user, calendly_list_event_types,
14
+ // calendly_list_scheduled_events, calendly_get_event,
15
+ // calendly_list_invitees, calendly_cancel_event
16
+ //
17
+ // See `CALENDLY_CONNECTOR_SDD.md` at the repo root for the full design.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.CalendlyApiError = exports.calendlyCancelEventTool = exports.calendlyListInviteesTool = exports.calendlyGetEventTool = exports.calendlyListScheduledEventsTool = exports.calendlyListEventTypesTool = exports.calendlyGetUserTool = exports.calendlyConnector = void 0;
20
+ var connector_1 = require("./connector");
21
+ Object.defineProperty(exports, "calendlyConnector", { enumerable: true, get: function () { return connector_1.calendlyConnector; } });
22
+ var get_user_1 = require("./tools/get-user");
23
+ Object.defineProperty(exports, "calendlyGetUserTool", { enumerable: true, get: function () { return get_user_1.calendlyGetUserTool; } });
24
+ var list_event_types_1 = require("./tools/list-event-types");
25
+ Object.defineProperty(exports, "calendlyListEventTypesTool", { enumerable: true, get: function () { return list_event_types_1.calendlyListEventTypesTool; } });
26
+ var list_scheduled_events_1 = require("./tools/list-scheduled-events");
27
+ Object.defineProperty(exports, "calendlyListScheduledEventsTool", { enumerable: true, get: function () { return list_scheduled_events_1.calendlyListScheduledEventsTool; } });
28
+ var get_event_1 = require("./tools/get-event");
29
+ Object.defineProperty(exports, "calendlyGetEventTool", { enumerable: true, get: function () { return get_event_1.calendlyGetEventTool; } });
30
+ var list_invitees_1 = require("./tools/list-invitees");
31
+ Object.defineProperty(exports, "calendlyListInviteesTool", { enumerable: true, get: function () { return list_invitees_1.calendlyListInviteesTool; } });
32
+ var cancel_event_1 = require("./tools/cancel-event");
33
+ Object.defineProperty(exports, "calendlyCancelEventTool", { enumerable: true, get: function () { return cancel_event_1.calendlyCancelEventTool; } });
34
+ var http_1 = require("./http");
35
+ Object.defineProperty(exports, "CalendlyApiError", { enumerable: true, get: function () { return http_1.CalendlyApiError; } });
@@ -0,0 +1,12 @@
1
+ import type { ConnectorToolFactory } from '@agentforge-io/core';
2
+ /**
3
+ * Cancel a Calendly meeting. The `reason` field is required by the
4
+ * Calendly API — passing an empty string is rejected with 400, so we
5
+ * default it server-side to a generic message. The invitee receives
6
+ * Calendly's standard cancellation email automatically.
7
+ *
8
+ * **Approval recommended.** This is a side-effectful write that's
9
+ * visible to the invitee — wire it as `approval` mode on the agent's
10
+ * tool whitelist so the human confirms before the email goes out.
11
+ */
12
+ export declare const calendlyCancelEventTool: ConnectorToolFactory;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyCancelEventTool = void 0;
4
+ const http_1 = require("../http");
5
+ /**
6
+ * Cancel a Calendly meeting. The `reason` field is required by the
7
+ * Calendly API — passing an empty string is rejected with 400, so we
8
+ * default it server-side to a generic message. The invitee receives
9
+ * Calendly's standard cancellation email automatically.
10
+ *
11
+ * **Approval recommended.** This is a side-effectful write that's
12
+ * visible to the invitee — wire it as `approval` mode on the agent's
13
+ * tool whitelist so the human confirms before the email goes out.
14
+ */
15
+ exports.calendlyCancelEventTool = {
16
+ definition: {
17
+ name: 'calendly_cancel_event',
18
+ description: 'Cancel a scheduled Calendly meeting. Calendly emails the invitee ' +
19
+ 'the cancellation automatically. **This is a destructive action ' +
20
+ 'visible to the invitee** — consider asking the human to confirm ' +
21
+ 'before invoking. Returns the cancellation record on success.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ eventUri: {
26
+ type: 'string',
27
+ description: 'Calendly event URI (e.g. ' +
28
+ '`https://api.calendly.com/scheduled_events/ABCD`).',
29
+ },
30
+ reason: {
31
+ type: 'string',
32
+ description: 'Reason shown to the invitee in the cancellation email. ' +
33
+ 'Defaults to "Cancelled by host" if omitted. Keep it short ' +
34
+ '— Calendly renders it inline.',
35
+ },
36
+ },
37
+ required: ['eventUri'],
38
+ },
39
+ },
40
+ build: (ctx) => async (input) => {
41
+ const eventUri = String(input.eventUri ?? '').trim();
42
+ if (!eventUri)
43
+ throw new Error('eventUri is required');
44
+ if (!eventUri.startsWith('https://api.calendly.com/scheduled_events/')) {
45
+ throw new Error('eventUri must be a Calendly scheduled event URI (starts with ' +
46
+ 'https://api.calendly.com/scheduled_events/)');
47
+ }
48
+ const reason = typeof input.reason === 'string' && input.reason.trim()
49
+ ? input.reason.trim()
50
+ : 'Cancelled by host';
51
+ const accessToken = await ctx.getAccessToken();
52
+ const url = `${eventUri}/cancellation`;
53
+ const result = await (0, http_1.calendlyPost)({
54
+ accessToken,
55
+ url,
56
+ body: { reason },
57
+ });
58
+ return JSON.stringify({
59
+ cancelled: result.resource.canceled,
60
+ reason: result.resource.reason,
61
+ cancelerType: result.resource.canceler_type,
62
+ cancelledAt: result.resource.created_at,
63
+ });
64
+ },
65
+ };
@@ -0,0 +1,7 @@
1
+ import type { ConnectorToolFactory } from '@agentforge-io/core';
2
+ /**
3
+ * Fetch a single scheduled event's full payload by URI. Use this after
4
+ * `calendly_list_scheduled_events` returns an event you want to inspect
5
+ * in detail — meeting notes, host members, join URL, etc.
6
+ */
7
+ export declare const calendlyGetEventTool: ConnectorToolFactory;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyGetEventTool = void 0;
4
+ const http_1 = require("../http");
5
+ /**
6
+ * Fetch a single scheduled event's full payload by URI. Use this after
7
+ * `calendly_list_scheduled_events` returns an event you want to inspect
8
+ * in detail — meeting notes, host members, join URL, etc.
9
+ */
10
+ exports.calendlyGetEventTool = {
11
+ definition: {
12
+ name: 'calendly_get_event',
13
+ description: 'Fetch a single Calendly scheduled event by its URI. Returns full ' +
14
+ 'detail including meeting notes, location, host members, and ' +
15
+ 'invitee count. Use after `calendly_list_scheduled_events` when ' +
16
+ 'you need fields the list view omits.',
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ eventUri: {
21
+ type: 'string',
22
+ description: 'Calendly event URI (e.g. ' +
23
+ '`https://api.calendly.com/scheduled_events/ABCD`) returned ' +
24
+ 'by `calendly_list_scheduled_events`.',
25
+ },
26
+ },
27
+ required: ['eventUri'],
28
+ },
29
+ },
30
+ build: (ctx) => async (input) => {
31
+ const eventUri = String(input.eventUri ?? '').trim();
32
+ if (!eventUri)
33
+ throw new Error('eventUri is required');
34
+ if (!eventUri.startsWith('https://api.calendly.com/scheduled_events/')) {
35
+ throw new Error('eventUri must be a Calendly scheduled event URI (starts with ' +
36
+ 'https://api.calendly.com/scheduled_events/)');
37
+ }
38
+ const accessToken = await ctx.getAccessToken();
39
+ const json = await (0, http_1.calendlyGet)({
40
+ accessToken,
41
+ url: eventUri,
42
+ });
43
+ const e = json.resource;
44
+ return JSON.stringify({
45
+ uri: e.uri,
46
+ name: e.name,
47
+ status: e.status,
48
+ startTime: e.start_time,
49
+ endTime: e.end_time,
50
+ eventTypeUri: e.event_type,
51
+ location: e.location
52
+ ? {
53
+ type: e.location.type,
54
+ location: e.location.location,
55
+ joinUrl: e.location.join_url,
56
+ }
57
+ : undefined,
58
+ invitees: e.invitees_counter,
59
+ meetingNotes: e.meeting_notes_plain ?? undefined,
60
+ hosts: e.event_memberships?.map((m) => ({
61
+ userUri: m.user,
62
+ email: m.user_email,
63
+ })),
64
+ });
65
+ },
66
+ };
@@ -0,0 +1,9 @@
1
+ import type { ConnectorToolFactory } from '@agentforge-io/core';
2
+ /**
3
+ * Returns the connected Calendly user's identity + organization URI. The
4
+ * `uri` and `current_organization` fields are needed by almost every
5
+ * other Calendly endpoint (event types are scoped to a user, scheduled
6
+ * events to an organization). Cheap call — agents should hit this once
7
+ * at the top of a session and reuse the result.
8
+ */
9
+ export declare const calendlyGetUserTool: ConnectorToolFactory;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyGetUserTool = void 0;
4
+ const http_1 = require("../http");
5
+ const USERS_ME = 'https://api.calendly.com/users/me';
6
+ /**
7
+ * Returns the connected Calendly user's identity + organization URI. The
8
+ * `uri` and `current_organization` fields are needed by almost every
9
+ * other Calendly endpoint (event types are scoped to a user, scheduled
10
+ * events to an organization). Cheap call — agents should hit this once
11
+ * at the top of a session and reuse the result.
12
+ */
13
+ exports.calendlyGetUserTool = {
14
+ definition: {
15
+ name: 'calendly_get_user',
16
+ description: 'Fetch the connected Calendly account: name, email, scheduling URL, ' +
17
+ 'timezone, user URI, and organization URI. Call this once at the ' +
18
+ 'start of any Calendly workflow — most other Calendly tools need ' +
19
+ 'the `userUri` or `organizationUri` returned here.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {},
23
+ },
24
+ },
25
+ build: (ctx) => async () => {
26
+ const accessToken = await ctx.getAccessToken();
27
+ const json = await (0, http_1.calendlyGet)({
28
+ accessToken,
29
+ url: USERS_ME,
30
+ });
31
+ const r = json.resource;
32
+ return JSON.stringify({
33
+ uri: r.uri,
34
+ name: r.name,
35
+ email: r.email,
36
+ schedulingUrl: r.scheduling_url,
37
+ timezone: r.timezone,
38
+ organizationUri: r.current_organization,
39
+ });
40
+ },
41
+ };
@@ -0,0 +1,11 @@
1
+ import type { ConnectorToolFactory } from '@agentforge-io/core';
2
+ /**
3
+ * List the user's Calendly event types — the bookable meeting templates
4
+ * the operator has configured on calendly.com (e.g. "30-min intro",
5
+ * "Strategy call"). Agents need this when an invitee asks "how do I book
6
+ * a meeting with you?" so the agent can return the right `scheduling_url`.
7
+ *
8
+ * Calendly doesn't expose a way to CREATE event types via API; templates
9
+ * are managed in the Calendly UI. This tool only reads.
10
+ */
11
+ export declare const calendlyListEventTypesTool: ConnectorToolFactory;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyListEventTypesTool = void 0;
4
+ const http_1 = require("../http");
5
+ const EVENT_TYPES = 'https://api.calendly.com/event_types';
6
+ /**
7
+ * List the user's Calendly event types — the bookable meeting templates
8
+ * the operator has configured on calendly.com (e.g. "30-min intro",
9
+ * "Strategy call"). Agents need this when an invitee asks "how do I book
10
+ * a meeting with you?" so the agent can return the right `scheduling_url`.
11
+ *
12
+ * Calendly doesn't expose a way to CREATE event types via API; templates
13
+ * are managed in the Calendly UI. This tool only reads.
14
+ */
15
+ exports.calendlyListEventTypesTool = {
16
+ definition: {
17
+ name: 'calendly_list_event_types',
18
+ description: "List the Calendly user's bookable event types (meeting templates). " +
19
+ 'Returns each event type with its name, duration, scheduling URL, ' +
20
+ 'and active status. **Call `calendly_get_user` first** to obtain ' +
21
+ 'the `userUri` required by this endpoint.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ userUri: {
26
+ type: 'string',
27
+ description: 'Calendly user URI (e.g. `https://api.calendly.com/users/ABCD`) ' +
28
+ 'returned by `calendly_get_user`.',
29
+ },
30
+ activeOnly: {
31
+ type: 'boolean',
32
+ default: true,
33
+ description: 'When true (default), only return event types currently ' +
34
+ 'accepting bookings. Set to false to also include disabled ones.',
35
+ },
36
+ },
37
+ required: ['userUri'],
38
+ },
39
+ },
40
+ build: (ctx) => async (input) => {
41
+ const userUri = String(input.userUri ?? '').trim();
42
+ if (!userUri)
43
+ throw new Error('userUri is required');
44
+ const activeOnly = input.activeOnly !== false;
45
+ const accessToken = await ctx.getAccessToken();
46
+ const result = await (0, http_1.calendlyGet)({
47
+ accessToken,
48
+ url: EVENT_TYPES,
49
+ query: {
50
+ user: userUri,
51
+ active: activeOnly ? true : undefined,
52
+ },
53
+ });
54
+ return JSON.stringify({
55
+ total: result.pagination.count,
56
+ eventTypes: result.collection.map((e) => ({
57
+ uri: e.uri,
58
+ name: e.name,
59
+ slug: e.slug,
60
+ durationMinutes: e.duration,
61
+ schedulingUrl: e.scheduling_url,
62
+ active: e.active,
63
+ kind: e.kind,
64
+ description: e.description_plain ?? undefined,
65
+ })),
66
+ });
67
+ },
68
+ };
@@ -0,0 +1,9 @@
1
+ import type { ConnectorToolFactory } from '@agentforge-io/core';
2
+ /**
3
+ * List the people who booked into a specific scheduled event. The
4
+ * `questions_and_answers` field exposes whatever custom intake form
5
+ * the operator configured on the event type — that's usually where the
6
+ * "How can I help?" context lives, which is gold for an agent
7
+ * preparing a meeting briefing.
8
+ */
9
+ export declare const calendlyListInviteesTool: ConnectorToolFactory;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyListInviteesTool = void 0;
4
+ const http_1 = require("../http");
5
+ /**
6
+ * List the people who booked into a specific scheduled event. The
7
+ * `questions_and_answers` field exposes whatever custom intake form
8
+ * the operator configured on the event type — that's usually where the
9
+ * "How can I help?" context lives, which is gold for an agent
10
+ * preparing a meeting briefing.
11
+ */
12
+ exports.calendlyListInviteesTool = {
13
+ definition: {
14
+ name: 'calendly_list_invitees',
15
+ description: "List the invitees (bookers) on a specific Calendly scheduled " +
16
+ 'event. Returns each invitee with name, email, status, timezone, ' +
17
+ 'and any intake-form answers. Use this to brief an agent before a ' +
18
+ 'meeting or to find the cancel/reschedule URLs for an invitee.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ eventUri: {
23
+ type: 'string',
24
+ description: 'Calendly event URI (e.g. ' +
25
+ '`https://api.calendly.com/scheduled_events/ABCD`).',
26
+ },
27
+ status: {
28
+ type: 'string',
29
+ enum: ['active', 'canceled'],
30
+ description: 'Optional filter — only return invitees in this status.',
31
+ },
32
+ limit: {
33
+ type: 'integer',
34
+ minimum: 1,
35
+ maximum: 100,
36
+ default: 20,
37
+ },
38
+ },
39
+ required: ['eventUri'],
40
+ },
41
+ },
42
+ build: (ctx) => async (input) => {
43
+ const eventUri = String(input.eventUri ?? '').trim();
44
+ if (!eventUri)
45
+ throw new Error('eventUri is required');
46
+ if (!eventUri.startsWith('https://api.calendly.com/scheduled_events/')) {
47
+ throw new Error('eventUri must be a Calendly scheduled event URI (starts with ' +
48
+ 'https://api.calendly.com/scheduled_events/)');
49
+ }
50
+ const limit = Math.min(100, Math.max(1, Number(input.limit ?? 20)));
51
+ const accessToken = await ctx.getAccessToken();
52
+ const url = `${eventUri}/invitees`;
53
+ const result = await (0, http_1.calendlyGet)({
54
+ accessToken,
55
+ url,
56
+ query: {
57
+ status: input.status === 'active' || input.status === 'canceled'
58
+ ? input.status
59
+ : undefined,
60
+ count: limit,
61
+ },
62
+ });
63
+ return JSON.stringify({
64
+ total: result.pagination.count,
65
+ invitees: result.collection.map((i) => ({
66
+ uri: i.uri,
67
+ name: i.name,
68
+ email: i.email,
69
+ status: i.status,
70
+ timezone: i.timezone,
71
+ rescheduled: i.rescheduled,
72
+ cancelUrl: i.cancel_url,
73
+ rescheduleUrl: i.reschedule_url,
74
+ intake: i.questions_and_answers
75
+ .filter((q) => q.answer && q.answer.trim().length > 0)
76
+ .map((q) => ({ question: q.question, answer: q.answer })),
77
+ })),
78
+ });
79
+ },
80
+ };
@@ -0,0 +1,8 @@
1
+ import type { ConnectorToolFactory } from '@agentforge-io/core';
2
+ /**
3
+ * List scheduled meetings on the connected Calendly account. Returns
4
+ * recent + upcoming meetings filtered by date range, status, or invitee
5
+ * email. Hard-capped at 100 results — the model rarely needs more, and
6
+ * Calendly's `count` query param tops out at 100 anyway.
7
+ */
8
+ export declare const calendlyListScheduledEventsTool: ConnectorToolFactory;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendlyListScheduledEventsTool = void 0;
4
+ const http_1 = require("../http");
5
+ const SCHEDULED_EVENTS = 'https://api.calendly.com/scheduled_events';
6
+ /**
7
+ * List scheduled meetings on the connected Calendly account. Returns
8
+ * recent + upcoming meetings filtered by date range, status, or invitee
9
+ * email. Hard-capped at 100 results — the model rarely needs more, and
10
+ * Calendly's `count` query param tops out at 100 anyway.
11
+ */
12
+ exports.calendlyListScheduledEventsTool = {
13
+ definition: {
14
+ name: 'calendly_list_scheduled_events',
15
+ description: 'List scheduled Calendly meetings filtered by date range, status, ' +
16
+ 'or invitee email. Returns each meeting with its URI, name, ' +
17
+ 'start/end time, status, location, and invitee count. **Call ' +
18
+ '`calendly_get_user` first** to obtain `userUri` or ' +
19
+ '`organizationUri`. Use `organizationUri` to see all meetings in ' +
20
+ 'the workspace; use `userUri` to see only this user\'s meetings.',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ userUri: {
25
+ type: 'string',
26
+ description: 'Calendly user URI. Pass either this OR `organizationUri` ' +
27
+ '(not both).',
28
+ },
29
+ organizationUri: {
30
+ type: 'string',
31
+ description: 'Calendly organization URI. Pass either this OR `userUri` ' +
32
+ '(not both).',
33
+ },
34
+ minStartTime: {
35
+ type: 'string',
36
+ description: 'ISO 8601 timestamp — only return meetings starting at or ' +
37
+ 'after this time (e.g. `2026-06-07T00:00:00Z`).',
38
+ },
39
+ maxStartTime: {
40
+ type: 'string',
41
+ description: 'ISO 8601 timestamp — only return meetings starting at or ' +
42
+ 'before this time.',
43
+ },
44
+ status: {
45
+ type: 'string',
46
+ enum: ['active', 'canceled'],
47
+ description: 'Filter by status. Omit to return both active and canceled.',
48
+ },
49
+ inviteeEmail: {
50
+ type: 'string',
51
+ description: "Optional — only return meetings where this email is an " +
52
+ 'invitee. Useful for "what meetings do I have with X?" queries.',
53
+ },
54
+ limit: {
55
+ type: 'integer',
56
+ minimum: 1,
57
+ maximum: 100,
58
+ default: 20,
59
+ },
60
+ },
61
+ },
62
+ },
63
+ build: (ctx) => async (input) => {
64
+ const userUri = typeof input.userUri === 'string' ? input.userUri.trim() : '';
65
+ const organizationUri = typeof input.organizationUri === 'string'
66
+ ? input.organizationUri.trim()
67
+ : '';
68
+ if (!userUri && !organizationUri) {
69
+ throw new Error('userUri or organizationUri is required');
70
+ }
71
+ const limit = Math.min(100, Math.max(1, Number(input.limit ?? 20)));
72
+ const accessToken = await ctx.getAccessToken();
73
+ const result = await (0, http_1.calendlyGet)({
74
+ accessToken,
75
+ url: SCHEDULED_EVENTS,
76
+ query: {
77
+ user: userUri || undefined,
78
+ organization: organizationUri || undefined,
79
+ min_start_time: typeof input.minStartTime === 'string' ? input.minStartTime : undefined,
80
+ max_start_time: typeof input.maxStartTime === 'string' ? input.maxStartTime : undefined,
81
+ status: input.status === 'active' || input.status === 'canceled'
82
+ ? input.status
83
+ : undefined,
84
+ invitee_email: typeof input.inviteeEmail === 'string' && input.inviteeEmail
85
+ ? input.inviteeEmail
86
+ : undefined,
87
+ count: limit,
88
+ },
89
+ });
90
+ return JSON.stringify({
91
+ total: result.pagination.count,
92
+ events: result.collection.map((e) => ({
93
+ uri: e.uri,
94
+ name: e.name,
95
+ status: e.status,
96
+ startTime: e.start_time,
97
+ endTime: e.end_time,
98
+ eventTypeUri: e.event_type,
99
+ location: e.location
100
+ ? {
101
+ type: e.location.type,
102
+ location: e.location.location,
103
+ joinUrl: e.location.join_url,
104
+ }
105
+ : undefined,
106
+ invitees: e.invitees_counter,
107
+ })),
108
+ });
109
+ },
110
+ };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@agentforge-io/connectors-calendly",
3
+ "version": "3.0.0",
4
+ "description": "Calendly connector for AgentForge \u2014 event types, scheduled events, invitees, and cancellation tools wired to per-tenant OAuth2 credentials managed by the core ConnectorRegistryService.",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.build.json",
13
+ "build:watch": "tsc -p tsconfig.build.json --watch",
14
+ "clean": "rm -rf dist *.tgz"
15
+ },
16
+ "peerDependencies": {
17
+ "@agentforge-io/core": "^3.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@agentforge-io/core": "*",
21
+ "@types/node": "^20.0.0",
22
+ "typescript": "^5.0.0"
23
+ }
24
+ }