@plotday/twister 0.56.0 → 0.58.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.
Files changed (331) hide show
  1. package/README.md +53 -44
  2. package/bin/commands/create.js +9 -14
  3. package/bin/commands/create.js.map +1 -1
  4. package/bin/commands/deploy.js +2 -0
  5. package/bin/commands/deploy.js.map +1 -1
  6. package/bin/commands/generate.js +8 -5
  7. package/bin/commands/generate.js.map +1 -1
  8. package/bin/index.js +2 -2
  9. package/bin/index.js.map +1 -1
  10. package/bin/templates/AGENTS.template.md +110 -94
  11. package/bin/templates/README.template.md +36 -33
  12. package/cli/templates/AGENTS.template.md +110 -94
  13. package/cli/templates/README.template.md +36 -33
  14. package/dist/connector.d.ts +24 -17
  15. package/dist/connector.d.ts.map +1 -1
  16. package/dist/connector.js +19 -12
  17. package/dist/connector.js.map +1 -1
  18. package/dist/docs/assets/hierarchy.js +1 -1
  19. package/dist/docs/assets/navigation.js +1 -1
  20. package/dist/docs/assets/search.js +1 -1
  21. package/dist/docs/classes/index.Connector.html +66 -60
  22. package/dist/docs/classes/index.FileNotFoundError.html +2 -2
  23. package/dist/docs/classes/index.Files.html +4 -4
  24. package/dist/docs/classes/index.Imap.html +10 -10
  25. package/dist/docs/classes/index.Options.html +2 -2
  26. package/dist/docs/classes/index.Smtp.html +6 -6
  27. package/dist/docs/classes/tool.ITool.html +2 -2
  28. package/dist/docs/classes/tool.Tool.html +23 -23
  29. package/dist/docs/classes/tools_ai.AI.html +5 -5
  30. package/dist/docs/classes/tools_callbacks.Callbacks.html +8 -8
  31. package/dist/docs/classes/tools_integrations.Integrations.html +26 -12
  32. package/dist/docs/classes/tools_network.Network.html +9 -9
  33. package/dist/docs/classes/tools_plot.Plot.html +34 -33
  34. package/dist/docs/classes/tools_store.Store.html +8 -8
  35. package/dist/docs/classes/tools_tasks.Tasks.html +6 -6
  36. package/dist/docs/classes/tools_twists.Twists.html +12 -11
  37. package/dist/docs/classes/twist.Twist.html +28 -28
  38. package/dist/docs/documents/Building_Connectors.html +42 -28
  39. package/dist/docs/documents/Built-in_Tools.html +170 -67
  40. package/dist/docs/documents/CLI_Reference.html +68 -47
  41. package/dist/docs/documents/Core_Concepts.html +52 -81
  42. package/dist/docs/documents/Getting_Started.html +28 -31
  43. package/dist/docs/documents/MULTI_USER_AUTH.html +45 -0
  44. package/dist/docs/documents/Runtime_Environment.html +13 -12
  45. package/dist/docs/documents/SYNC_STRATEGIES.html +373 -0
  46. package/dist/docs/enums/plot.ActionType.html +9 -9
  47. package/dist/docs/enums/plot.ActorType.html +4 -4
  48. package/dist/docs/enums/plot.ConferencingProvider.html +6 -6
  49. package/dist/docs/enums/plot.ThemeColor.html +9 -9
  50. package/dist/docs/enums/tag.Tag.html +3 -3
  51. package/dist/docs/enums/tools_ai.AIModel.html +3 -3
  52. package/dist/docs/enums/tools_integrations.AuthProvider.html +13 -13
  53. package/dist/docs/enums/tools_plot.ContactAccess.html +2 -2
  54. package/dist/docs/enums/tools_plot.FocusAccess.html +3 -3
  55. package/dist/docs/enums/tools_plot.LinkAccess.html +3 -3
  56. package/dist/docs/enums/tools_plot.ThreadAccess.html +4 -4
  57. package/dist/docs/functions/index.Uuid.Generate.html +1 -1
  58. package/dist/docs/functions/utils_hash.quickHash.html +1 -1
  59. package/dist/docs/hierarchy.html +1 -1
  60. package/dist/docs/index.html +7 -8
  61. package/dist/docs/interfaces/tools_ai.AIRequest.html +13 -13
  62. package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
  63. package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
  64. package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
  65. package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
  66. package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
  67. package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
  68. package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
  69. package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
  70. package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
  71. package/dist/docs/interfaces/tools_twists.TwistSource.html +3 -3
  72. package/dist/docs/interfaces/utils_types.ToolShed.html +5 -5
  73. package/dist/docs/media/AGENTS.md +101 -74
  74. package/dist/docs/modules/index.html +1 -1
  75. package/dist/docs/modules/tools_integrations.html +1 -1
  76. package/dist/docs/modules.html +1 -1
  77. package/dist/docs/types/index.BooleanDef.html +2 -2
  78. package/dist/docs/types/index.CreateLinkDraft.html +9 -9
  79. package/dist/docs/types/index.ImapAddress.html +3 -3
  80. package/dist/docs/types/index.ImapConnectOptions.html +6 -6
  81. package/dist/docs/types/index.ImapFetchOptions.html +4 -4
  82. package/dist/docs/types/index.ImapFlagOperation.html +1 -1
  83. package/dist/docs/types/index.ImapMailbox.html +5 -5
  84. package/dist/docs/types/index.ImapMailboxStatus.html +7 -7
  85. package/dist/docs/types/index.ImapMessage.html +14 -14
  86. package/dist/docs/types/index.ImapSearchCriteria.html +9 -9
  87. package/dist/docs/types/index.ImapSession.html +1 -1
  88. package/dist/docs/types/index.NewSchedule.html +13 -13
  89. package/dist/docs/types/index.NewScheduleContact.html +2 -2
  90. package/dist/docs/types/index.NewScheduleOccurrence.html +1 -1
  91. package/dist/docs/types/index.NoteWriteBackResult.html +3 -3
  92. package/dist/docs/types/index.NumberDef.html +2 -2
  93. package/dist/docs/types/index.OptionDef.html +1 -1
  94. package/dist/docs/types/index.OptionalScopeGroup.html +6 -6
  95. package/dist/docs/types/index.OptionsSchema.html +1 -1
  96. package/dist/docs/types/index.ReactionCapabilities.html +1 -1
  97. package/dist/docs/types/index.ResolvedOptions.html +1 -1
  98. package/dist/docs/types/index.ResolvedRecipient.html +5 -5
  99. package/dist/docs/types/index.Schedule.html +12 -12
  100. package/dist/docs/types/index.ScheduleContact.html +2 -2
  101. package/dist/docs/types/index.ScheduleContactRole.html +1 -1
  102. package/dist/docs/types/index.ScheduleContactStatus.html +1 -1
  103. package/dist/docs/types/index.ScheduleOccurrence.html +6 -6
  104. package/dist/docs/types/index.ScheduleOccurrenceUpdate.html +1 -1
  105. package/dist/docs/types/index.ScopeConfig.html +3 -3
  106. package/dist/docs/types/index.SelectDef.html +2 -2
  107. package/dist/docs/types/index.Serializable.html +1 -1
  108. package/dist/docs/types/index.SmtpAddress.html +3 -3
  109. package/dist/docs/types/index.SmtpConnectOptions.html +7 -7
  110. package/dist/docs/types/index.SmtpMessage.html +12 -12
  111. package/dist/docs/types/index.SmtpSendResult.html +4 -4
  112. package/dist/docs/types/index.SmtpSession.html +1 -1
  113. package/dist/docs/types/index.TextDef.html +2 -2
  114. package/dist/docs/types/index.Uuid.html +1 -1
  115. package/dist/docs/types/plot.Action.html +1 -1
  116. package/dist/docs/types/plot.Actor.html +5 -5
  117. package/dist/docs/types/plot.ActorId.html +4 -4
  118. package/dist/docs/types/plot.Contact.html +4 -4
  119. package/dist/docs/types/plot.ContentType.html +1 -1
  120. package/dist/docs/types/plot.Focus.html +8 -8
  121. package/dist/docs/types/plot.FocusUpdate.html +1 -1
  122. package/dist/docs/types/plot.Link.html +17 -17
  123. package/dist/docs/types/plot.LinkUpdate.html +1 -1
  124. package/dist/docs/types/plot.NewActor.html +1 -1
  125. package/dist/docs/types/plot.NewContact.html +1 -1
  126. package/dist/docs/types/plot.NewFocus.html +1 -1
  127. package/dist/docs/types/plot.NewLink.html +5 -2
  128. package/dist/docs/types/plot.NewLinkWithNotes.html +1 -1
  129. package/dist/docs/types/plot.NewNote.html +1 -1
  130. package/dist/docs/types/plot.NewReactions.html +1 -1
  131. package/dist/docs/types/plot.NewTags.html +1 -1
  132. package/dist/docs/types/plot.NewThread.html +1 -1
  133. package/dist/docs/types/plot.NewThreadWithNotes.html +1 -1
  134. package/dist/docs/types/plot.Note.html +1 -1
  135. package/dist/docs/types/plot.NoteUpdate.html +1 -1
  136. package/dist/docs/types/plot.PlanOperation.html +1 -1
  137. package/dist/docs/types/plot.Reaction.html +3 -3
  138. package/dist/docs/types/plot.Reactions.html +1 -1
  139. package/dist/docs/types/plot.Tags.html +1 -1
  140. package/dist/docs/types/plot.Thread.html +1 -1
  141. package/dist/docs/types/plot.ThreadAccessLevel.html +1 -1
  142. package/dist/docs/types/plot.ThreadCommon.html +6 -6
  143. package/dist/docs/types/plot.ThreadFilter.html +2 -2
  144. package/dist/docs/types/plot.ThreadMeta.html +1 -1
  145. package/dist/docs/types/plot.ThreadType.html +1 -1
  146. package/dist/docs/types/plot.ThreadUpdate.html +1 -1
  147. package/dist/docs/types/plot.ThreadWithNotes.html +1 -1
  148. package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
  149. package/dist/docs/types/tools_ai.AICapabilities.html +4 -4
  150. package/dist/docs/types/tools_ai.AIMessage.html +1 -1
  151. package/dist/docs/types/tools_ai.AIOptions.html +2 -2
  152. package/dist/docs/types/tools_ai.AISource.html +1 -1
  153. package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
  154. package/dist/docs/types/tools_ai.AITool.html +1 -1
  155. package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
  156. package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
  157. package/dist/docs/types/tools_ai.AIUsage.html +5 -5
  158. package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
  159. package/dist/docs/types/tools_ai.DataContent.html +1 -1
  160. package/dist/docs/types/tools_ai.ModelPreferences.html +5 -5
  161. package/dist/docs/types/tools_callbacks.Callback.html +2 -2
  162. package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
  163. package/dist/docs/types/tools_integrations.ArchiveNotesFilter.html +5 -0
  164. package/dist/docs/types/tools_integrations.AuthToken.html +6 -5
  165. package/dist/docs/types/tools_integrations.Authorization.html +4 -4
  166. package/dist/docs/types/tools_integrations.Channel.html +6 -6
  167. package/dist/docs/types/tools_integrations.ComposeConfig.html +4 -4
  168. package/dist/docs/types/tools_integrations.ContactRoleConfig.html +5 -5
  169. package/dist/docs/types/tools_integrations.LinkTypeConfig.html +21 -21
  170. package/dist/docs/types/tools_integrations.NewCustomEmoji.html +8 -8
  171. package/dist/docs/types/tools_integrations.StatusIcon.html +1 -1
  172. package/dist/docs/types/tools_integrations.SyncContext.html +4 -4
  173. package/dist/docs/types/tools_network.WebhookRequest.html +6 -6
  174. package/dist/docs/types/tools_plot.LinkFilter.html +5 -5
  175. package/dist/docs/types/tools_plot.LinkSearchResult.html +1 -1
  176. package/dist/docs/types/tools_plot.NoteIntentHandler.html +4 -4
  177. package/dist/docs/types/tools_plot.NoteSearchResult.html +1 -1
  178. package/dist/docs/types/tools_plot.SearchOptions.html +4 -4
  179. package/dist/docs/types/tools_plot.SearchResult.html +1 -1
  180. package/dist/docs/types/tools_twists.Log.html +2 -2
  181. package/dist/docs/types/tools_twists.TwistPermissions.html +1 -1
  182. package/dist/docs/types/utils_types.BuiltInTools.html +2 -2
  183. package/dist/docs/types/utils_types.ExtractBuildReturn.html +1 -1
  184. package/dist/docs/types/utils_types.InferOptions.html +1 -1
  185. package/dist/docs/types/utils_types.InferTools.html +1 -1
  186. package/dist/docs/types/utils_types.JSONValue.html +1 -1
  187. package/dist/docs/types/utils_types.PromiseValues.html +1 -1
  188. package/dist/docs/types/utils_types.ToolBuilder.html +1 -1
  189. package/dist/docs/variables/tools_plot.SEARCH_DEFAULT_LIMIT.html +1 -1
  190. package/dist/docs/variables/tools_plot.SEARCH_MAX_LIMIT.html +1 -1
  191. package/dist/facets.d.ts +30 -0
  192. package/dist/facets.d.ts.map +1 -0
  193. package/dist/facets.js +16 -0
  194. package/dist/facets.js.map +1 -0
  195. package/dist/llm-docs/connector.d.ts +1 -1
  196. package/dist/llm-docs/connector.d.ts.map +1 -1
  197. package/dist/llm-docs/connector.js +1 -1
  198. package/dist/llm-docs/connector.js.map +1 -1
  199. package/dist/llm-docs/facets.d.ts +9 -0
  200. package/dist/llm-docs/facets.d.ts.map +1 -0
  201. package/dist/llm-docs/facets.js +8 -0
  202. package/dist/llm-docs/facets.js.map +1 -0
  203. package/dist/llm-docs/index.d.ts.map +1 -1
  204. package/dist/llm-docs/index.js +2 -0
  205. package/dist/llm-docs/index.js.map +1 -1
  206. package/dist/llm-docs/plot.d.ts +1 -1
  207. package/dist/llm-docs/plot.d.ts.map +1 -1
  208. package/dist/llm-docs/plot.js +1 -1
  209. package/dist/llm-docs/plot.js.map +1 -1
  210. package/dist/llm-docs/tool.d.ts +1 -1
  211. package/dist/llm-docs/tool.d.ts.map +1 -1
  212. package/dist/llm-docs/tool.js +1 -1
  213. package/dist/llm-docs/tool.js.map +1 -1
  214. package/dist/llm-docs/tools/ai.d.ts +1 -1
  215. package/dist/llm-docs/tools/ai.d.ts.map +1 -1
  216. package/dist/llm-docs/tools/ai.js +1 -1
  217. package/dist/llm-docs/tools/ai.js.map +1 -1
  218. package/dist/llm-docs/tools/callbacks.d.ts +1 -1
  219. package/dist/llm-docs/tools/callbacks.d.ts.map +1 -1
  220. package/dist/llm-docs/tools/callbacks.js +1 -1
  221. package/dist/llm-docs/tools/callbacks.js.map +1 -1
  222. package/dist/llm-docs/tools/files.d.ts +1 -1
  223. package/dist/llm-docs/tools/files.d.ts.map +1 -1
  224. package/dist/llm-docs/tools/files.js +1 -1
  225. package/dist/llm-docs/tools/files.js.map +1 -1
  226. package/dist/llm-docs/tools/imap.d.ts +1 -1
  227. package/dist/llm-docs/tools/imap.d.ts.map +1 -1
  228. package/dist/llm-docs/tools/imap.js +1 -1
  229. package/dist/llm-docs/tools/imap.js.map +1 -1
  230. package/dist/llm-docs/tools/integrations.d.ts +1 -1
  231. package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
  232. package/dist/llm-docs/tools/integrations.js +1 -1
  233. package/dist/llm-docs/tools/integrations.js.map +1 -1
  234. package/dist/llm-docs/tools/network.d.ts +1 -1
  235. package/dist/llm-docs/tools/network.d.ts.map +1 -1
  236. package/dist/llm-docs/tools/network.js +1 -1
  237. package/dist/llm-docs/tools/network.js.map +1 -1
  238. package/dist/llm-docs/tools/plot.d.ts +1 -1
  239. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  240. package/dist/llm-docs/tools/plot.js +1 -1
  241. package/dist/llm-docs/tools/plot.js.map +1 -1
  242. package/dist/llm-docs/tools/smtp.d.ts +1 -1
  243. package/dist/llm-docs/tools/smtp.d.ts.map +1 -1
  244. package/dist/llm-docs/tools/smtp.js +1 -1
  245. package/dist/llm-docs/tools/smtp.js.map +1 -1
  246. package/dist/llm-docs/tools/tasks.d.ts +1 -1
  247. package/dist/llm-docs/tools/tasks.d.ts.map +1 -1
  248. package/dist/llm-docs/tools/tasks.js +1 -1
  249. package/dist/llm-docs/tools/tasks.js.map +1 -1
  250. package/dist/llm-docs/tools/twists.d.ts +1 -1
  251. package/dist/llm-docs/tools/twists.d.ts.map +1 -1
  252. package/dist/llm-docs/tools/twists.js +1 -1
  253. package/dist/llm-docs/tools/twists.js.map +1 -1
  254. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  255. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  256. package/dist/llm-docs/twist-guide-template.js +1 -1
  257. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  258. package/dist/llm-docs/twist.d.ts +1 -1
  259. package/dist/llm-docs/twist.d.ts.map +1 -1
  260. package/dist/llm-docs/twist.js +1 -1
  261. package/dist/llm-docs/twist.js.map +1 -1
  262. package/dist/plot.d.ts +15 -8
  263. package/dist/plot.d.ts.map +1 -1
  264. package/dist/plot.js.map +1 -1
  265. package/dist/tool.d.ts +4 -4
  266. package/dist/tool.js +4 -4
  267. package/dist/tools/ai.d.ts +12 -13
  268. package/dist/tools/ai.d.ts.map +1 -1
  269. package/dist/tools/ai.js +8 -9
  270. package/dist/tools/ai.js.map +1 -1
  271. package/dist/tools/callbacks.d.ts +1 -1
  272. package/dist/tools/files.d.ts +2 -2
  273. package/dist/tools/imap.d.ts +1 -1
  274. package/dist/tools/imap.js +1 -1
  275. package/dist/tools/integrations.d.ts +29 -2
  276. package/dist/tools/integrations.d.ts.map +1 -1
  277. package/dist/tools/integrations.js.map +1 -1
  278. package/dist/tools/network.d.ts +5 -5
  279. package/dist/tools/plot.d.ts +42 -37
  280. package/dist/tools/plot.d.ts.map +1 -1
  281. package/dist/tools/plot.js +16 -12
  282. package/dist/tools/plot.js.map +1 -1
  283. package/dist/tools/smtp.d.ts +1 -1
  284. package/dist/tools/smtp.js +1 -1
  285. package/dist/tools/tasks.d.ts +6 -8
  286. package/dist/tools/tasks.d.ts.map +1 -1
  287. package/dist/tools/tasks.js +5 -7
  288. package/dist/tools/tasks.js.map +1 -1
  289. package/dist/tools/twists.d.ts +15 -14
  290. package/dist/tools/twists.d.ts.map +1 -1
  291. package/dist/tools/twists.js +2 -2
  292. package/dist/tools/twists.js.map +1 -1
  293. package/dist/twist-guide.d.ts +1 -1
  294. package/dist/twist-guide.d.ts.map +1 -1
  295. package/dist/twist.d.ts +2 -2
  296. package/dist/twist.js +2 -2
  297. package/package.json +6 -1
  298. package/src/connector.ts +23 -16
  299. package/src/facets.ts +40 -0
  300. package/src/llm-docs/connector.ts +1 -1
  301. package/src/llm-docs/facets.ts +8 -0
  302. package/src/llm-docs/index.ts +2 -0
  303. package/src/llm-docs/plot.ts +1 -1
  304. package/src/llm-docs/tool.ts +1 -1
  305. package/src/llm-docs/tools/ai.ts +1 -1
  306. package/src/llm-docs/tools/callbacks.ts +1 -1
  307. package/src/llm-docs/tools/files.ts +1 -1
  308. package/src/llm-docs/tools/imap.ts +1 -1
  309. package/src/llm-docs/tools/integrations.ts +1 -1
  310. package/src/llm-docs/tools/network.ts +1 -1
  311. package/src/llm-docs/tools/plot.ts +1 -1
  312. package/src/llm-docs/tools/smtp.ts +1 -1
  313. package/src/llm-docs/tools/tasks.ts +1 -1
  314. package/src/llm-docs/tools/twists.ts +1 -1
  315. package/src/llm-docs/twist-guide-template.ts +1 -1
  316. package/src/llm-docs/twist.ts +1 -1
  317. package/src/plot.ts +15 -8
  318. package/src/tool.ts +4 -4
  319. package/src/tools/ai.ts +12 -13
  320. package/src/tools/callbacks.ts +1 -1
  321. package/src/tools/files.ts +2 -2
  322. package/src/tools/imap.ts +1 -1
  323. package/src/tools/integrations.ts +34 -1
  324. package/src/tools/network.ts +5 -5
  325. package/src/tools/plot.ts +42 -37
  326. package/src/tools/smtp.ts +1 -1
  327. package/src/tools/tasks.ts +6 -8
  328. package/src/tools/twists.ts +15 -14
  329. package/src/twist.ts +2 -2
  330. package/dist/docs/media/MULTI_USER_AUTH.md +0 -116
  331. package/dist/docs/media/SYNC_STRATEGIES.md +0 -818
