@plotday/twister 0.47.0 → 0.49.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.
- package/bin/commands/generate.js +5 -5
- package/bin/commands/generate.js.map +1 -1
- package/bin/templates/AGENTS.template.md +8 -2
- package/bin/utils/bundle.js +14 -0
- package/bin/utils/bundle.js.map +1 -1
- package/cli/templates/AGENTS.template.md +8 -2
- package/dist/connector.d.ts +67 -7
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +15 -5
- package/dist/connector.js.map +1 -1
- package/dist/docs/assets/hierarchy.js +1 -1
- package/dist/docs/assets/navigation.js +1 -1
- package/dist/docs/assets/search.js +1 -1
- package/dist/docs/classes/index.Connector.html +58 -49
- package/dist/docs/classes/index.Imap.html +1 -1
- package/dist/docs/classes/index.Options.html +1 -1
- package/dist/docs/classes/index.Smtp.html +1 -1
- package/dist/docs/classes/tools_ai.AI.html +1 -1
- package/dist/docs/classes/tools_callbacks.Callbacks.html +1 -1
- package/dist/docs/classes/tools_integrations.Integrations.html +21 -5
- package/dist/docs/classes/tools_network.Network.html +1 -1
- package/dist/docs/classes/tools_plot.Plot.html +1 -1
- package/dist/docs/classes/tools_store.Store.html +1 -1
- package/dist/docs/classes/tools_tasks.Tasks.html +1 -1
- package/dist/docs/classes/tools_twists.Twists.html +1 -1
- package/dist/docs/classes/twist.Twist.html +28 -28
- package/dist/docs/documents/Building_Connectors.html +8 -1
- package/dist/docs/documents/CLI_Reference.html +6 -4
- package/dist/docs/enums/tag.Tag.html +11 -1
- package/dist/docs/enums/tools_integrations.AuthProvider.html +14 -12
- package/dist/docs/hierarchy.html +1 -1
- package/dist/docs/media/AGENTS.md +298 -775
- package/dist/docs/media/MULTI_USER_AUTH.md +6 -4
- package/dist/docs/media/SYNC_STRATEGIES.md +20 -14
- package/dist/docs/modules/index.html +1 -1
- package/dist/docs/types/index.CreateLinkDraft.html +7 -12
- package/dist/docs/types/index.NoteWriteBackResult.html +38 -0
- package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
- package/dist/docs/types/tools_integrations.AuthToken.html +4 -4
- package/dist/docs/types/tools_integrations.Authorization.html +4 -4
- package/dist/llm-docs/connector.d.ts +1 -1
- package/dist/llm-docs/connector.d.ts.map +1 -1
- package/dist/llm-docs/connector.js +1 -1
- package/dist/llm-docs/connector.js.map +1 -1
- package/dist/llm-docs/tag.d.ts +1 -1
- package/dist/llm-docs/tag.d.ts.map +1 -1
- package/dist/llm-docs/tag.js +1 -1
- package/dist/llm-docs/tag.js.map +1 -1
- package/dist/llm-docs/tools/integrations.d.ts +1 -1
- package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
- package/dist/llm-docs/tools/integrations.js +1 -1
- package/dist/llm-docs/tools/integrations.js.map +1 -1
- package/dist/llm-docs/twist-guide-template.d.ts +1 -1
- package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
- package/dist/llm-docs/twist-guide-template.js +1 -1
- package/dist/llm-docs/twist-guide-template.js.map +1 -1
- package/dist/llm-docs/twist.d.ts +1 -1
- package/dist/llm-docs/twist.d.ts.map +1 -1
- package/dist/llm-docs/twist.js +1 -1
- package/dist/llm-docs/twist.js.map +1 -1
- package/dist/tag.d.ts +11 -1
- package/dist/tag.d.ts.map +1 -1
- package/dist/tag.js +10 -0
- package/dist/tag.js.map +1 -1
- package/dist/tools/integrations.d.ts +25 -1
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js +2 -0
- package/dist/tools/integrations.js.map +1 -1
- package/dist/twist-guide.d.ts +1 -1
- package/dist/twist-guide.d.ts.map +1 -1
- package/dist/twist.d.ts +2 -1
- package/dist/twist.d.ts.map +1 -1
- package/dist/twist.js.map +1 -1
- package/dist/utils/markdown.d.ts +27 -0
- package/dist/utils/markdown.d.ts.map +1 -0
- package/dist/utils/markdown.js +82 -0
- package/dist/utils/markdown.js.map +1 -0
- package/package.json +7 -1
- package/src/connector.ts +427 -0
- package/src/creator-docs.ts +29 -0
- package/src/index.ts +10 -0
- package/src/llm-docs/connector.ts +8 -0
- package/src/llm-docs/index.ts +48 -0
- package/src/llm-docs/options.ts +8 -0
- package/src/llm-docs/plot.ts +8 -0
- package/src/llm-docs/schedule.ts +8 -0
- package/src/llm-docs/tag.ts +8 -0
- package/src/llm-docs/tool.ts +8 -0
- package/src/llm-docs/tools/ai.ts +8 -0
- package/src/llm-docs/tools/callbacks.ts +8 -0
- package/src/llm-docs/tools/imap.ts +8 -0
- package/src/llm-docs/tools/integrations.ts +8 -0
- package/src/llm-docs/tools/network.ts +8 -0
- package/src/llm-docs/tools/plot.ts +8 -0
- package/src/llm-docs/tools/smtp.ts +8 -0
- package/src/llm-docs/tools/store.ts +8 -0
- package/src/llm-docs/tools/tasks.ts +8 -0
- package/src/llm-docs/tools/twists.ts +8 -0
- package/src/llm-docs/twist-guide-template.ts +8 -0
- package/src/llm-docs/twist.ts +8 -0
- package/src/options.ts +115 -0
- package/src/plot.ts +1068 -0
- package/src/schedule.ts +203 -0
- package/src/tag.ts +54 -0
- package/src/tool.ts +377 -0
- package/src/tools/ai.ts +845 -0
- package/src/tools/callbacks.ts +134 -0
- package/src/tools/imap.ts +266 -0
- package/src/tools/index.ts +10 -0
- package/src/tools/integrations.ts +352 -0
- package/src/tools/network.ts +240 -0
- package/src/tools/plot.ts +692 -0
- package/src/tools/smtp.ts +166 -0
- package/src/tools/store.ts +149 -0
- package/src/tools/tasks.ts +137 -0
- package/src/tools/twists.ts +228 -0
- package/src/twist-guide.ts +9 -0
- package/src/twist.ts +436 -0
- package/src/utils/hash.ts +8 -0
- package/src/utils/markdown.ts +94 -0
- package/src/utils/serializable.ts +54 -0
- package/src/utils/types.ts +130 -0
- package/src/utils/uuid.ts +9 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Actor,
|
|
3
|
+
type ActorId,
|
|
4
|
+
type NewContact,
|
|
5
|
+
type NewLinkWithNotes,
|
|
6
|
+
ITool,
|
|
7
|
+
Serializable,
|
|
8
|
+
} from "..";
|
|
9
|
+
import { Tag } from "../tag";
|
|
10
|
+
import type { JSONValue } from "../utils/types";
|
|
11
|
+
import type { Uuid } from "../utils/uuid";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A resource that can be synced (e.g., a calendar, project, channel).
|
|
15
|
+
* Returned by getChannels() and managed by users in the twist setup/edit modal.
|
|
16
|
+
*/
|
|
17
|
+
export type Channel = {
|
|
18
|
+
/** External ID shared across users (e.g., Google calendar ID) */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Display name shown in the UI */
|
|
21
|
+
title: string;
|
|
22
|
+
/** Optional nested channel resources (e.g., subfolders) */
|
|
23
|
+
children?: Channel[];
|
|
24
|
+
/** Per-channel link type configs. Overrides twist-level linkTypes when present. */
|
|
25
|
+
linkTypes?: LinkTypeConfig[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Describes a link type that a connector creates.
|
|
30
|
+
* Used for display in the UI (icons, labels).
|
|
31
|
+
*/
|
|
32
|
+
export type LinkTypeConfig = {
|
|
33
|
+
/** Machine-readable type identifier (e.g., "issue", "pull_request") */
|
|
34
|
+
type: string;
|
|
35
|
+
/** Human-readable label (e.g., "Issue", "Pull Request") */
|
|
36
|
+
label: string;
|
|
37
|
+
/** URL to an icon for this link type (light mode). Prefer Iconify `logos/*` URLs. */
|
|
38
|
+
logo?: string;
|
|
39
|
+
/** URL to an icon for dark mode. Use when the default logo is invisible on dark backgrounds (e.g., Iconify `simple-icons/*` with `?color=`). */
|
|
40
|
+
logoDark?: string;
|
|
41
|
+
/** URL to a monochrome icon (uses `currentColor`). Prefer Iconify `simple-icons/*` URLs without a `?color=` param. */
|
|
42
|
+
logoMono?: string;
|
|
43
|
+
/** Possible status values for this type */
|
|
44
|
+
statuses?: Array<{
|
|
45
|
+
/** Machine-readable status (e.g., "open", "done") */
|
|
46
|
+
status: string;
|
|
47
|
+
/** Human-readable label (e.g., "Open", "Done") */
|
|
48
|
+
label: string;
|
|
49
|
+
/** Tag to propagate to thread when this status is active (e.g., Tag.Done) */
|
|
50
|
+
tag?: Tag;
|
|
51
|
+
/** Whether this status represents completion (done, closed, merged, cancelled, etc.) */
|
|
52
|
+
done?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Whether this status represents the connector's "to-do" / active state.
|
|
55
|
+
* When a user adds a thread to Plot's agenda, done-status links flip to
|
|
56
|
+
* the status marked `todo: true` (e.g., Gmail's "starred", Linear's
|
|
57
|
+
* "todo") so the link widget and thread tags reflect the active state.
|
|
58
|
+
* At most one status per type should set this.
|
|
59
|
+
*/
|
|
60
|
+
todo?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Default status applied when Plot asks the connector to create a new
|
|
63
|
+
* item of this type via `Connector.onCreateLink`. Declaring at least one
|
|
64
|
+
* status with `createDefault: true` is how a link type opts in to
|
|
65
|
+
* Plot-initiated creation. At most one status per type should set this.
|
|
66
|
+
*/
|
|
67
|
+
createDefault?: boolean;
|
|
68
|
+
}>;
|
|
69
|
+
/** Whether this link type supports displaying and changing the assignee */
|
|
70
|
+
supportsAssignee?: boolean;
|
|
71
|
+
/** Default thread creation mode for this link type: 'all' | 'actionable' | 'manual' */
|
|
72
|
+
defaultCreateThreads?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Context passed to onChannelEnabled with plan-based sync hints.
|
|
77
|
+
* Connectors can use these hints to limit initial sync scope.
|
|
78
|
+
*/
|
|
79
|
+
export type SyncContext = {
|
|
80
|
+
/**
|
|
81
|
+
* Earliest date to include in initial sync, based on the user's plan.
|
|
82
|
+
*
|
|
83
|
+
* Non-calendar connectors should use this as their date filter (timeMin,
|
|
84
|
+
* created.gte, etc.) during initial sync. Calendar connectors should
|
|
85
|
+
* ignore this for API queries (to avoid missing recurring events) — the
|
|
86
|
+
* API layer filters non-recurring items automatically.
|
|
87
|
+
*
|
|
88
|
+
* Undefined when no limit applies.
|
|
89
|
+
*/
|
|
90
|
+
syncHistoryMin?: Date;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Built-in tool for managing OAuth authentication and channel resources.
|
|
95
|
+
*
|
|
96
|
+
* The Integrations tool:
|
|
97
|
+
* 1. Manages channel resources (calendars, projects, etc.) per actor
|
|
98
|
+
* 2. Returns tokens for the user who enabled sync on a channel
|
|
99
|
+
* 3. Supports per-actor auth via actAs() for write-back operations
|
|
100
|
+
* 4. Provides saveLink/saveContacts for Connectors to save data directly
|
|
101
|
+
*
|
|
102
|
+
* Connectors declare their provider, scopes, and channel lifecycle methods as
|
|
103
|
+
* class properties and methods. The Integrations tool reads these automatically.
|
|
104
|
+
* Auth and channel management is handled in the twist edit modal in Flutter.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* class CalendarConnector extends Connector<CalendarConnector> {
|
|
109
|
+
* readonly provider = AuthProvider.Google;
|
|
110
|
+
* readonly scopes = ["https://www.googleapis.com/auth/calendar"];
|
|
111
|
+
*
|
|
112
|
+
* build(build: ToolBuilder) {
|
|
113
|
+
* return {
|
|
114
|
+
* integrations: build(Integrations),
|
|
115
|
+
* };
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* async getChannels(auth: Authorization, token: AuthToken): Promise<Channel[]> {
|
|
119
|
+
* const calendars = await this.listCalendars(token);
|
|
120
|
+
* return calendars.map(c => ({ id: c.id, title: c.name }));
|
|
121
|
+
* }
|
|
122
|
+
*
|
|
123
|
+
* async onChannelEnabled(channel: Channel) {
|
|
124
|
+
* // Start syncing
|
|
125
|
+
* }
|
|
126
|
+
*
|
|
127
|
+
* async onChannelDisabled(channel: Channel) {
|
|
128
|
+
* // Stop syncing
|
|
129
|
+
* }
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export abstract class Integrations extends ITool {
|
|
134
|
+
/**
|
|
135
|
+
* Merge scopes from multiple tools, deduplicating.
|
|
136
|
+
*
|
|
137
|
+
* @param scopeArrays - Arrays of scopes to merge
|
|
138
|
+
* @returns Deduplicated array of scopes
|
|
139
|
+
*/
|
|
140
|
+
static MergeScopes(...scopeArrays: string[][]): string[] {
|
|
141
|
+
return Array.from(new Set(scopeArrays.flat()));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Retrieves an access token for a channel resource.
|
|
146
|
+
*
|
|
147
|
+
* Returns the token of the user who enabled sync on the given channel.
|
|
148
|
+
* If the channel is not enabled or the token is expired/invalid, returns null.
|
|
149
|
+
*
|
|
150
|
+
* @param channelId - The channel resource ID (e.g., calendar ID)
|
|
151
|
+
* @returns Promise resolving to the access token or null
|
|
152
|
+
*/
|
|
153
|
+
abstract get(channelId: string): Promise<AuthToken | null>;
|
|
154
|
+
/**
|
|
155
|
+
* Retrieves an access token for a channel resource.
|
|
156
|
+
*
|
|
157
|
+
* @param provider - The OAuth provider (deprecated, ignored for single-provider connectors)
|
|
158
|
+
* @param channelId - The channel resource ID (e.g., calendar ID)
|
|
159
|
+
* @returns Promise resolving to the access token or null
|
|
160
|
+
* @deprecated Use get(channelId) instead. The provider is implicit from the connector.
|
|
161
|
+
*/
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
163
|
+
abstract get(provider: AuthProvider, channelId: string): Promise<AuthToken | null>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Execute a callback as a specific actor, requesting auth if needed.
|
|
167
|
+
*
|
|
168
|
+
* If the actor has a valid token, calls the callback immediately with it.
|
|
169
|
+
* If the actor has no token, creates a private auth note in the specified
|
|
170
|
+
* activity prompting them to connect. Once they authorize, this callback fires.
|
|
171
|
+
*
|
|
172
|
+
* @param provider - The OAuth provider
|
|
173
|
+
* @param actorId - The actor to act as
|
|
174
|
+
* @param activityId - The activity to create an auth note in (if needed)
|
|
175
|
+
* @param callback - Function to call with the token
|
|
176
|
+
* @param extraArgs - Additional arguments to pass to the callback
|
|
177
|
+
*/
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
179
|
+
abstract actAs<
|
|
180
|
+
TArgs extends Serializable[],
|
|
181
|
+
TCallback extends (token: AuthToken, ...args: TArgs) => any
|
|
182
|
+
>(
|
|
183
|
+
provider: AuthProvider,
|
|
184
|
+
actorId: ActorId,
|
|
185
|
+
activityId: Uuid,
|
|
186
|
+
callback: TCallback,
|
|
187
|
+
...extraArgs: TArgs
|
|
188
|
+
): Promise<void>;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Saves a link with notes to the connector's priority.
|
|
192
|
+
*
|
|
193
|
+
* Creates a thread+link pair. The thread is a lightweight container;
|
|
194
|
+
* the link holds the external entity data (source, meta, type, status, etc.).
|
|
195
|
+
*
|
|
196
|
+
* This method is available only to Connectors (not regular Twists).
|
|
197
|
+
*
|
|
198
|
+
* @param link - The link with notes to save
|
|
199
|
+
* @returns Promise resolving to the saved thread's UUID, or null if the
|
|
200
|
+
* link was filtered out (e.g. older than the plan's sync history limit)
|
|
201
|
+
*/
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
203
|
+
abstract saveLink(link: NewLinkWithNotes): Promise<Uuid | null>;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Batch version of {@link saveLink} — saves many links in one call.
|
|
207
|
+
*
|
|
208
|
+
* Connectors syncing many items per page (e.g. calendar events, issues,
|
|
209
|
+
* messages) should prefer this over looping `saveLink`. Each `saveLink`
|
|
210
|
+
* crosses the runtime boundary and counts against the per-execution
|
|
211
|
+
* request budget; `saveLinks` collapses N saves into a single crossing.
|
|
212
|
+
*
|
|
213
|
+
* Failures on individual links DO NOT abort the batch. A bad item lands
|
|
214
|
+
* as `null` in its slot and the rest still save. This prevents one
|
|
215
|
+
* malformed record from losing an entire page of sync progress.
|
|
216
|
+
*
|
|
217
|
+
* This method is available only to Connectors (not regular Twists).
|
|
218
|
+
*
|
|
219
|
+
* @param links - Array of links with notes to save
|
|
220
|
+
* @returns Array of the same length and order as `links`. Each entry is
|
|
221
|
+
* the saved thread's UUID, or `null` if the link was filtered out
|
|
222
|
+
* (e.g. older than the plan's sync history limit) OR failed to save.
|
|
223
|
+
* The two null causes are not distinguished; the save failure is
|
|
224
|
+
* logged server-side.
|
|
225
|
+
*/
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
227
|
+
abstract saveLinks(links: NewLinkWithNotes[]): Promise<(Uuid | null)[]>;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Saves contacts to the connector's priority.
|
|
231
|
+
*
|
|
232
|
+
* @param contacts - Array of contacts to save
|
|
233
|
+
* @returns Promise resolving to the saved actors
|
|
234
|
+
*/
|
|
235
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
236
|
+
abstract saveContacts(contacts: NewContact[]): Promise<Actor[]>;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Archives links matching the given filter that were created by this connector.
|
|
240
|
+
*
|
|
241
|
+
* For each archived link's thread, if no other non-archived links remain,
|
|
242
|
+
* the thread is also archived.
|
|
243
|
+
*
|
|
244
|
+
* @param filter - Filter criteria for which links to archive
|
|
245
|
+
* @returns Promise that resolves when archiving is complete
|
|
246
|
+
*/
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
248
|
+
abstract archiveLinks(filter: ArchiveLinkFilter): Promise<void>;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Sets or clears todo status on a thread owned by this connector.
|
|
252
|
+
*
|
|
253
|
+
* @param source - The link source URL identifying the thread
|
|
254
|
+
* @param actorId - The user to set the todo for
|
|
255
|
+
* @param todo - true to mark as todo, false to clear/complete
|
|
256
|
+
* @param options - Additional options
|
|
257
|
+
* @param options.date - The todo date (when todo=true). Defaults to today.
|
|
258
|
+
*/
|
|
259
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
260
|
+
abstract setThreadToDo(
|
|
261
|
+
source: string,
|
|
262
|
+
actorId: ActorId,
|
|
263
|
+
todo: boolean,
|
|
264
|
+
options?: { date?: Date | string }
|
|
265
|
+
): Promise<void>;
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Filter criteria for archiving links.
|
|
271
|
+
* All fields are optional; only provided fields are used for matching.
|
|
272
|
+
*/
|
|
273
|
+
export type ArchiveLinkFilter = {
|
|
274
|
+
/** Filter by channel ID */
|
|
275
|
+
channelId?: string;
|
|
276
|
+
/** Filter by link type (e.g., "issue", "pull_request") */
|
|
277
|
+
type?: string;
|
|
278
|
+
/** Filter by link status (e.g., "open", "closed") */
|
|
279
|
+
status?: string;
|
|
280
|
+
/** Filter by metadata fields (uses containment matching) */
|
|
281
|
+
meta?: Record<string, JSONValue>;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Enumeration of supported OAuth providers.
|
|
286
|
+
*
|
|
287
|
+
* Each provider has different OAuth endpoints, scopes, and token formats.
|
|
288
|
+
* The Integrations tool handles the provider-specific implementation details.
|
|
289
|
+
*/
|
|
290
|
+
export enum AuthProvider {
|
|
291
|
+
/** Google OAuth provider for Google Workspace services */
|
|
292
|
+
Google = "google",
|
|
293
|
+
/** Microsoft OAuth provider for Microsoft 365 services */
|
|
294
|
+
Microsoft = "microsoft",
|
|
295
|
+
/** Notion OAuth provider for Notion workspaces */
|
|
296
|
+
Notion = "notion",
|
|
297
|
+
/** Slack OAuth provider for Slack workspaces */
|
|
298
|
+
Slack = "slack",
|
|
299
|
+
/** Atlassian OAuth provider for Jira and Confluence */
|
|
300
|
+
Atlassian = "atlassian",
|
|
301
|
+
/** Linear OAuth provider for Linear workspaces */
|
|
302
|
+
Linear = "linear",
|
|
303
|
+
/** Monday.com OAuth provider */
|
|
304
|
+
Monday = "monday",
|
|
305
|
+
/** GitHub OAuth provider for GitHub repositories and organizations */
|
|
306
|
+
GitHub = "github",
|
|
307
|
+
/** Asana OAuth provider for Asana workspaces */
|
|
308
|
+
Asana = "asana",
|
|
309
|
+
/** HubSpot OAuth provider for HubSpot CRM */
|
|
310
|
+
HubSpot = "hubspot",
|
|
311
|
+
/** Todoist OAuth provider for Todoist task management */
|
|
312
|
+
Todoist = "todoist",
|
|
313
|
+
/** Airtable OAuth provider for Airtable bases */
|
|
314
|
+
Airtable = "airtable",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Represents a completed authorization from an OAuth flow.
|
|
319
|
+
*
|
|
320
|
+
* Contains the provider, granted scopes, and the actor (contact) that was authorized.
|
|
321
|
+
* Tokens are looked up by (provider, actorId) rather than a random ID.
|
|
322
|
+
*/
|
|
323
|
+
export type Authorization = {
|
|
324
|
+
/** The OAuth provider this authorization is for */
|
|
325
|
+
provider: AuthProvider;
|
|
326
|
+
/** Array of OAuth scopes this authorization covers */
|
|
327
|
+
scopes: string[];
|
|
328
|
+
/** The external account that was authorized (e.g., the Google account) */
|
|
329
|
+
actor: Actor;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Represents a stored OAuth authentication token.
|
|
334
|
+
*
|
|
335
|
+
* Contains the actual access token and the scopes it was granted,
|
|
336
|
+
* which may be a subset of the originally requested scopes.
|
|
337
|
+
*/
|
|
338
|
+
export type AuthToken = {
|
|
339
|
+
/** The OAuth access token */
|
|
340
|
+
token: string;
|
|
341
|
+
/** Array of granted OAuth scopes */
|
|
342
|
+
scopes: string[];
|
|
343
|
+
/**
|
|
344
|
+
* Provider-specific metadata as key-value pairs.
|
|
345
|
+
*
|
|
346
|
+
* For Slack (AuthProvider.Slack):
|
|
347
|
+
* - authed_user_id: The authenticated user's Slack ID
|
|
348
|
+
* - bot_user_id: The bot user's Slack ID
|
|
349
|
+
* - team_name: The Slack workspace/team name
|
|
350
|
+
*/
|
|
351
|
+
provider?: Record<string, string>;
|
|
352
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { ITool } from "..";
|
|
2
|
+
import { type JSONValue, Serializable } from "../utils/types";
|
|
3
|
+
import { type AuthProvider, type Authorization } from "./integrations";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents an incoming webhook request.
|
|
7
|
+
*
|
|
8
|
+
* This object is passed to webhook callback functions and contains all
|
|
9
|
+
* the information about the HTTP request that triggered the webhook.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* async onWebhookReceived(request: WebhookRequest, context: any) {
|
|
14
|
+
* console.log(`${request.method} request received`);
|
|
15
|
+
* console.log("Headers:", request.headers);
|
|
16
|
+
* console.log("Query params:", request.params);
|
|
17
|
+
* console.log("Body:", request.body);
|
|
18
|
+
* console.log("Context:", context);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export type WebhookRequest = {
|
|
23
|
+
/** HTTP method of the request (GET, POST, etc.) */
|
|
24
|
+
method: string;
|
|
25
|
+
/** HTTP headers from the request */
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
/** Query string parameters from the request URL */
|
|
28
|
+
params: Record<string, string>;
|
|
29
|
+
/** Request body (parsed as JSON if applicable) */
|
|
30
|
+
body: JSONValue;
|
|
31
|
+
/** Raw request body (for signature verification) */
|
|
32
|
+
rawBody?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Built-in tool for requesting HTTP access permissions and managing webhooks.
|
|
37
|
+
*
|
|
38
|
+
* The Network tool serves two purposes:
|
|
39
|
+
* 1. Declares which URLs a twist or tool is allowed to access via HTTP/HTTPS
|
|
40
|
+
* 2. Provides webhook creation and management for receiving HTTP callbacks
|
|
41
|
+
*
|
|
42
|
+
* **IMPORTANT**: Must be requested in the Twist or Tool Init method to declare
|
|
43
|
+
* HTTP access permissions. Without requesting this tool with the appropriate URLs,
|
|
44
|
+
* all outbound HTTP requests (fetch, etc.) will be blocked.
|
|
45
|
+
*
|
|
46
|
+
* **Permission Patterns:**
|
|
47
|
+
* - `*` - Allow access to all URLs
|
|
48
|
+
* - `https://*.example.com` - Allow access to all subdomains
|
|
49
|
+
* - `https://api.example.com/*` - Allow access to all paths on the domain
|
|
50
|
+
* - `https://api.example.com/v1/*` - Allow access to specific path prefix
|
|
51
|
+
*
|
|
52
|
+
* **Webhook Characteristics:**
|
|
53
|
+
* - Persistent across worker restarts
|
|
54
|
+
* - Automatic callback routing to parent tool/twist
|
|
55
|
+
* - Support for all HTTP methods
|
|
56
|
+
* - Context preservation for callback execution
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* class MyTwist extends Twist<MyTwist> {
|
|
61
|
+
* build(build: ToolBuilder) {
|
|
62
|
+
* return {
|
|
63
|
+
* // Request HTTP access to specific APIs
|
|
64
|
+
* network: build(Network, {
|
|
65
|
+
* urls: [
|
|
66
|
+
* 'https://api.github.com/*',
|
|
67
|
+
* 'https://api.openai.com/*'
|
|
68
|
+
* ]
|
|
69
|
+
* })
|
|
70
|
+
* };
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* class CalendarTool extends Tool<CalendarTool> {
|
|
78
|
+
* build(build: ToolBuilder) {
|
|
79
|
+
* return {
|
|
80
|
+
* network: build(Network, {
|
|
81
|
+
* urls: ['https://www.googleapis.com/calendar/*']
|
|
82
|
+
* })
|
|
83
|
+
* };
|
|
84
|
+
* }
|
|
85
|
+
*
|
|
86
|
+
* async setupCalendarWebhook(calendarId: string) {
|
|
87
|
+
* // Create webhook URL that will call onCalendarEvent
|
|
88
|
+
* const webhookUrl = await this.tools.network.createWebhook(
|
|
89
|
+
* {},
|
|
90
|
+
* this.onCalendarEvent,
|
|
91
|
+
* calendarId,
|
|
92
|
+
* "google"
|
|
93
|
+
* );
|
|
94
|
+
*
|
|
95
|
+
* // Register webhook with Google Calendar API
|
|
96
|
+
* await this.registerWithGoogleCalendar(calendarId, webhookUrl);
|
|
97
|
+
*
|
|
98
|
+
* return webhookUrl;
|
|
99
|
+
* }
|
|
100
|
+
*
|
|
101
|
+
* async onCalendarEvent(request: WebhookRequest, calendarId: string, provider: string) {
|
|
102
|
+
* console.log("Calendar event received:", {
|
|
103
|
+
* method: request.method,
|
|
104
|
+
* calendarId,
|
|
105
|
+
* provider,
|
|
106
|
+
* body: request.body
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* // Process the calendar event change
|
|
110
|
+
* await this.processCalendarChange(request.body);
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* async cleanup(webhookUrl: string) {
|
|
114
|
+
* await this.tools.network.deleteWebhook(webhookUrl);
|
|
115
|
+
* }
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export abstract class Network extends ITool {
|
|
120
|
+
static readonly Options: {
|
|
121
|
+
/**
|
|
122
|
+
* All network access is blocked except the specified URLs.
|
|
123
|
+
* Wildcards (*) are supported for domains and paths.
|
|
124
|
+
*/
|
|
125
|
+
urls: string[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a new webhook endpoint.
|
|
130
|
+
*
|
|
131
|
+
* Generates a unique HTTP endpoint that will invoke the callback function
|
|
132
|
+
* when requests are received. The callback receives the WebhookRequest plus any extraArgs.
|
|
133
|
+
*
|
|
134
|
+
* **Provider-Specific Behavior:**
|
|
135
|
+
* - **Slack**: Uses provider-specific routing via team_id. Requires `authorization` parameter.
|
|
136
|
+
* - **Gmail** (Google with Gmail scopes): Returns a Google Pub/Sub topic name instead of a webhook URL.
|
|
137
|
+
* The topic name (e.g., "projects/plot-prod/topics/gmail-webhook-abc123") should be passed
|
|
138
|
+
* to the Gmail API's `users.watch` endpoint. Requires `authorization` parameter with Gmail scopes.
|
|
139
|
+
* - **Pub/Sub** (`pubsub: true`): Returns a Google Pub/Sub topic name instead of a webhook URL.
|
|
140
|
+
* Use this for services that deliver events via Pub/Sub (e.g., Google Workspace Events API).
|
|
141
|
+
* A Pub/Sub topic and push subscription are created automatically; the returned topic name
|
|
142
|
+
* can be passed to any Google API that accepts a Pub/Sub notification endpoint.
|
|
143
|
+
* - **Default**: Returns a standard webhook URL for all other cases.
|
|
144
|
+
*
|
|
145
|
+
* @param options - Webhook creation options
|
|
146
|
+
* @param options.provider - Optional provider for provider-specific webhook routing
|
|
147
|
+
* @param options.authorization - Optional authorization for provider-specific webhooks (required for Slack and Gmail)
|
|
148
|
+
* @param callback - Function receiving (request, ...extraArgs)
|
|
149
|
+
* @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
|
|
150
|
+
* @returns Promise resolving to the webhook URL, or for Gmail/Pub/Sub, a Pub/Sub topic name
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* // Pub/Sub webhook for Workspace Events API
|
|
155
|
+
* const topicName = await this.tools.network.createWebhook(
|
|
156
|
+
* { pubsub: true },
|
|
157
|
+
* this.onEventReceived,
|
|
158
|
+
* channelId
|
|
159
|
+
* );
|
|
160
|
+
* // topicName: "projects/plot-prod/topics/ps-abc123"
|
|
161
|
+
*
|
|
162
|
+
* // Pass topic name to Workspace Events API
|
|
163
|
+
* await api.createSubscription(targetResource, topicName, eventTypes);
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* // Gmail webhook - auto-detected from scopes, returns Pub/Sub topic name
|
|
169
|
+
* const topicName = await this.tools.network.createWebhook(
|
|
170
|
+
* {},
|
|
171
|
+
* this.onGmailNotification,
|
|
172
|
+
* "inbox"
|
|
173
|
+
* );
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
abstract createWebhook<
|
|
177
|
+
TArgs extends Serializable[],
|
|
178
|
+
TCallback extends (request: WebhookRequest, ...args: TArgs) => any
|
|
179
|
+
>(
|
|
180
|
+
options: {
|
|
181
|
+
provider?: AuthProvider;
|
|
182
|
+
authorization?: Authorization;
|
|
183
|
+
/** When true, creates a Google Pub/Sub topic instead of a webhook URL. */
|
|
184
|
+
pubsub?: boolean;
|
|
185
|
+
/**
|
|
186
|
+
* Controls whether the returned webhook URL runs callbacks synchronously
|
|
187
|
+
* or asynchronously.
|
|
188
|
+
*
|
|
189
|
+
* **Async (default, `async: true`)** — Plot enqueues each incoming
|
|
190
|
+
* request and immediately returns `200 { queued: true }`. A background
|
|
191
|
+
* queue consumer runs the callback with bounded concurrency. The
|
|
192
|
+
* sender never sees the callback's return value or any error thrown
|
|
193
|
+
* by it, and delivery is at-least-once (the callback must be
|
|
194
|
+
* idempotent). This is the right default for the vast majority of
|
|
195
|
+
* webhooks — service event notifications, bulk-import fan-out, etc. —
|
|
196
|
+
* because it removes ingress-path database pressure and prevents
|
|
197
|
+
* sender-side retry storms when callbacks are slow.
|
|
198
|
+
*
|
|
199
|
+
* **Sync (`async: false`)** — Plot runs the callback inline and
|
|
200
|
+
* responds with the callback's return value. Required when:
|
|
201
|
+
* - The sender reads the response body (e.g. Microsoft Graph
|
|
202
|
+
* subscription validation, which POSTs with a `validationToken` and
|
|
203
|
+
* expects the token echoed as `text/plain`).
|
|
204
|
+
* - The sender uses the HTTP status code to decide whether to retry
|
|
205
|
+
* (e.g. to surface 4xx for permanent failures).
|
|
206
|
+
* - The handler must observe throws before the sender times out.
|
|
207
|
+
*
|
|
208
|
+
* When `async: false`, a callback returning a `string` is sent back
|
|
209
|
+
* with `Content-Type: text/plain`; any other value is serialized as
|
|
210
|
+
* JSON. `undefined` / `void` yields a plain `200 OK` body.
|
|
211
|
+
*
|
|
212
|
+
* Defaults to `true`.
|
|
213
|
+
*/
|
|
214
|
+
async?: boolean;
|
|
215
|
+
},
|
|
216
|
+
callback: TCallback,
|
|
217
|
+
...extraArgs: TArgs
|
|
218
|
+
): Promise<string>;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Deletes an existing webhook endpoint.
|
|
222
|
+
*
|
|
223
|
+
* Removes the webhook endpoint and stops processing requests.
|
|
224
|
+
* Works with all webhook types (standard, Slack, and Gmail).
|
|
225
|
+
*
|
|
226
|
+
* **For Gmail webhooks:** Also deletes the associated Google Pub/Sub topic and subscription.
|
|
227
|
+
*
|
|
228
|
+
* **For Slack webhooks:** Removes the callback registration for the specific team.
|
|
229
|
+
*
|
|
230
|
+
* **For standard webhooks:** Removes the webhook endpoint. Any subsequent requests
|
|
231
|
+
* to the deleted webhook will return 404.
|
|
232
|
+
*
|
|
233
|
+
* @param url - The webhook identifier returned from `createWebhook()`.
|
|
234
|
+
* This can be a URL (standard webhooks), a Pub/Sub topic name (Gmail),
|
|
235
|
+
* or an opaque identifier (Slack). Always pass the exact value returned
|
|
236
|
+
* from `createWebhook()`.
|
|
237
|
+
* @returns Promise that resolves when the webhook is deleted
|
|
238
|
+
*/
|
|
239
|
+
abstract deleteWebhook(url: string): Promise<void>;
|
|
240
|
+
}
|