@plotday/twister 0.38.0 → 0.40.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/README.md +22 -16
- package/bin/commands/create.js +4 -4
- package/bin/commands/create.js.map +1 -1
- package/bin/index.js +1 -1
- package/bin/index.js.map +1 -1
- package/bin/templates/AGENTS.template.md +32 -0
- package/bin/templates/README.template.md +2 -2
- package/bin/utils/bundle.js +1 -1
- package/bin/utils/bundle.js.map +1 -1
- package/cli/templates/AGENTS.template.md +32 -0
- package/cli/templates/README.template.md +2 -2
- package/dist/{source.d.ts → connector.d.ts} +24 -22
- package/dist/connector.d.ts.map +1 -0
- package/dist/{source.js → connector.js} +23 -21
- package/dist/connector.js.map +1 -0
- 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.Source.html → index.Connector.html} +27 -27
- package/dist/docs/classes/index.Options.html +1 -1
- package/dist/docs/classes/tools_ai.AI.html +7 -3
- package/dist/docs/classes/tools_callbacks.Callbacks.html +1 -1
- package/dist/docs/classes/tools_integrations.Integrations.html +15 -15
- package/dist/docs/classes/tools_network.Network.html +1 -1
- package/dist/docs/classes/tools_plot.Plot.html +23 -21
- 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 +3 -3
- package/dist/docs/classes/twist.Twist.html +1 -1
- package/dist/docs/documents/Building_Connectors.html +137 -0
- package/dist/docs/documents/Built-in_Tools.html +3 -3
- package/dist/docs/documents/Core_Concepts.html +5 -5
- package/dist/docs/documents/Getting_Started.html +1 -1
- package/dist/docs/enums/tools_ai.AIModel.html +2 -2
- package/dist/docs/enums/tools_integrations.AuthProvider.html +11 -11
- package/dist/docs/hierarchy.html +1 -1
- package/dist/docs/index.html +3 -3
- package/dist/docs/interfaces/tools_ai.AIRequest.html +11 -11
- package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
- package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
- package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
- package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
- package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
- package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
- package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
- package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
- package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
- package/dist/docs/media/AGENTS.md +82 -65
- package/dist/docs/media/MULTI_USER_AUTH.md +1 -1
- package/dist/docs/media/SYNC_STRATEGIES.md +9 -9
- package/dist/docs/modules/index.html +1 -1
- package/dist/docs/modules/tools_ai.html +1 -1
- package/dist/docs/modules.html +1 -1
- package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
- package/dist/docs/types/tools_ai.AICapabilities.html +6 -0
- package/dist/docs/types/tools_ai.AIMessage.html +1 -1
- package/dist/docs/types/tools_ai.AIOptions.html +4 -0
- package/dist/docs/types/tools_ai.AISource.html +1 -1
- package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
- package/dist/docs/types/tools_ai.AITool.html +1 -1
- package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
- package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
- package/dist/docs/types/tools_ai.AIUsage.html +5 -5
- package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
- package/dist/docs/types/tools_ai.DataContent.html +1 -1
- package/dist/docs/types/tools_ai.ModelPreferences.html +4 -4
- 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/docs/types/tools_integrations.Channel.html +5 -5
- package/dist/docs/types/tools_integrations.LinkTypeConfig.html +11 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/llm-docs/connector.d.ts +9 -0
- package/dist/llm-docs/connector.d.ts.map +1 -0
- package/dist/llm-docs/connector.js +8 -0
- package/dist/llm-docs/connector.js.map +1 -0
- package/dist/llm-docs/index.js +2 -2
- package/dist/llm-docs/index.js.map +1 -1
- package/dist/llm-docs/tools/ai.d.ts +1 -1
- package/dist/llm-docs/tools/ai.d.ts.map +1 -1
- package/dist/llm-docs/tools/ai.js +1 -1
- package/dist/llm-docs/tools/ai.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/tools/plot.d.ts +1 -1
- package/dist/llm-docs/tools/plot.d.ts.map +1 -1
- package/dist/llm-docs/tools/plot.js +1 -1
- package/dist/llm-docs/tools/plot.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/tools/ai.d.ts +18 -0
- package/dist/tools/ai.d.ts.map +1 -1
- package/dist/tools/ai.js.map +1 -1
- package/dist/tools/integrations.d.ts +16 -11
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js +3 -3
- package/dist/tools/integrations.js.map +1 -1
- package/dist/tools/plot.d.ts +4 -0
- package/dist/tools/plot.d.ts.map +1 -1
- package/dist/tools/plot.js.map +1 -1
- package/dist/twist-guide.d.ts +1 -1
- package/dist/twist-guide.d.ts.map +1 -1
- package/package.json +38 -38
- package/tsconfig.base.json +1 -1
- package/dist/docs/documents/Building_Sources.html +0 -137
- package/dist/llm-docs/source.d.ts +0 -9
- package/dist/llm-docs/source.d.ts.map +0 -1
- package/dist/llm-docs/source.js +0 -8
- package/dist/llm-docs/source.js.map +0 -1
- package/dist/source.d.ts.map +0 -1
- package/dist/source.js.map +0 -1
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Connector Development Guide
|
|
2
2
|
|
|
3
|
-
This guide covers everything needed to build a Plot
|
|
3
|
+
This guide covers everything needed to build a Plot connector correctly.
|
|
4
4
|
|
|
5
5
|
**For twist development**: See `../twister/cli/templates/AGENTS.template.md`
|
|
6
6
|
**For general navigation**: See `../AGENTS.md`
|
|
7
7
|
**For type definitions**: See `../twister/src/tools/*.ts` (comprehensive JSDoc)
|
|
8
8
|
|
|
9
|
-
## Quick Start: Complete
|
|
9
|
+
## Quick Start: Complete Connector Scaffold
|
|
10
10
|
|
|
11
|
-
Every
|
|
11
|
+
Every connector follows this structure:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
|
|
14
|
+
connectors/<name>/
|
|
15
15
|
src/
|
|
16
16
|
index.ts # Re-exports: export { default, ClassName } from "./class-file"
|
|
17
|
-
<class-name>.ts # Main
|
|
17
|
+
<class-name>.ts # Main Connector class
|
|
18
18
|
<api-name>.ts # (optional) Separate API client + transform functions
|
|
19
19
|
package.json
|
|
20
20
|
tsconfig.json
|
|
@@ -26,7 +26,7 @@ sources/<name>/
|
|
|
26
26
|
|
|
27
27
|
```json
|
|
28
28
|
{
|
|
29
|
-
"name": "@plotday/
|
|
29
|
+
"name": "@plotday/connector-<name>",
|
|
30
30
|
"displayName": "Human Name",
|
|
31
31
|
"description": "One-line purpose statement",
|
|
32
32
|
"author": "Plot <team@plot.day> (https://plot.day)",
|
|
@@ -37,7 +37,7 @@ sources/<name>/
|
|
|
37
37
|
"types": "./dist/index.d.ts",
|
|
38
38
|
"exports": {
|
|
39
39
|
".": {
|
|
40
|
-
"@plotday/
|
|
40
|
+
"@plotday/connector": "./src/index.ts",
|
|
41
41
|
"types": "./dist/index.d.ts",
|
|
42
42
|
"default": "./dist/index.js"
|
|
43
43
|
}
|
|
@@ -56,18 +56,18 @@ sources/<name>/
|
|
|
56
56
|
"repository": {
|
|
57
57
|
"type": "git",
|
|
58
58
|
"url": "https://github.com/plotday/plot.git",
|
|
59
|
-
"directory": "
|
|
59
|
+
"directory": "connectors/<name>"
|
|
60
60
|
},
|
|
61
61
|
"homepage": "https://plot.day",
|
|
62
|
-
"keywords": ["plot", "
|
|
62
|
+
"keywords": ["plot", "connector", "<name>"],
|
|
63
63
|
"publishConfig": { "access": "public" }
|
|
64
64
|
}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
**Notes:**
|
|
68
|
-
- `"@plotday/
|
|
68
|
+
- `"@plotday/connector"` export condition resolves to TypeScript source during workspace development
|
|
69
69
|
- Add third-party SDKs to `dependencies` (e.g., `"@linear/sdk": "^72.0.0"`)
|
|
70
|
-
- Add `@plotday/
|
|
70
|
+
- Add `@plotday/connector-google-contacts` as `"workspace:^"` if your connector syncs contacts (Google connectors only)
|
|
71
71
|
|
|
72
72
|
### tsconfig.json
|
|
73
73
|
|
|
@@ -85,10 +85,10 @@ sources/<name>/
|
|
|
85
85
|
### src/index.ts
|
|
86
86
|
|
|
87
87
|
```typescript
|
|
88
|
-
export { default,
|
|
88
|
+
export { default, ConnectorName } from "./connector-name";
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
##
|
|
91
|
+
## Connector Class Template
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
94
|
import {
|
|
@@ -98,8 +98,8 @@ import {
|
|
|
98
98
|
type NewActivityWithNotes,
|
|
99
99
|
type NewNote,
|
|
100
100
|
type SyncToolOptions,
|
|
101
|
-
|
|
102
|
-
type
|
|
101
|
+
Connector,
|
|
102
|
+
type ConnectorBuilder,
|
|
103
103
|
} from "@plotday/twister";
|
|
104
104
|
import type { NewContact } from "@plotday/twister/plot";
|
|
105
105
|
import { type Callback, Callbacks } from "@plotday/twister/tools/callbacks";
|
|
@@ -121,7 +121,7 @@ type SyncState = {
|
|
|
121
121
|
initialSync: boolean;
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
export class
|
|
124
|
+
export class MyConnector extends Connector<MyConnector> {
|
|
125
125
|
// 1. Static constants
|
|
126
126
|
static readonly PROVIDER = AuthProvider.Linear; // Use appropriate provider
|
|
127
127
|
static readonly SCOPES = ["read", "write"];
|
|
@@ -129,12 +129,12 @@ export class MySource extends Source<MySource> {
|
|
|
129
129
|
declare readonly Options: SyncToolOptions;
|
|
130
130
|
|
|
131
131
|
// 2. Declare dependencies
|
|
132
|
-
build(build:
|
|
132
|
+
build(build: ConnectorBuilder) {
|
|
133
133
|
return {
|
|
134
134
|
integrations: build(Integrations, {
|
|
135
135
|
providers: [{
|
|
136
|
-
provider:
|
|
137
|
-
scopes:
|
|
136
|
+
provider: MyConnector.PROVIDER,
|
|
137
|
+
scopes: MyConnector.SCOPES,
|
|
138
138
|
getChannels: this.getChannels,
|
|
139
139
|
onChannelEnabled: this.onChannelEnabled,
|
|
140
140
|
onChannelDisabled: this.onChannelDisabled,
|
|
@@ -149,7 +149,7 @@ export class MySource extends Source<MySource> {
|
|
|
149
149
|
|
|
150
150
|
// 3. Create API client using channel-based auth
|
|
151
151
|
private async getClient(channelId: string): Promise<any> {
|
|
152
|
-
const token = await this.tools.integrations.get(
|
|
152
|
+
const token = await this.tools.integrations.get(MyConnector.PROVIDER, channelId);
|
|
153
153
|
if (!token) throw new Error("No authentication token available");
|
|
154
154
|
return new SomeApiClient({ accessToken: token.token });
|
|
155
155
|
}
|
|
@@ -371,16 +371,16 @@ export class MySource extends Source<MySource> {
|
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
export default
|
|
374
|
+
export default MyConnector;
|
|
375
375
|
```
|
|
376
376
|
|
|
377
377
|
## The Integrations Pattern (Auth + Channels)
|
|
378
378
|
|
|
379
|
-
**This is how ALL authentication works.** Auth is handled in the Flutter edit modal, not in code.
|
|
379
|
+
**This is how ALL authentication works.** Auth is handled in the Flutter edit modal, not in code. Connectors declare their provider config in `build()`.
|
|
380
380
|
|
|
381
381
|
### How It Works
|
|
382
382
|
|
|
383
|
-
1.
|
|
383
|
+
1. Connector declares providers in `build()` with `getChannels`, `onChannelEnabled`, `onChannelDisabled` callbacks
|
|
384
384
|
2. User clicks "Connect" in the twist edit modal → OAuth flow happens automatically
|
|
385
385
|
3. After auth, the runtime calls your `getChannels()` to list available resources
|
|
386
386
|
4. User enables resources in the modal → `onChannelEnabled()` fires
|
|
@@ -397,7 +397,7 @@ For bidirectional sync where actions should be attributed to the acting user:
|
|
|
397
397
|
|
|
398
398
|
```typescript
|
|
399
399
|
await this.tools.integrations.actAs(
|
|
400
|
-
|
|
400
|
+
MyConnector.PROVIDER,
|
|
401
401
|
actorId, // The user who performed the action
|
|
402
402
|
activityId, // Activity to create auth prompt in (if user hasn't connected)
|
|
403
403
|
this.performWriteBack,
|
|
@@ -411,20 +411,20 @@ async performWriteBack(token: AuthToken, ...extraArgs: any[]): Promise<void> {
|
|
|
411
411
|
}
|
|
412
412
|
```
|
|
413
413
|
|
|
414
|
-
### Cross-
|
|
414
|
+
### Cross-Connector Auth Sharing (Google Connectors)
|
|
415
415
|
|
|
416
|
-
When building a Google
|
|
416
|
+
When building a Google connector that should also sync contacts, merge scopes:
|
|
417
417
|
|
|
418
418
|
```typescript
|
|
419
|
-
import GoogleContacts from "@plotday/
|
|
419
|
+
import GoogleContacts from "@plotday/connector-google-contacts";
|
|
420
420
|
|
|
421
|
-
build(build:
|
|
421
|
+
build(build: ConnectorBuilder) {
|
|
422
422
|
return {
|
|
423
423
|
integrations: build(Integrations, {
|
|
424
424
|
providers: [{
|
|
425
425
|
provider: AuthProvider.Google,
|
|
426
426
|
scopes: Integrations.MergeScopes(
|
|
427
|
-
|
|
427
|
+
MyGoogleConnector.SCOPES,
|
|
428
428
|
GoogleContacts.SCOPES
|
|
429
429
|
),
|
|
430
430
|
getChannels: this.getChannels,
|
|
@@ -438,18 +438,18 @@ build(build: SourceBuilder) {
|
|
|
438
438
|
}
|
|
439
439
|
```
|
|
440
440
|
|
|
441
|
-
## Architecture:
|
|
441
|
+
## Architecture: Connectors Save Directly
|
|
442
442
|
|
|
443
|
-
**
|
|
443
|
+
**Connectors save data directly** via `integrations.saveLink()`. Connectors build `NewLinkWithNotes` objects and save them, rather than passing them through a parent twist.
|
|
444
444
|
|
|
445
445
|
This means:
|
|
446
|
-
-
|
|
447
|
-
-
|
|
448
|
-
-
|
|
446
|
+
- Connectors request `Plot` with `ContactAccess.Write` (for contacts on threads)
|
|
447
|
+
- Connectors declare providers via `Integrations` with lifecycle callbacks
|
|
448
|
+
- Connectors call save methods directly to persist synced data
|
|
449
449
|
|
|
450
450
|
## Critical: Callback Serialization Pattern
|
|
451
451
|
|
|
452
|
-
**The #1 mistake when building
|
|
452
|
+
**The #1 mistake when building connectors is passing function references as callback arguments.** Functions cannot be serialized across worker boundaries.
|
|
453
453
|
|
|
454
454
|
### ❌ WRONG - Passing Function as Callback Argument
|
|
455
455
|
|
|
@@ -500,7 +500,7 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
500
500
|
|
|
501
501
|
## Callback Backward Compatibility
|
|
502
502
|
|
|
503
|
-
**All callbacks automatically upgrade to new
|
|
503
|
+
**All callbacks automatically upgrade to new connector versions on deployment.** You MUST maintain backward compatibility.
|
|
504
504
|
|
|
505
505
|
- ❌ Don't change function signatures (remove/reorder params, change types)
|
|
506
506
|
- ✅ Do add optional parameters at the end
|
|
@@ -533,7 +533,7 @@ async preUpgrade(): Promise<void> {
|
|
|
533
533
|
|
|
534
534
|
## Storage Key Conventions
|
|
535
535
|
|
|
536
|
-
All
|
|
536
|
+
All connectors use consistent key prefixes:
|
|
537
537
|
|
|
538
538
|
| Key Pattern | Purpose |
|
|
539
539
|
|------------|---------|
|
|
@@ -545,7 +545,7 @@ All sources use consistent key prefixes:
|
|
|
545
545
|
| `webhook_secret_<id>` | Webhook signing secret |
|
|
546
546
|
| `watch_renewal_task_<id>` | Scheduled task token for webhook renewal |
|
|
547
547
|
|
|
548
|
-
## Source URL Conventions
|
|
548
|
+
## Activity Source URL Conventions
|
|
549
549
|
|
|
550
550
|
The `activity.source` field is the idempotency key for automatic upserts. Use a canonical format:
|
|
551
551
|
|
|
@@ -554,7 +554,7 @@ The `activity.source` field is the idempotency key for automatic upserts. Use a
|
|
|
554
554
|
<provider>:<namespace>:<id> — When provider has multiple entity types
|
|
555
555
|
```
|
|
556
556
|
|
|
557
|
-
Examples from existing
|
|
557
|
+
Examples from existing connectors:
|
|
558
558
|
```
|
|
559
559
|
linear:issue:<issueId>
|
|
560
560
|
asana:task:<taskGid>
|
|
@@ -659,7 +659,7 @@ async onChannelDisabled(filter: ActivityFilter): Promise<void> {
|
|
|
659
659
|
|
|
660
660
|
## Initial vs. Incremental Sync (REQUIRED)
|
|
661
661
|
|
|
662
|
-
**Every
|
|
662
|
+
**Every connector MUST track whether it is performing an initial sync (first import) or an incremental sync (ongoing updates).** Omitting this causes notification spam from bulk historical imports.
|
|
663
663
|
|
|
664
664
|
| Field | Initial Sync | Incremental Sync | Reason |
|
|
665
665
|
|-------|-------------|------------------|--------|
|
|
@@ -682,7 +682,7 @@ The `initialSync` flag must flow from the entry point (`onChannelEnabled` / `sta
|
|
|
682
682
|
|
|
683
683
|
The scaffold's `SyncState` type includes `initialSync: boolean`. Set it to `true` in `startBatchSync`, read it in `syncBatch`, and preserve it across batches. Webhook/incremental handlers pass `false`.
|
|
684
684
|
|
|
685
|
-
**Pattern B: Pass as callback argument** (used by
|
|
685
|
+
**Pattern B: Pass as callback argument** (used by connectors like Gmail that don't store `initialSync` in state)
|
|
686
686
|
|
|
687
687
|
Pass `initialSync` as an explicit argument through `this.callback()`:
|
|
688
688
|
|
|
@@ -715,7 +715,7 @@ async syncBatch(
|
|
|
715
715
|
|
|
716
716
|
### Localhost Guard (REQUIRED)
|
|
717
717
|
|
|
718
|
-
All
|
|
718
|
+
All connectors MUST skip webhook registration in local development:
|
|
719
719
|
|
|
720
720
|
```typescript
|
|
721
721
|
const webhookUrl = await this.tools.network.createWebhook({}, this.onWebhook, resourceId);
|
|
@@ -754,7 +754,7 @@ private async scheduleWatchRenewal(resourceId: string): Promise<void> {
|
|
|
754
754
|
|
|
755
755
|
## Bidirectional Sync
|
|
756
756
|
|
|
757
|
-
For
|
|
757
|
+
For connectors that support write-backs (updating external items from Plot):
|
|
758
758
|
|
|
759
759
|
### Issue/Task Updates (`updateIssue`)
|
|
760
760
|
|
|
@@ -789,16 +789,33 @@ async addIssueComment(meta: ActivityMeta, body: string, noteId?: string): Promis
|
|
|
789
789
|
The parent twist prevents infinite loops by checking note authorship:
|
|
790
790
|
|
|
791
791
|
```typescript
|
|
792
|
-
// In the twist (not the
|
|
792
|
+
// In the twist (not the connector):
|
|
793
793
|
async onNoteCreated(note: Note): Promise<void> {
|
|
794
794
|
if (note.author.type === ActorType.Twist) return; // Prevent loops
|
|
795
795
|
// ... sync note to external service
|
|
796
796
|
}
|
|
797
797
|
```
|
|
798
798
|
|
|
799
|
+
### Default Mention on Replies
|
|
800
|
+
|
|
801
|
+
Connectors with bidirectional sync should set `thread.defaultMention: true` in their Plot tool options so replies to synced threads automatically mention the connector:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
plot: build(Plot, {
|
|
805
|
+
thread: {
|
|
806
|
+
access: ThreadAccess.Create,
|
|
807
|
+
defaultMention: true, // Replies to synced threads mention this connector by default
|
|
808
|
+
updated: this.onThreadUpdated,
|
|
809
|
+
},
|
|
810
|
+
note: { created: this.onNoteCreated },
|
|
811
|
+
}),
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
Without this, users must manually toggle the connector's mention chip on each reply. Connectors that don't process replies (e.g., read-only calendar sync) should NOT set this flag.
|
|
815
|
+
|
|
799
816
|
## Contacts Pattern
|
|
800
817
|
|
|
801
|
-
|
|
818
|
+
Connectors that sync user data should create contacts for authors and assignees:
|
|
802
819
|
|
|
803
820
|
```typescript
|
|
804
821
|
import type { NewContact } from "@plotday/twister/plot";
|
|
@@ -841,21 +858,21 @@ declare const Buffer: {
|
|
|
841
858
|
## Building and Testing
|
|
842
859
|
|
|
843
860
|
```bash
|
|
844
|
-
# Build the
|
|
845
|
-
cd public/
|
|
861
|
+
# Build the connector
|
|
862
|
+
cd public/connectors/<name> && pnpm build
|
|
846
863
|
|
|
847
864
|
# Type-check without building
|
|
848
|
-
cd public/
|
|
865
|
+
cd public/connectors/<name> && pnpm exec tsc --noEmit
|
|
849
866
|
|
|
850
867
|
# Install dependencies (from repo root)
|
|
851
868
|
pnpm install
|
|
852
869
|
```
|
|
853
870
|
|
|
854
|
-
After creating a new
|
|
871
|
+
After creating a new connector, add it to `pnpm-workspace.yaml` if not already covered by the glob pattern.
|
|
855
872
|
|
|
856
|
-
##
|
|
873
|
+
## Connector Development Checklist
|
|
857
874
|
|
|
858
|
-
- [ ] Extend `
|
|
875
|
+
- [ ] Extend `Connector<YourConnector>`
|
|
859
876
|
- [ ] Declare `static readonly PROVIDER`, `static readonly SCOPES`
|
|
860
877
|
- [ ] Declare `static readonly Options: SyncToolOptions` and `declare readonly Options: SyncToolOptions`
|
|
861
878
|
- [ ] Declare all dependencies in `build()`: Integrations, Network, Callbacks, Tasks, Plot
|
|
@@ -875,7 +892,7 @@ After creating a new source, add it to `pnpm-workspace.yaml` if not already cove
|
|
|
875
892
|
- [ ] Create contacts for authors/assignees with `NewContact`
|
|
876
893
|
- [ ] Clean up all stored state and callbacks in `stopSync()` and `onChannelDisabled()`
|
|
877
894
|
- [ ] Add `package.json` with correct structure, `tsconfig.json`, and `src/index.ts` re-export
|
|
878
|
-
- [ ] Verify the
|
|
895
|
+
- [ ] Verify the connector builds: `pnpm build`
|
|
879
896
|
|
|
880
897
|
## Common Pitfalls
|
|
881
898
|
|
|
@@ -887,7 +904,7 @@ After creating a new source, add it to `pnpm-workspace.yaml` if not already cove
|
|
|
887
904
|
6. **❌ Using mutable IDs in `source`** — Use immutable IDs (Jira issue ID, not issue key)
|
|
888
905
|
7. **❌ Not breaking loops into batches** — Each execution has ~1000 request limit
|
|
889
906
|
8. **❌ Missing localhost guard** — Webhook registration fails silently on localhost
|
|
890
|
-
9. **❌ Calling `plot.createThread()` from a
|
|
907
|
+
9. **❌ Calling `plot.createThread()` from a connector** — Connectors save data directly via `integrations.saveLink()`
|
|
891
908
|
10. **❌ Breaking callback signatures** — Old callbacks auto-upgrade; add optional params at end only
|
|
892
909
|
11. **❌ Passing `undefined` in serializable values** — Use `null` instead
|
|
893
910
|
12. **❌ Forgetting to clean up on disable** — Delete callbacks, webhooks, and stored state
|
|
@@ -897,14 +914,14 @@ After creating a new source, add it to `pnpm-workspace.yaml` if not already cove
|
|
|
897
914
|
|
|
898
915
|
## Study These Examples
|
|
899
916
|
|
|
900
|
-
|
|
|
901
|
-
|
|
902
|
-
| `linear/` |
|
|
903
|
-
| `google-calendar/` |
|
|
904
|
-
| `slack/` |
|
|
905
|
-
| `gmail/` |
|
|
906
|
-
| `google-drive/` |
|
|
907
|
-
| `jira/` |
|
|
908
|
-
| `asana/` |
|
|
909
|
-
| `outlook-calendar/` |
|
|
910
|
-
| `google-contacts/` | (Supporting) | Contact sync, cross-
|
|
917
|
+
| Connector | Category | Key Patterns |
|
|
918
|
+
|-----------|----------|-------------|
|
|
919
|
+
| `linear/` | ProjectConnector | Clean reference implementation, webhook handling, bidirectional sync |
|
|
920
|
+
| `google-calendar/` | CalendarConnector | Recurring events, RSVP write-back, watch renewal, cross-connector auth sharing |
|
|
921
|
+
| `slack/` | MessagingConnector | Team-sharded webhooks, thread model, Slack-specific auth |
|
|
922
|
+
| `gmail/` | MessagingConnector | PubSub webhooks, email thread transformation, HTML contentType, callback-arg initialSync pattern |
|
|
923
|
+
| `google-drive/` | DocumentConnector | Document comments, reply threading, file watching |
|
|
924
|
+
| `jira/` | ProjectConnector | Immutable vs mutable IDs, comment metadata for dedup |
|
|
925
|
+
| `asana/` | ProjectConnector | HMAC webhook verification, section-based projects |
|
|
926
|
+
| `outlook-calendar/` | CalendarConnector | Microsoft Graph API, subscription management |
|
|
927
|
+
| `google-contacts/` | (Supporting) | Contact sync, cross-connector `syncWithAuth()` pattern |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Multi-User Priority Auth
|
|
2
2
|
|
|
3
|
-
Twists and
|
|
3
|
+
Twists and connectors operating in shared priorities must handle authentication for multiple users. This guide covers the patterns for per-user auth and private auth activities.
|
|
4
4
|
|
|
5
5
|
## Auth Models
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sync Strategies
|
|
2
2
|
|
|
3
|
-
This guide explains good ways to build
|
|
3
|
+
This guide explains good ways to build connectors that sync other services with Plot. Choosing the right strategy depends on whether you need to update items, deduplicate them, or simply create them once.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
@@ -115,7 +115,7 @@ interface Activity {
|
|
|
115
115
|
### Example: Calendar Event Sync
|
|
116
116
|
|
|
117
117
|
```typescript
|
|
118
|
-
export default class
|
|
118
|
+
export default class GoogleCalendarConnector extends Connector<GoogleCalendarConnector> {
|
|
119
119
|
async syncEvent(event: calendar_v3.Schema$Event): Promise<void> {
|
|
120
120
|
const activity: NewActivityWithNotes = {
|
|
121
121
|
// Use the event's canonical URL as the source
|
|
@@ -168,7 +168,7 @@ export default class GoogleCalendarSource extends Source<GoogleCalendarSource> {
|
|
|
168
168
|
### Example: Task/Issue Sync
|
|
169
169
|
|
|
170
170
|
```typescript
|
|
171
|
-
export default class
|
|
171
|
+
export default class LinearConnector extends Connector<LinearConnector> {
|
|
172
172
|
async syncIssue(issue: LinearIssue): Promise<void> {
|
|
173
173
|
const activity: NewActivityWithNotes = {
|
|
174
174
|
source: issue.url, // Linear provides stable URLs
|
|
@@ -274,7 +274,7 @@ Use this strategy when:
|
|
|
274
274
|
### Example: Multiple Activities from Single Source
|
|
275
275
|
|
|
276
276
|
```typescript
|
|
277
|
-
export default class
|
|
277
|
+
export default class GmailConnector extends Connector<GmailConnector> {
|
|
278
278
|
/**
|
|
279
279
|
* Creates separate activities for email threads and individual messages.
|
|
280
280
|
* One email thread can have multiple Plot activities.
|
|
@@ -505,7 +505,7 @@ When syncing activities from external systems, it's critical to distinguish betw
|
|
|
505
505
|
|
|
506
506
|
### The `initialSync` Flag Pattern
|
|
507
507
|
|
|
508
|
-
All sync-based
|
|
508
|
+
All sync-based connectors should track whether they're performing an initial sync or incremental sync:
|
|
509
509
|
|
|
510
510
|
| Field | Initial Sync | Incremental Sync | Reason |
|
|
511
511
|
|-------|--------------|------------------|---------|
|
|
@@ -588,7 +588,7 @@ async syncBatch(
|
|
|
588
588
|
**Initial sync (first import):**
|
|
589
589
|
- Activities are **unarchived** (`archived: false`) - gives user a fresh start
|
|
590
590
|
- Activities are marked as **read** (`unread: false`) - prevents notification spam from bulk historical imports
|
|
591
|
-
- Use case: When user first installs the
|
|
591
|
+
- Use case: When user first installs the connector or reconnects after disconnection
|
|
592
592
|
|
|
593
593
|
**Incremental sync (ongoing updates):**
|
|
594
594
|
- New activities appear as **unread** (`unread: true`) - user gets notified of new items
|
|
@@ -694,9 +694,9 @@ if (existingId) {
|
|
|
694
694
|
|
|
695
695
|
## Best Practices
|
|
696
696
|
|
|
697
|
-
### 1. Be Consistent Within a
|
|
697
|
+
### 1. Be Consistent Within a Connector
|
|
698
698
|
|
|
699
|
-
Choose one strategy per
|
|
699
|
+
Choose one strategy per connector and stick with it. Mixing strategies in the same connector can lead to confusion and bugs.
|
|
700
700
|
|
|
701
701
|
### 2. Use Descriptive Keys
|
|
702
702
|
|
|
@@ -805,4 +805,4 @@ For more information:
|
|
|
805
805
|
|
|
806
806
|
- [Core Concepts](CORE_CONCEPTS.md) - Understanding activities, notes, and priorities
|
|
807
807
|
- [Tools Guide](TOOLS_GUIDE.md) - Complete reference for the Plot tool
|
|
808
|
-
- [Building
|
|
808
|
+
- [Building Connectors](BUILDING_CONNECTORS.md) - Creating external service integrations
|