@@ -22,8 +22,12 @@ connectors/<name>/
22
22
  {
23
23
  "name": "@plotday/connector-<name>",
24
24
  "private": true,
25
+ "plotTwistId": "<uuid>",
25
26
  "displayName": "Human Name",
26
27
  "description": "One-line purpose statement",
28
+ "logoUrl": "https://api.iconify.design/logos/<name>-icon.svg",
29
+ "publisher": "Plot",
30
+ "publisherUrl": "https://plot.day",
27
31
  "author": "Plot <team@plot.day> (https://plot.day)",
28
32
  "license": "MIT",
29
33
  "version": "0.1.0",
@@ -37,8 +41,7 @@ connectors/<name>/
37
41
  "default": "./dist/index.js"
38
42
  }
39
43
  },
40
- "files": ["dist", "README.md", "LICENSE"],
41
- "scripts": { "build": "tsc", "clean": "rm -rf dist" },
44
+ "scripts": { "build": "tsc", "clean": "rm -rf dist", "lint": "plot lint", "deploy": "plot deploy" },
42
45
  "dependencies": { "@plotday/twister": "workspace:^" },
43
46
  "devDependencies": { "typescript": "^5.9.3" },
44
47
  "repository": { "type": "git", "url": "https://github.com/plotday/plot.git", "directory": "connectors/<name>" },
@@ -48,6 +51,7 @@ connectors/<name>/
48
51
  ```
49
52
 
50
53
  - `"@plotday/connector"` export condition resolves to TS source during workspace dev.
54
+ - `plotTwistId` is the connector's stable twist UUID — `plot create --connector` generates one; `plot deploy` reads it along with `displayName`, `description`, `logoUrl`/`logoUrlDark`, and `publisher`.
51
55
  - Add third-party SDKs to `dependencies` (e.g. `"@linear/sdk": "^72.0.0"`).
52
56
  - Add `@plotday/connector-google-contacts` as `"workspace:^"` if you sync contacts (Google connectors only).
53
57
 
@@ -68,47 +72,47 @@ Use `connectors/linear/` as the canonical reference. Minimum shape:
68
72
 
69
73
  ```typescript
70
74
  import {
71
- ActivityType, LinkType, Connector,
72
- type NewActivityWithNotes, type SyncToolOptions, type ConnectorBuilder,
75
+ Connector,
76
+ type NewLinkWithNotes, type ToolBuilder,
73
77
  } from "@plotday/twister";
74
- import { type Callback, Callbacks } from "@plotday/twister/tools/callbacks";
75
78
  import {
76
79
  AuthProvider, Integrations,
77
80
  type AuthToken, type Authorization, type Channel,
81
+ type StatusIcon, type SyncContext,
78
82
  } from "@plotday/twister/tools/integrations";
79
83
  import { Network, type WebhookRequest } from "@plotday/twister/tools/network";
80
- import { Tasks } from "@plotday/twister/tools/tasks";
81
84
 
82
85
  export class MyConnector extends Connector<MyConnector> {
83
- static readonly PROVIDER = AuthProvider.Linear;
84
- static readonly SCOPES = ["read", "write"];
85
- static readonly Options: SyncToolOptions;
86
86
  static readonly handleReplies = true; // only for bidirectional connectors
87
- declare readonly Options: SyncToolOptions;
88
87
 
89
- build(build: ConnectorBuilder) {
88
+ readonly provider = AuthProvider.Linear;
89
+ readonly scopes = ["read", "write"]; // or a ScopeConfig: { required, optional }
90
+ readonly linkTypes = [{
91
+ type: "issue",
92
+ label: "Issue",
93
+ statuses: [
94
+ { status: "unstarted", label: "To Do", icon: "todo" as StatusIcon },
95
+ { status: "completed", label: "Done", done: true, icon: "done" as StatusIcon },
96
+ ],
97
+ }];
98
+
99
+ build(build: ToolBuilder) {
90
100
  return {
91
- integrations: build(Integrations, {
92
- providers: [{
93
- provider: MyConnector.PROVIDER,
94
- scopes: MyConnector.SCOPES,
95
- getChannels: this.getChannels,
96
- onChannelEnabled: this.onChannelEnabled,
97
- onChannelDisabled: this.onChannelDisabled,
98
- }],
99
- }),
101
+ integrations: build(Integrations),
100
102
  network: build(Network, { urls: ["https://api.example.com/*"] }),
101
- callbacks: build(Callbacks),
102
- tasks: build(Tasks),
103
103
  };
104
104
  }
105
105
 
106
- async getChannels(_auth: Authorization, token: AuthToken): Promise<Channel[]> { /* list resources */ }
106
+ async getChannels(_auth: Authorization | null, token: AuthToken | null): Promise<Channel[]> { /* list resources */ }
107
107
 
108
- async onChannelEnabled(channel: Channel): Promise<void> {
109
- // 1. Store parent callback tokens via tools.callbacks.createFromParent(this.options.onItem)
110
- // 2. Queue webhook + initial sync as SEPARATE tasks via runTask() — never inline;
111
- // onChannelEnabled blocks the HTTP response until it returns.
108
+ async onChannelEnabled(channel: Channel, context?: SyncContext): Promise<void> {
109
+ // Runs inline in the HTTP request handler — only this.set()/this.get()/
110
+ // this.callback()/this.runTask() belong here. Queue webhook setup +
111
+ // initial sync as SEPARATE tasks via this.runTask(), never inline.
112
+ // Can be re-dispatched (auto-enable, recovery: context.recovering) —
113
+ // overwrite stored state unconditionally, don't skip-if-present.
114
+ // Call integrations.channelSyncCompleted(channel.id) once the initial
115
+ // backfill finishes (from the last syncBatch, not from here).
112
116
  }
113
117
 
114
118
  async onChannelDisabled(channel: Channel): Promise<void> {
@@ -119,18 +123,22 @@ export class MyConnector extends Connector<MyConnector> {
119
123
  export default MyConnector;
120
124
  ```
121
125
 
126
+ The runtime reads `provider`, `scopes`, and `linkTypes` from the class and drives OAuth and channel management automatically. The built-in `callbacks`, `store`, and `tasks` tools are always available (`this.callback()`, `this.set()`/`this.get()`/`this.clear()`, `this.runTask()`) and need no `build()` entry.
127
+
122
128
  Required private helpers: `getClient(channelId)`, `setupWebhook(id)`, `startBatchSync(id)`, `syncBatch(id)`, `transformItem(item, id, initialSync)`, `onWebhook(req, id)`. See `linear/` for the full pattern.
123
129
 
124
130
  ## Integrations (auth + channels)
125
131
 
126
- Auth is handled in the Flutter edit modal — you declare providers in `build()`, the runtime drives OAuth.
132
+ Auth is handled in the Flutter edit modal — you declare `provider` and `scopes` as class properties, the runtime drives OAuth.
127
133
 
128
134
  1. User clicks "Connect" → OAuth runs automatically.
129
135
  2. Runtime calls your `getChannels()` to list resources.
130
136
  3. User enables → `onChannelEnabled()`. User disables → `onChannelDisabled()`.
131
- 4. Read tokens via `this.tools.integrations.get(PROVIDER, channelId)`.
137
+ 4. Read tokens via `this.tools.integrations.get(channelId)`.
138
+
139
+ `AuthProvider` values: `Google`, `Microsoft`, `Notion`, `Slack`, `Atlassian`, `Linear`, `Monday`, `GitHub`, `Asana`, `HubSpot`, `Todoist`, `Airtable`.
132
140
 
133
- `AuthProvider` values: `Google`, `Microsoft`, `Notion`, `Slack`, `Atlassian`, `Linear`, `Monday`, `GitHub`, `Asana`, `HubSpot`.
141
+ `scopes` may be a flat array (all required) or a `ScopeConfig` (`{ required, optional }`) whose optional scope groups render as connect-time toggles; detect declined groups via the granted `token.scopes` and degrade gracefully (see `slack/` and `google-calendar/`). Connectors without OAuth (API keys, CalDAV credentials) omit `provider` and collect credentials via the `Options` tool with `secure: true` fields (see `attio/`, `fellow/`, `apple-calendar/`).
134
142
 
135
143
  ### Per-user auth for write-backs
136
144
 
@@ -145,7 +153,7 @@ Plot but is not dispatched — there is no instance to deliver to.
145
153
 
146
154
  ### Cross-connector auth sharing (Google)
147
155
 
148
- Merge scopes with `Integrations.MergeScopes(MyGoogleConnector.SCOPES, GoogleContacts.SCOPES)` and add `googleContacts: build(GoogleContacts)` to your `build()` return.
156
+ Set `readonly scopes = Integrations.MergeScopes(MyGoogleConnector.SCOPES, GoogleContacts.SCOPES)` and add `googleContacts: build(GoogleContacts)` to your `build()` return (see `gmail/`, `google-drive/`). Alternatively declare the contacts scopes as an optional `ScopeConfig` group so the user can decline them (see `google-calendar/`).
149
157
 
150
158
  ## Architecture
151
159
 
@@ -156,25 +164,32 @@ Connectors persist data directly via `integrations.saveLink()` (building `NewLin
156
164
  Functions are not serializable across worker boundaries. Convert to tokens, store primitives.
157
165
 
158
166
  ```typescript
167
+ import { type Callback } from "@plotday/twister/tools/callbacks";
168
+
159
169
  // ❌ WRONG — passing a function as a callback arg
160
- await this.callback(this.syncBatch, callback, ...extraArgs);
161
- // Error: Cannot create callback args: Found function at path "value[0]"
162
-
163
- // ✅ CORRECT
164
- const token = await this.tools.callbacks.createFromParent(callback, ...extraArgs);
165
- await this.set(`callback_${resourceId}`, token);
166
- const batch = await this.callback(this.syncBatch, resourceId); // primitives only
167
- await this.tools.tasks.runTask(batch);
168
-
169
- // In syncBatch:
170
- const token = await this.get<Callback>(`callback_${resourceId}`);
171
- if (!token) throw new Error(`Callback not found for ${resourceId}`);
172
- await this.tools.callbacks.run(token, item);
170
+ await this.callback(this.syncBatch, this.onItem);
171
+ // Error: Found function at path "value[0]"
172
+
173
+ // ✅ CORRECT — callback args are serializable primitives only
174
+ const batch = await this.callback(this.syncBatch, resourceId);
175
+ await this.runTask(batch);
176
+
177
+ // To invoke something later (scheduled renewals, stored continuations),
178
+ // persist the token, not the function:
179
+ const token = await this.callback(this.renewWatch, resourceId);
180
+ await this.set(`renewal_${resourceId}`, token);
181
+
182
+ // Later:
183
+ const stored = await this.get<Callback>(`renewal_${resourceId}`);
184
+ if (!stored) throw new Error(`Callback not found for ${resourceId}`);
185
+ await this.run(stored);
173
186
  ```
174
187
 
175
188
  Serializable: strings, numbers, booleans, `null`, plain objects, arrays, Dates (SuperJSON), callback tokens.
176
189
  Not serializable: functions, `undefined` (use `null`), symbols, RPC stubs, circular refs.
177
190
 
191
+ (`tools.callbacks.createFromParent()` exists for tokenizing a function handed in from a parent twist/tool — regular connectors don't need it.)
192
+
178
193
  ## Callback backward compatibility
179
194
 
180
195
  Deployed callbacks auto-upgrade to new connector versions. Signatures must stay compatible:
@@ -191,14 +206,12 @@ async syncBatch(batchNumber: number, resourceId: string, initialSync?: boolean)
191
206
  }
192
207
  ```
193
208
 
194
- For breaking changes, do migration in `preUpgrade()` (e.g. clear stale locks).
209
+ For breaking changes, do migration in `upgrade()` (called once per active instance when a new version deploys — e.g. clear stale locks, see `gmail/`, `google-calendar/`).
195
210
 
196
211
  ## Storage key conventions
197
212
 
198
213
  | Key | Purpose |
199
214
  |---|---|
200
- | `item_callback_<id>` | Token for parent's `onItem` |
201
- | `disable_callback_<id>` | Token for parent's `onChannelDisabled` |
202
215
  | `sync_state_<id>` | Current batch pagination state |
203
216
  | `sync_enabled_<id>` | Boolean tracking enabled state |
204
217
  | `webhook_id_<id>` | External webhook registration id |
@@ -207,7 +220,7 @@ For breaking changes, do migration in `preUpgrade()` (e.g. clear stale locks).
207
220
 
208
221
  ## `source` — idempotency + cross-user dedup (CRITICAL)
209
222
 
210
- `activity.source` / `link.source` is the upsert key AND the cross-user dedup key: two instances emitting the same `source` converge on a single shared thread across users (that's how two users on the same Gmail message share one thread).
223
+ `link.source` is the upsert key AND the cross-user dedup key: two instances emitting the same `source` converge on a single shared thread across users (that's how two users on the same Gmail message share one thread).
211
224
 
212
225
  **Your `source` must be globally unique for the external item, not merely unique within a user's account.** If two different users' connectors could emit the same string for different items, include a qualifier (workspace, tenant, mailbox, project).
213
226
 
@@ -233,6 +246,8 @@ Pick the format up front — retrofits require a backfill migration.
233
246
 
234
247
  **Mutable ids:** use the immutable id in `source`, store the mutable key in `meta` only (e.g. Jira issue id in `source`, issue key in `meta`).
235
248
 
249
+ **Cross-connector bundling:** `link.sources` (plural) carries additional canonical aliases — any element shared with another link's `sources` bundles the two onto one thread. E.g. calendar connectors emit an `icaluid:<iCalUID>` alias so meeting-notes connectors (Fellow, Granola) can attach to the event's thread.
250
+
236
251
  ### Attestation-based visibility
237
252
 
238
253
  Populating `thread.contacts` with recipients does NOT automatically admit them. The runtime requires each user's own connector to sync the item before they get a `thread_priority` row. Users whose sync lands first go to `thread.pending_contacts` and are promoted on the next attester's sync. You just populate `contacts` with every recipient you see — the runtime's `upsert_thread` handles the rest.
@@ -266,12 +281,17 @@ Previews (`preview` fields) always use plain text — `snippet` or truncated tit
266
281
 
267
282
  ## Sync metadata injection
268
283
 
269
- Every synced activity must include provider and channel metadata — the twist's bulk operations (e.g. archiving on disable) rely on it:
284
+ Every synced link must carry provider and channel metadata — bulk operations (e.g. `integrations.archiveLinks({ channelId })` on disable) rely on it:
270
285
 
271
286
  ```typescript
272
- activity.meta = { ...activity.meta, syncProvider: "myprovider", channelId: resourceId };
287
+ link.channelId = resourceId; // first-class field on NewLink
288
+ link.meta = { ...link.meta, syncProvider: "myprovider" };
273
289
  ```
274
290
 
291
+ ## Classifier facets (optional)
292
+
293
+ Messaging-style connectors may set `link.facets` (`format` / `automation` / `reach` from `@plotday/twister/facets`) as internal classifier signal. Set a dimension only when a heuristic is confident; leave it `null`/omitted otherwise. See `gmail/src/gmail-facets.ts` and `slack/src/slack-facets.ts`.
294
+
275
295
  ## Initial vs incremental sync (REQUIRED)
276
296
 
277
297
  Missing this causes notification spam from bulk historical imports.
@@ -282,12 +302,12 @@ Missing this causes notification spam from bulk historical imports.
282
302
  | `archived` | `false` | *omit* |
283
303
 
284
304
  ```typescript
285
- const activity = {
305
+ const link = {
286
306
  ...(initialSync ? { unread: false, archived: false } : {}),
287
307
  };
288
308
  ```
289
309
 
290
- The flag must flow from entry point through every batch to the activity-creation site.
310
+ The flag must flow from entry point through every batch to the link-creation site.
291
311
 
292
312
  - **Pattern A — store in SyncState**: include `initialSync: boolean` in your state type; set `true` in `startBatchSync`, preserve across batches, webhooks pass `false`.
293
313
  - **Pattern B — pass as callback arg**: make it the last, optional param (for backward compat) and propagate: `async syncBatch(batch: number, mode: "full"|"incremental", channelId: string, initialSync?: boolean)`. Used by connectors like Gmail.
@@ -325,11 +345,12 @@ if (taskToken) await this.set(`watch_renewal_task_${resourceId}`, taskToken);
325
345
  ## Bidirectional sync
326
346
 
327
347
  ```typescript
328
- // Update issue/task from Plot
329
- async updateIssue(activity: Activity): Promise<void> {
330
- const externalId = activity.meta?.externalId as string;
331
- const client = await this.getClient(activity.meta?.resourceId as string);
332
- await client.updateItem(externalId, { title: activity.title, done: /* ... */ });
348
+ // Write back status/assignee changes the user makes in Plot on links this
349
+ // connector created (dispatched by the runtime)
350
+ async onLinkUpdated(link: Link): Promise<void> {
351
+ const externalId = link.meta?.externalId as string;
352
+ const client = await this.getClient(link.channelId ?? (link.meta?.resourceId as string));
353
+ await client.updateItem(externalId, { title: link.title, status: /* map link.status */ });
333
354
  }
334
355
 
335
356
  // Post a comment. Return a NoteWriteBackResult with externalContent so the
@@ -395,9 +416,9 @@ Opt a link type in by adding a `compose` block to its `LinkTypeConfig`:
395
416
  type: "issue",
396
417
  label: "Issue",
397
418
  statuses: [
398
- { status: "backlog", label: "Backlog" },
399
- { status: "unstarted", label: "To Do" },
400
- { status: "completed", label: "Done", done: true },
419
+ { status: "backlog", label: "Backlog", icon: "backlog" as StatusIcon },
420
+ { status: "unstarted", label: "To Do", icon: "todo" as StatusIcon },
421
+ { status: "completed", label: "Done", done: true, icon: "done" as StatusIcon },
401
422
  ],
402
423
  compose: { status: "unstarted" }, // targets defaults to "channels"
403
424
  }
@@ -432,7 +453,7 @@ async onCreateLink(draft: CreateLinkDraft): Promise<NewLinkWithNotes | null> {
432
453
  }
433
454
  ```
434
455
 
435
- `CreateLinkDraft`: `channelId`, `type`, `status`, `title`, `noteContent`, `contacts: Actor[]`. See `twister/src/connector.ts`.
456
+ `CreateLinkDraft`: `channelId`, `type`, `status` (`null` for status-less link types), `title`, `noteContent`, `contacts: Actor[]`, plus — for `compose.targets: "contacts"`/`"addresses"` — `recipients?: ResolvedRecipient[]` (contacts pre-resolved to platform account IDs with their thread `role`) and `inviteEmails?: string[]` (free-form typed addresses). See `twister/src/connector.ts`.
436
457
 
437
458
  Resolve category statuses (`"unstarted"`, etc.) to the provider's state id yourself — the draft's status is whatever the picker showed.
438
459
 
@@ -443,11 +464,16 @@ The returned link is written with `updated_by` set to the twist, so subsequent s
443
464
  Contacts are created implicitly when you save threads/links — no `addContacts()` call, no `ContactAccess.Write`.
444
465
 
445
466
  ```typescript
446
- const author: NewContact | undefined = creator?.email
447
- ? { email: creator.email, name: creator.name, avatar: creator.avatarUrl ?? undefined }
467
+ const author: NewContact | undefined = creator
468
+ ? {
469
+ ...(creator.email ? { email: creator.email } : {}),
470
+ name: creator.name ?? "",
471
+ avatar: creator.avatarUrl ?? undefined,
472
+ source: { accountId: creator.id }, // platform identity, resolves without email
473
+ }
448
474
  : undefined;
449
475
 
450
- const activity: NewActivityWithNotes = {
476
+ const link: NewLinkWithNotes = {
451
477
  author,
452
478
  assignee: assigneeContact ?? null,
453
479
  notes: [{ author /* note-level author too */ }],
@@ -468,8 +494,8 @@ declare const Buffer: {
468
494
  ## Build & test
469
495
 
470
496
  ```bash
471
- cd public/connectors/<name> && pnpm build
472
- cd public/connectors/<name> && pnpm exec tsc --noEmit
497
+ cd connectors/<name> && pnpm build
498
+ cd connectors/<name> && pnpm exec tsc --noEmit
473
499
  pnpm install # from repo root
474
500
  ```
475
501
 
@@ -477,19 +503,20 @@ Add to `pnpm-workspace.yaml` if not already covered by a glob.
477
503
 
478
504
  ## Checklist
479
505
 
480
- - [ ] Extend `Connector<YourConnector>`; declare `PROVIDER`, `SCOPES`, `Options` (static + `declare readonly`)
481
- - [ ] `build()` declares Integrations, Network, Callbacks, Tasks (plus GoogleContacts if applicable)
506
+ - [ ] Extend `Connector<YourConnector>`; declare `readonly provider`, `readonly scopes`, `readonly linkTypes`
507
+ - [ ] `build()` declares Integrations and Network (plus GoogleContacts/Files if applicable) — `callbacks`/`store`/`tasks` are built-in
482
508
  - [ ] Set `handleReplies = true` only if bidirectional
483
509
  - [ ] For bidirectional note sync: `onNoteCreated` / `onNoteUpdated` return `NoteWriteBackResult` with `externalContent` matching what sync-in will emit for this note — no `Promise<string | void>` in new connectors
484
510
  - [ ] For bidirectional note sync: implement `onNoteUpdated` if the external supports editing (document the gap if it doesn't)
485
511
  - [ ] `onChannelEnabled` uses `runTask()` (NOT `run()`) for webhook setup and initial sync — blocks HTTP response otherwise
486
- - [ ] Convert parent callbacks to tokens with `createFromParent()`; store via `this.set()`; retrieve with `this.get<Callback>()`
512
+ - [ ] `onChannelEnabled` is idempotent (overwrites state) it re-fires on auto-enable and recovery (`context.recovering`)
513
+ - [ ] Call `integrations.channelSyncCompleted(channelId)` exactly once when the initial backfill finishes
487
514
  - [ ] Never pass functions, RPC stubs, or `undefined` to `this.callback()` — use `null`
488
- - [ ] Validate token exists before `callbacks.run()`
515
+ - [ ] Validate stored callback tokens exist before `this.run()`
489
516
  - [ ] Localhost guard in webhook setup; verify webhook signatures
490
517
  - [ ] Canonical, globally-unique `source` using immutable ids; mutable keys in `meta` only
491
518
  - [ ] `note.key` for note-level upserts
492
- - [ ] Inject `syncProvider` + `channelId` into `activity.meta`
519
+ - [ ] Set `link.channelId` and inject `syncProvider` into `link.meta`
493
520
  - [ ] `contentType: "html"` for HTML — never strip tags locally
494
521
  - [ ] `created` on notes = external timestamp, not sync time
495
522
  - [ ] `initialSync` propagated through every entry point and batch; set `unread: false, archived: false` on initial, omit on incremental
@@ -501,14 +528,14 @@ Add to `pnpm-workspace.yaml` if not already covered by a glob.
501
528
  ## Common pitfalls
502
529
 
503
530
  1. Passing functions/`undefined`/RPC stubs to `this.callback()` → use tokens + `null`.
504
- 2. Forgetting sync metadata (`syncProvider`, `channelId`) → breaks bulk archive on disable.
531
+ 2. Forgetting sync metadata (`link.channelId`, `meta.syncProvider`) → breaks bulk archive on disable.
505
532
  3. Not propagating `initialSync` through the whole pipeline → notification spam.
506
533
  4. Mutable ids in `source` (e.g. Jira issue key) → use immutable id, store key in `meta`.
507
534
  5. `source` that's only unique within one user's account → breaks cross-user dedup; add workspace/tenant/mailbox qualifier.
508
535
  6. Not breaking long loops into batches → each execution has ~1000 request limit.
509
536
  7. Missing localhost guard → webhook registration fails silently.
510
537
  8. Calling `plot.createThread()` from a connector → use `integrations.saveLink()`.
511
- 9. Breaking callback signatures → add optional params at end only; use `preUpgrade()` for breaking migrations.
538
+ 9. Breaking callback signatures → add optional params at end only; use `upgrade()` for breaking migrations.
512
539
  10. Not cleaning up on disable → orphan callbacks, webhooks, state.
513
540
  11. Two-way sync without metadata correlation → embed Plot id in external metadata to prevent race-condition duplicates (see SYNC_STRATEGIES.md §6).
514
541
  12. Stripping HTML locally → breaks encoding + loses links; use `contentType: "html"`.
@@ -533,4 +560,4 @@ Add to `pnpm-workspace.yaml` if not already covered by a glob.
533
560
  | `jira/` | ProjectConnector | Immutable vs mutable ids; comment metadata dedup |
534
561
  | `asana/` | ProjectConnector | HMAC webhook verification; section-based projects |
535
562
  | `outlook-calendar/` | CalendarConnector | Microsoft Graph; subscription management |
536
- | `google-contacts/` | Supporting | Contact sync; cross-connector `syncWithAuth()` |
563
+ | `google-contacts/` | Supporting | Contact sync; shared Google auth consumed by other connectors via `MergeScopes` |