@plotday/twister 0.38.0 → 0.39.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 (111) hide show
  1. package/README.md +22 -16
  2. package/bin/commands/create.js +4 -4
  3. package/bin/commands/create.js.map +1 -1
  4. package/bin/index.js +1 -1
  5. package/bin/index.js.map +1 -1
  6. package/bin/templates/AGENTS.template.md +32 -0
  7. package/bin/templates/README.template.md +2 -2
  8. package/bin/utils/bundle.js +1 -1
  9. package/bin/utils/bundle.js.map +1 -1
  10. package/cli/templates/AGENTS.template.md +32 -0
  11. package/cli/templates/README.template.md +2 -2
  12. package/dist/{source.d.ts → connector.d.ts} +24 -22
  13. package/dist/connector.d.ts.map +1 -0
  14. package/dist/{source.js → connector.js} +23 -21
  15. package/dist/connector.js.map +1 -0
  16. package/dist/docs/assets/hierarchy.js +1 -1
  17. package/dist/docs/assets/navigation.js +1 -1
  18. package/dist/docs/assets/search.js +1 -1
  19. package/dist/docs/classes/{index.Source.html → index.Connector.html} +27 -27
  20. package/dist/docs/classes/index.Options.html +1 -1
  21. package/dist/docs/classes/tools_ai.AI.html +7 -3
  22. package/dist/docs/classes/tools_callbacks.Callbacks.html +1 -1
  23. package/dist/docs/classes/tools_integrations.Integrations.html +11 -11
  24. package/dist/docs/classes/tools_network.Network.html +1 -1
  25. package/dist/docs/classes/tools_plot.Plot.html +23 -21
  26. package/dist/docs/classes/tools_store.Store.html +1 -1
  27. package/dist/docs/classes/tools_tasks.Tasks.html +1 -1
  28. package/dist/docs/classes/tools_twists.Twists.html +3 -3
  29. package/dist/docs/classes/twist.Twist.html +1 -1
  30. package/dist/docs/documents/Building_Connectors.html +137 -0
  31. package/dist/docs/documents/Built-in_Tools.html +3 -3
  32. package/dist/docs/documents/Core_Concepts.html +5 -5
  33. package/dist/docs/documents/Getting_Started.html +1 -1
  34. package/dist/docs/enums/tools_ai.AIModel.html +2 -2
  35. package/dist/docs/hierarchy.html +1 -1
  36. package/dist/docs/index.html +3 -3
  37. package/dist/docs/interfaces/tools_ai.AIRequest.html +11 -11
  38. package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
  39. package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
  40. package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
  41. package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
  42. package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
  43. package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
  44. package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
  45. package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
  46. package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
  47. package/dist/docs/media/AGENTS.md +82 -65
  48. package/dist/docs/media/MULTI_USER_AUTH.md +1 -1
  49. package/dist/docs/media/SYNC_STRATEGIES.md +9 -9
  50. package/dist/docs/modules/index.html +1 -1
  51. package/dist/docs/modules/tools_ai.html +1 -1
  52. package/dist/docs/modules.html +1 -1
  53. package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
  54. package/dist/docs/types/tools_ai.AICapabilities.html +6 -0
  55. package/dist/docs/types/tools_ai.AIMessage.html +1 -1
  56. package/dist/docs/types/tools_ai.AIOptions.html +4 -0
  57. package/dist/docs/types/tools_ai.AISource.html +1 -1
  58. package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
  59. package/dist/docs/types/tools_ai.AITool.html +1 -1
  60. package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
  61. package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
  62. package/dist/docs/types/tools_ai.AIUsage.html +5 -5
  63. package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
  64. package/dist/docs/types/tools_ai.DataContent.html +1 -1
  65. package/dist/docs/types/tools_ai.ModelPreferences.html +4 -4
  66. package/dist/docs/types/tools_integrations.LinkTypeConfig.html +1 -1
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +1 -1
  70. package/dist/index.js.map +1 -1
  71. package/dist/llm-docs/connector.d.ts +9 -0
  72. package/dist/llm-docs/connector.d.ts.map +1 -0
  73. package/dist/llm-docs/connector.js +8 -0
  74. package/dist/llm-docs/connector.js.map +1 -0
  75. package/dist/llm-docs/index.js +2 -2
  76. package/dist/llm-docs/index.js.map +1 -1
  77. package/dist/llm-docs/tools/ai.d.ts +1 -1
  78. package/dist/llm-docs/tools/ai.d.ts.map +1 -1
  79. package/dist/llm-docs/tools/ai.js +1 -1
  80. package/dist/llm-docs/tools/ai.js.map +1 -1
  81. package/dist/llm-docs/tools/integrations.d.ts +1 -1
  82. package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
  83. package/dist/llm-docs/tools/integrations.js +1 -1
  84. package/dist/llm-docs/tools/integrations.js.map +1 -1
  85. package/dist/llm-docs/tools/plot.d.ts +1 -1
  86. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  87. package/dist/llm-docs/tools/plot.js +1 -1
  88. package/dist/llm-docs/tools/plot.js.map +1 -1
  89. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  90. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  91. package/dist/llm-docs/twist-guide-template.js +1 -1
  92. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  93. package/dist/tools/ai.d.ts +18 -0
  94. package/dist/tools/ai.d.ts.map +1 -1
  95. package/dist/tools/ai.js.map +1 -1
  96. package/dist/tools/integrations.d.ts +11 -11
  97. package/dist/tools/integrations.js +3 -3
  98. package/dist/tools/plot.d.ts +4 -0
  99. package/dist/tools/plot.d.ts.map +1 -1
  100. package/dist/tools/plot.js.map +1 -1
  101. package/dist/twist-guide.d.ts +1 -1
  102. package/dist/twist-guide.d.ts.map +1 -1
  103. package/package.json +38 -38
  104. package/tsconfig.base.json +1 -1
  105. package/dist/docs/documents/Building_Sources.html +0 -137
  106. package/dist/llm-docs/source.d.ts +0 -9
  107. package/dist/llm-docs/source.d.ts.map +0 -1
  108. package/dist/llm-docs/source.js +0 -8
  109. package/dist/llm-docs/source.js.map +0 -1
  110. package/dist/source.d.ts.map +0 -1
  111. package/dist/source.js.map +0 -1
@@ -1,137 +0,0 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>Building Sources | Creating Plot Twists</title><link rel="icon" href="../assets/favicon.svg" type="image/svg+xml"/><meta name="description" content="Documentation for Creating Plot Twists"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="/" class="title">Creating Plot Twists</a><div id="tsd-toolbar-links"><a href="https://plot.day">Plot</a><a href="https://github.com/plotday/plot">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister">NPM</a></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">Building Sources</a></li></ul></div><div class="tsd-panel tsd-typography"><h1 id="building-sources" class="tsd-anchor-link">Building Sources<a href="#building-sources" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h1><p>Sources connect Plot to external services like Google Calendar, Slack, Linear, and more. They sync data into Plot and optionally support bidirectional updates. This guide covers everything you need to know about building sources.</p>
2
- <h2 id="table-of-contents" class="tsd-anchor-link">Table of Contents<a href="#table-of-contents" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
3
- <li><a href="#sources-vs-twists">Sources vs Twists</a></li>
4
- <li><a href="#source-structure">Source Structure</a></li>
5
- <li><a href="#oauth-and-channel-lifecycle">OAuth and Channel Lifecycle</a></li>
6
- <li><a href="#data-sync">Data Sync</a></li>
7
- <li><a href="#batch-processing">Batch Processing</a></li>
8
- <li><a href="#complete-example">Complete Example</a></li>
9
- <li><a href="#best-practices">Best Practices</a></li>
10
- </ul>
11
- <hr>
12
- <h2 id="sources-vs-twists" class="tsd-anchor-link">Sources vs Twists<a href="#sources-vs-twists" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><table>
13
- <thead>
14
- <tr>
15
- <th></th>
16
- <th>Sources</th>
17
- <th>Twists</th>
18
- </tr>
19
- </thead>
20
- <tbody>
21
- <tr>
22
- <td><strong>Purpose</strong></td>
23
- <td>Sync data from external services</td>
24
- <td>Implement opinionated workflows</td>
25
- </tr>
26
- <tr>
27
- <td><strong>Base class</strong></td>
28
- <td><code>Source&lt;T&gt;</code> (extends <code>Twist&lt;T&gt;</code>)</td>
29
- <td><code>Twist&lt;T&gt;</code></td>
30
- </tr>
31
- <tr>
32
- <td><strong>Auth</strong></td>
33
- <td>OAuth via <code>Integrations</code> with channel lifecycle</td>
34
- <td>Optional</td>
35
- </tr>
36
- <tr>
37
- <td><strong>Data flow</strong></td>
38
- <td>External service -&gt; Plot (and optionally back)</td>
39
- <td>Internal logic, orchestration</td>
40
- </tr>
41
- <tr>
42
- <td><strong>Examples</strong></td>
43
- <td>Google Calendar, Slack, Linear, Jira</td>
44
- <td>Task automation, AI assistants</td>
45
- </tr>
46
- </tbody>
47
- </table>
48
- <p><strong>Build a Source</strong> when you need to integrate an external service — syncing calendars, issues, messages, etc.</p>
49
- <p><strong>Build a Twist</strong> when you need workflow logic that doesn't require external service integration, or when you want to orchestrate multiple sources.</p>
50
- <hr>
51
- <h2 id="source-structure" class="tsd-anchor-link">Source Structure<a href="#source-structure" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Sources extend the <code>Source&lt;T&gt;</code> base class and declare dependencies using <code>SourceBuilder</code>:</p>
52
- <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">Source</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">SourceBuilder</span><span class="hl-1">,</span><br/><span class="hl-1">} </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">AuthProvider</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">AuthToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Authorization</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Channel</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">Integrations</span><span class="hl-1">,</span><br/><span class="hl-1">} </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Network</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/network&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Plot</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/plot&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Tasks</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/tasks&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Callbacks</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/callbacks&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-0">default</span><span class="hl-1"> </span><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">MySource</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Source</span><span class="hl-1">&lt;</span><span class="hl-5">MySource</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">static</span><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">PROVIDER</span><span class="hl-1"> = </span><span class="hl-2">AuthProvider</span><span class="hl-1">.</span><span class="hl-2">Linear</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">static</span><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">SCOPES</span><span class="hl-1"> = [</span><span class="hl-3">&quot;read&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;write&quot;</span><span class="hl-1">];</span><br/><br/><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-5">SourceBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">integrations:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Integrations</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">providers:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">provider:</span><span class="hl-1"> </span><span class="hl-2">MySource</span><span class="hl-1">.</span><span class="hl-8">PROVIDER</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">scopes:</span><span class="hl-1"> </span><span class="hl-2">MySource</span><span class="hl-1">.</span><span class="hl-8">SCOPES</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">getChannels:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">getChannels</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">onChannelEnabled:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onChannelEnabled</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">onChannelDisabled:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onChannelDisabled</span><span class="hl-1">,</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1"> }),</span><br/><span class="hl-1"> </span><span class="hl-2">network:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Network</span><span class="hl-1">, { </span><span class="hl-2">urls:</span><span class="hl-1"> [</span><span class="hl-3">&quot;https://api.example.com/*&quot;</span><span class="hl-1">] }),</span><br/><span class="hl-1"> </span><span class="hl-2">plot:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Plot</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">tasks:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Tasks</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">callbacks:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Callbacks</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// ... lifecycle methods below</span><br/><span class="hl-1">}</span>
53
- </code><button type="button">Copy</button></pre>
54
-
55
- <h3 id="package-structure" class="tsd-anchor-link">Package Structure<a href="#package-structure" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code><span class="hl-2">sources</span><span class="hl-1">/</span><span class="hl-2">my</span><span class="hl-1">-</span><span class="hl-2">source</span><span class="hl-1">/</span><br/><span class="hl-1"> </span><span class="hl-2">src</span><span class="hl-1">/</span><br/><span class="hl-1"> </span><span class="hl-2">index</span><span class="hl-1">.</span><span class="hl-2">ts</span><span class="hl-1"> # </span><span class="hl-2">Re</span><span class="hl-1">-</span><span class="hl-15">exports</span><span class="hl-1">: </span><span class="hl-0">export</span><span class="hl-1"> { </span><span class="hl-0">default</span><span class="hl-1">, </span><span class="hl-2">MySource</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;./my-source&quot;</span><br/><span class="hl-1"> </span><span class="hl-2">my</span><span class="hl-1">-</span><span class="hl-2">source</span><span class="hl-1">.</span><span class="hl-2">ts</span><span class="hl-1"> # </span><span class="hl-2">Main</span><span class="hl-1"> </span><span class="hl-2">Source</span><span class="hl-1"> </span><span class="hl-4">class</span><br/><span class="hl-1"> </span><span class="hl-5">package</span><span class="hl-1">.</span><span class="hl-5">json</span><br/><span class="hl-1"> </span><span class="hl-5">tsconfig</span><span class="hl-1">.</span><span class="hl-5">json</span>
56
- </code><button>Copy</button></pre>
57
-
58
- <hr>
59
- <h2 id="oauth-and-channel-lifecycle" class="tsd-anchor-link">OAuth and Channel Lifecycle<a href="#oauth-and-channel-lifecycle" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Sources use the Integrations tool for OAuth. Auth is handled automatically in the Flutter edit modal — you don't need to build UI for it.</p>
60
- <h3 id="how-it-works" class="tsd-anchor-link">How It Works<a href="#how-it-works" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><ol>
61
- <li>Source declares providers in <code>build()</code> with <code>getChannels</code>, <code>onChannelEnabled</code>, <code>onChannelDisabled</code> callbacks</li>
62
- <li>User clicks &quot;Connect&quot; in the twist edit modal -&gt; OAuth flow happens automatically</li>
63
- <li>After auth, the runtime calls <code>getChannels()</code> to list available resources</li>
64
- <li>User enables/disables resources in the modal</li>
65
- </ol>
66
- <h3 id="getchannels" class="tsd-anchor-link">getChannels<a href="#getchannels" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Return available resources after authentication:</p>
67
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">getChannels</span><span class="hl-1">(</span><span class="hl-2">_auth</span><span class="hl-1">: </span><span class="hl-2">Authorization</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">: </span><span class="hl-2">AuthToken</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-2">Channel</span><span class="hl-1">[]&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">client</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">ApiClient</span><span class="hl-1">({ </span><span class="hl-2">accessToken:</span><span class="hl-1"> </span><span class="hl-2">token</span><span class="hl-1">.</span><span class="hl-2">token</span><span class="hl-1"> });</span><br/><span class="hl-1"> const </span><span class="hl-2">resources</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">listResources</span><span class="hl-1">();</span><br/><span class="hl-1"> return resources.map(</span><span class="hl-2">r</span><span class="hl-1"> </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({ </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">r</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">r</span><span class="hl-1">.</span><span class="hl-2">name</span><span class="hl-1"> }));</span><br/><span class="hl-1">}</span>
68
- </code><button type="button">Copy</button></pre>
69
-
70
- <h3 id="onchannelenabled" class="tsd-anchor-link">onChannelEnabled<a href="#onchannelenabled" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Called when the user enables a resource. Set up syncing:</p>
71
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onChannelEnabled</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">: </span><span class="hl-2">Channel</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> await this.setupWebhook(channel.id);</span><br/><span class="hl-1"> await this.startBatchSync(channel.id);</span><br/><span class="hl-1">}</span>
72
- </code><button type="button">Copy</button></pre>
73
-
74
- <h3 id="onchanneldisabled" class="tsd-anchor-link">onChannelDisabled<a href="#onchanneldisabled" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Called when the user disables a resource. Clean up:</p>
75
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onChannelDisabled</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">: </span><span class="hl-2">Channel</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Remove webhook</span><br/><span class="hl-1"> const </span><span class="hl-2">webhookId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">`webhook_id_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (</span><span class="hl-2">webhookId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">client</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">deleteWebhook</span><span class="hl-1">(</span><span class="hl-2">webhookId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-3">`webhook_id_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Clean up stored state</span><br/><span class="hl-1"> await this.clear(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-2">);</span><br/><span class="hl-2">}</span>
76
- </code><button type="button">Copy</button></pre>
77
-
78
- <h3 id="getting-auth-tokens" class="tsd-anchor-link">Getting Auth Tokens<a href="#getting-auth-tokens" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Retrieve tokens for API calls using the channel ID:</p>
79
- <pre><code class="typescript"><span class="hl-2">private</span><span class="hl-1"> </span><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-2">ApiClient</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-2">MySource</span><span class="hl-1">.</span><span class="hl-8">PROVIDER</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (!</span><span class="hl-2">token</span><span class="hl-1">) throw new </span><span class="hl-6">Error</span><span class="hl-1">(</span><span class="hl-3">&quot;No authentication token available&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> return new </span><span class="hl-6">ApiClient</span><span class="hl-1">({ </span><span class="hl-2">accessToken</span><span class="hl-1">: </span><span class="hl-2">token</span><span class="hl-1">.</span><span class="hl-2">token</span><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
80
- </code><button type="button">Copy</button></pre>
81
-
82
- <hr>
83
- <h2 id="data-sync" class="tsd-anchor-link">Data Sync<a href="#data-sync" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Sources sync data using <code>Activity.source</code> and <code>Note.key</code> for automatic upserts (no manual ID tracking needed).</p>
84
- <h3 id="transforming-external-items" class="tsd-anchor-link">Transforming External Items<a href="#transforming-external-items" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-2">private</span><span class="hl-1"> </span><span class="hl-6">transformItem</span><span class="hl-1">(</span><span class="hl-2">item</span><span class="hl-1">: </span><span class="hl-2">any</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">, </span><span class="hl-2">initialSync</span><span class="hl-1">: </span><span class="hl-2">boolean</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">`myprovider:item:</span><span class="hl-4">${</span><span class="hl-2">item</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-7">// Canonical source for deduplication</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">item</span><span class="hl-1">.</span><span class="hl-2">title</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">meta:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">externalId:</span><span class="hl-1"> </span><span class="hl-2">item</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">syncProvider:</span><span class="hl-1"> </span><span class="hl-3">&quot;myprovider&quot;</span><span class="hl-1">, </span><span class="hl-7">// Required for bulk operations</span><br/><span class="hl-1"> </span><span class="hl-2">channelId</span><span class="hl-1">, </span><span class="hl-7">// Required for bulk operations</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-7">// Enables note-level upserts</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">item</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1"> || </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">contentType:</span><span class="hl-1"> </span><span class="hl-2">item</span><span class="hl-1">.</span><span class="hl-2">descriptionHtml</span><span class="hl-1"> ? </span><span class="hl-3">&quot;html&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> : </span><span class="hl-3">&quot;text&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1"> ...(</span><span class="hl-2">initialSync</span><span class="hl-1"> ? { </span><span class="hl-2">unread:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> } : {}), </span><span class="hl-7">// Mark read on initial sync</span><br/><span class="hl-1"> ...(</span><span class="hl-2">initialSync</span><span class="hl-1"> ? { </span><span class="hl-2">archived:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> } : {}), </span><span class="hl-7">// Unarchive on initial sync</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
85
- </code><button type="button">Copy</button></pre>
86
-
87
- <h3 id="initial-vs-incremental-sync" class="tsd-anchor-link">Initial vs Incremental Sync<a href="#initial-vs-incremental-sync" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>All sources <strong>must</strong> distinguish between initial sync (first import) and incremental sync (ongoing updates):</p>
88
- <table>
89
- <thead>
90
- <tr>
91
- <th>Field</th>
92
- <th>Initial Sync</th>
93
- <th>Incremental Sync</th>
94
- <th>Reason</th>
95
- </tr>
96
- </thead>
97
- <tbody>
98
- <tr>
99
- <td><code>unread</code></td>
100
- <td><code>false</code></td>
101
- <td><em>omit</em></td>
102
- <td>Avoid notification spam from historical imports</td>
103
- </tr>
104
- <tr>
105
- <td><code>archived</code></td>
106
- <td><code>false</code></td>
107
- <td><em>omit</em></td>
108
- <td>Unarchive on install, preserve user choice on updates</td>
109
- </tr>
110
- </tbody>
111
- </table>
112
- <p>See <a href="../media/SYNC_STRATEGIES.md">Sync Strategies</a> for detailed patterns on deduplication, upserts, and tag management.</p>
113
- <hr>
114
- <h2 id="batch-processing" class="tsd-anchor-link">Batch Processing<a href="#batch-processing" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Sources run in an ephemeral environment with ~1000 requests per execution. Break long operations into batches using <code>runTask()</code>, which creates a new execution with fresh request limits.</p>
115
- <pre><code class="typescript"><span class="hl-2">private</span><span class="hl-1"> </span><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">startBatchSync</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> await this.set(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-15">cursor</span><span class="hl-1">: </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">batchNumber</span><span class="hl-1">: </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">initialSync</span><span class="hl-1">: </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> const </span><span class="hl-2">batchCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> await this.tools.tasks.runTask(batchCallback);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">private</span><span class="hl-1"> </span><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">state</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (!</span><span class="hl-2">state</span><span class="hl-1">) return;</span><br/><br/><span class="hl-1"> const </span><span class="hl-2">client</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> const </span><span class="hl-2">result</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">listItems</span><span class="hl-1">({ </span><span class="hl-2">cursor:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">cursor</span><span class="hl-1">, </span><span class="hl-2">limit:</span><span class="hl-1"> </span><span class="hl-14">50</span><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-6">for</span><span class="hl-1"> (</span><span class="hl-2">const</span><span class="hl-1"> </span><span class="hl-2">item</span><span class="hl-1"> </span><span class="hl-2">of</span><span class="hl-1"> result.items) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">activity</span><span class="hl-1"> = </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">transformItem</span><span class="hl-1">(</span><span class="hl-2">item</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">, </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">initialSync</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">(</span><span class="hl-2">activity</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (result.nextCursor) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">cursor:</span><span class="hl-1"> </span><span class="hl-2">result</span><span class="hl-1">.</span><span class="hl-2">nextCursor</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">batchNumber:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">batchNumber</span><span class="hl-1"> + </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">initialSync:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">initialSync</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">nextBatch</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">tasks</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">nextBatch</span><span class="hl-1">);</span><br/><span class="hl-1"> } else {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
116
- </code><button type="button">Copy</button></pre>
117
-
118
- <hr>
119
- <h2 id="complete-example" class="tsd-anchor-link">Complete Example<a href="#complete-example" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>A minimal source that syncs issues from an external service:</p>
120
- <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">LinkType</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">Source</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">SourceBuilder</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">SyncToolOptions</span><span class="hl-1">,</span><br/><span class="hl-1">} </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">AuthProvider</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">AuthToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Authorization</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Channel</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">Integrations</span><span class="hl-1">,</span><br/><span class="hl-1">} </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Network</span><span class="hl-1">, </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">WebhookRequest</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/network&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Plot</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/plot&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Tasks</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/tasks&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Callbacks</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/callbacks&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-0">default</span><span class="hl-1"> </span><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">IssueSource</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Source</span><span class="hl-1">&lt;</span><span class="hl-5">IssueSource</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">static</span><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">PROVIDER</span><span class="hl-1"> = </span><span class="hl-2">AuthProvider</span><span class="hl-1">.</span><span class="hl-2">Linear</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">static</span><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">SCOPES</span><span class="hl-1"> = [</span><span class="hl-3">&quot;read&quot;</span><span class="hl-1">];</span><br/><span class="hl-1"> </span><span class="hl-4">static</span><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">Options</span><span class="hl-1">: </span><span class="hl-5">SyncToolOptions</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">declare</span><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">Options</span><span class="hl-1">: </span><span class="hl-5">SyncToolOptions</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-5">SourceBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">integrations:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Integrations</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">providers:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">provider:</span><span class="hl-1"> </span><span class="hl-2">IssueSource</span><span class="hl-1">.</span><span class="hl-8">PROVIDER</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">scopes:</span><span class="hl-1"> </span><span class="hl-2">IssueSource</span><span class="hl-1">.</span><span class="hl-8">SCOPES</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">getChannels:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">getChannels</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">onChannelEnabled:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onChannelEnabled</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">onChannelDisabled:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onChannelDisabled</span><span class="hl-1">,</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1"> }),</span><br/><span class="hl-1"> </span><span class="hl-2">network:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Network</span><span class="hl-1">, { </span><span class="hl-2">urls:</span><span class="hl-1"> [</span><span class="hl-3">&quot;https://api.linear.app/*&quot;</span><span class="hl-1">] }),</span><br/><span class="hl-1"> </span><span class="hl-2">plot:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Plot</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">tasks:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Tasks</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">callbacks:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Callbacks</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">getChannels</span><span class="hl-1">(</span><span class="hl-2">_auth</span><span class="hl-1">: </span><span class="hl-5">Authorization</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">: </span><span class="hl-5">AuthToken</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">Channel</span><span class="hl-1">[]&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Return available projects/teams for the user to select</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">client</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">LinearClient</span><span class="hl-1">({ </span><span class="hl-2">accessToken:</span><span class="hl-1"> </span><span class="hl-2">token</span><span class="hl-1">.</span><span class="hl-2">token</span><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">teams</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">teams</span><span class="hl-1">();</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-2">teams</span><span class="hl-1">.</span><span class="hl-2">nodes</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">(</span><span class="hl-2">t</span><span class="hl-1"> </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({ </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">t</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">t</span><span class="hl-1">.</span><span class="hl-2">name</span><span class="hl-1"> }));</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">onChannelEnabled</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">: </span><span class="hl-5">Channel</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Set up webhook</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhookUrl</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">network</span><span class="hl-1">.</span><span class="hl-6">createWebhook</span><span class="hl-1">(</span><br/><span class="hl-1"> {}, </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onWebhook</span><span class="hl-1">, </span><span class="hl-2">channel</span><span class="hl-1">.</span><span class="hl-2">id</span><br/><span class="hl-1"> );</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">webhookUrl</span><span class="hl-1">.</span><span class="hl-6">includes</span><span class="hl-1">(</span><span class="hl-3">&quot;localhost&quot;</span><span class="hl-1">)) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">client</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhook</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">createWebhook</span><span class="hl-1">({ </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">webhookUrl</span><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">webhook</span><span class="hl-1">?.</span><span class="hl-2">id</span><span class="hl-1">) </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`webhook_id_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-2">webhook</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Start initial sync</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">cursor:</span><span class="hl-1"> </span><span class="hl-4">null</span><span class="hl-1">, </span><span class="hl-2">batchNumber:</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1">, </span><span class="hl-2">initialSync:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">batch</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">, </span><span class="hl-2">channel</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">tasks</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">batch</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">onChannelDisabled</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">: </span><span class="hl-5">Channel</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhookId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">`webhook_id_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">webhookId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">try</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">client</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">deleteWebhook</span><span class="hl-1">(</span><span class="hl-2">webhookId</span><span class="hl-1">);</span><br/><span class="hl-1"> } </span><span class="hl-0">catch</span><span class="hl-1"> { </span><span class="hl-7">/* ignore */</span><span class="hl-1"> }</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-3">`webhook_id_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channel</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">private</span><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-2">IssueSource</span><span class="hl-1">.</span><span class="hl-8">PROVIDER</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">token</span><span class="hl-1">) </span><span class="hl-0">throw</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Error</span><span class="hl-1">(</span><span class="hl-3">&quot;No auth token&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">LinearClient</span><span class="hl-1">({ </span><span class="hl-2">accessToken:</span><span class="hl-1"> </span><span class="hl-2">token</span><span class="hl-1">.</span><span class="hl-2">token</span><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">private</span><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">state</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">any</span><span class="hl-1">&gt;(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">state</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">client</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">getClient</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">result</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">client</span><span class="hl-1">.</span><span class="hl-6">issues</span><span class="hl-1">({ </span><span class="hl-2">teamId:</span><span class="hl-1"> </span><span class="hl-2">channelId</span><span class="hl-1">, </span><span class="hl-2">after:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">cursor</span><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">issue</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">result</span><span class="hl-1">.</span><span class="hl-2">nodes</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">`linear:issue:</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">`</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">identifier</span><span class="hl-4">}</span><span class="hl-3">: </span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">title</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">done:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">completedAt</span><span class="hl-1"> ? </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">completedAt</span><span class="hl-1">) : </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">meta:</span><span class="hl-1"> { </span><span class="hl-2">syncProvider:</span><span class="hl-1"> </span><span class="hl-3">&quot;linear&quot;</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1"> || </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">links:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">url</span><span class="hl-1"> ? [{</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">LinkType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Open in Linear&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">url</span><span class="hl-1">,</span><br/><span class="hl-1"> }] : </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1"> ...(</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">initialSync</span><span class="hl-1"> ? { </span><span class="hl-2">unread:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> } : {}),</span><br/><span class="hl-1"> ...(</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">initialSync</span><span class="hl-1"> ? { </span><span class="hl-2">archived:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> } : {}),</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">result</span><span class="hl-1">.</span><span class="hl-2">pageInfo</span><span class="hl-1">.</span><span class="hl-2">hasNextPage</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">cursor:</span><span class="hl-1"> </span><span class="hl-2">result</span><span class="hl-1">.</span><span class="hl-2">pageInfo</span><span class="hl-1">.</span><span class="hl-2">endCursor</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">batchNumber:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">batchNumber</span><span class="hl-1"> + </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">initialSync:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">initialSync</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">next</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">tasks</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">next</span><span class="hl-1">);</span><br/><span class="hl-1"> } </span><span class="hl-0">else</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">private</span><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">onWebhook</span><span class="hl-1">(</span><span class="hl-2">request</span><span class="hl-1">: </span><span class="hl-5">WebhookRequest</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">payload</span><span class="hl-1"> = </span><span class="hl-8">JSON</span><span class="hl-1">.</span><span class="hl-6">parse</span><span class="hl-1">(</span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">rawBody</span><span class="hl-1"> || </span><span class="hl-3">&quot;{}&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">payload</span><span class="hl-1">.</span><span class="hl-2">type</span><span class="hl-1"> !== </span><span class="hl-3">&quot;Issue&quot;</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">issue</span><span class="hl-1"> = </span><span class="hl-2">payload</span><span class="hl-1">.</span><span class="hl-2">data</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">`linear:issue:</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">`</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">identifier</span><span class="hl-4">}</span><span class="hl-3">: </span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">title</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">done:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">completedAt</span><span class="hl-1"> ? </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">completedAt</span><span class="hl-1">) : </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">meta:</span><span class="hl-1"> { </span><span class="hl-2">syncProvider:</span><span class="hl-1"> </span><span class="hl-3">&quot;linear&quot;</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1"> || </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1"> </span><span class="hl-7">// Incremental sync: omit unread and archived</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
121
- </code><button type="button">Copy</button></pre>
122
-
123
- <hr>
124
- <h2 id="best-practices" class="tsd-anchor-link">Best Practices<a href="#best-practices" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="1-always-inject-sync-metadata" class="tsd-anchor-link">1. Always Inject Sync Metadata<a href="#1-always-inject-sync-metadata" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Every synced activity must include <code>syncProvider</code> and <code>channelId</code> in <code>meta</code> for bulk operations (e.g., archiving all activities when a channel is disabled).</p>
125
- <h3 id="2-use-canonical-source-urls" class="tsd-anchor-link">2. Use Canonical Source URLs<a href="#2-use-canonical-source-urls" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Use immutable IDs in <code>Activity.source</code> for deduplication. For services with mutable identifiers (like Jira issue keys), use the immutable ID in <code>source</code> and store the mutable key in <code>meta</code>.</p>
126
- <h3 id="3-handle-html-content-correctly" class="tsd-anchor-link">3. Handle HTML Content Correctly<a href="#3-handle-html-content-correctly" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Never strip HTML tags locally. Pass raw HTML with <code>contentType: &quot;html&quot;</code> for server-side markdown conversion.</p>
127
- <h3 id="4-add-localhost-guard-for-webhooks" class="tsd-anchor-link">4. Add Localhost Guard for Webhooks<a href="#4-add-localhost-guard-for-webhooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Skip webhook registration in development when the URL contains &quot;localhost&quot;.</p>
128
- <h3 id="5-maintain-callback-backward-compatibility" class="tsd-anchor-link">5. Maintain Callback Backward Compatibility<a href="#5-maintain-callback-backward-compatibility" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>All callbacks automatically upgrade to new source versions. Only add optional parameters at the end of callback method signatures.</p>
129
- <h3 id="6-clean-up-on-disable" class="tsd-anchor-link">6. Clean Up on Disable<a href="#6-clean-up-on-disable" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Delete webhooks, callbacks, and stored state in <code>onChannelDisabled()</code>.</p>
130
- <hr>
131
- <h2 id="next-steps" class="tsd-anchor-link">Next Steps<a href="#next-steps" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
132
- <li><strong><a href="../media/AGENTS.md">Source Development Guide</a></strong> - Comprehensive scaffold, patterns, and checklist</li>
133
- <li><strong><a href="../media/SYNC_STRATEGIES.md">Sync Strategies</a></strong> - Deduplication, upserts, and tag management</li>
134
- <li><strong><a href="Built-in_Tools.html">Built-in Tools Guide</a></strong> - Complete reference for Plot, Store, Integrations, and more</li>
135
- <li><strong><a href="../media/MULTI_USER_AUTH.md">Multi-User Auth</a></strong> - Per-user auth for write-backs</li>
136
- </ul>
137
- </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#building-sources"><span>Building <wbr/>Sources</span></a><ul><li><a href="#table-of-contents"><span>Table of <wbr/>Contents</span></a></li><li><a href="#sources-vs-twists"><span>Sources vs <wbr/>Twists</span></a></li><li><a href="#source-structure"><span>Source <wbr/>Structure</span></a></li><li><ul><li><a href="#package-structure"><span>Package <wbr/>Structure</span></a></li></ul></li><li><a href="#oauth-and-channel-lifecycle"><span>O<wbr/>Auth and <wbr/>Channel <wbr/>Lifecycle</span></a></li><li><ul><li><a href="#how-it-works"><span>How <wbr/>It <wbr/>Works</span></a></li><li><a href="#getchannels"><span>get<wbr/>Channels</span></a></li><li><a href="#onchannelenabled"><span>on<wbr/>Channel<wbr/>Enabled</span></a></li><li><a href="#onchanneldisabled"><span>on<wbr/>Channel<wbr/>Disabled</span></a></li><li><a href="#getting-auth-tokens"><span>Getting <wbr/>Auth <wbr/>Tokens</span></a></li></ul></li><li><a href="#data-sync"><span>Data <wbr/>Sync</span></a></li><li><ul><li><a href="#transforming-external-items"><span>Transforming <wbr/>External <wbr/>Items</span></a></li><li><a href="#initial-vs-incremental-sync"><span>Initial vs <wbr/>Incremental <wbr/>Sync</span></a></li></ul></li><li><a href="#batch-processing"><span>Batch <wbr/>Processing</span></a></li><li><a href="#complete-example"><span>Complete <wbr/>Example</span></a></li><li><a href="#best-practices"><span>Best <wbr/>Practices</span></a></li><li><ul><li><a href="#1-always-inject-sync-metadata"><span>1. <wbr/>Always <wbr/>Inject <wbr/>Sync <wbr/>Metadata</span></a></li><li><a href="#2-use-canonical-source-urls"><span>2. <wbr/>Use <wbr/>Canonical <wbr/>Source <wbr/>UR<wbr/>Ls</span></a></li><li><a href="#3-handle-html-content-correctly"><span>3. <wbr/>Handle <wbr/>HTML <wbr/>Content <wbr/>Correctly</span></a></li><li><a href="#4-add-localhost-guard-for-webhooks"><span>4. <wbr/>Add <wbr/>Localhost <wbr/>Guard for <wbr/>Webhooks</span></a></li><li><a href="#5-maintain-callback-backward-compatibility"><span>5. <wbr/>Maintain <wbr/>Callback <wbr/>Backward <wbr/>Compatibility</span></a></li><li><a href="#6-clean-up-on-disable"><span>6. <wbr/>Clean <wbr/>Up on <wbr/>Disable</span></a></li></ul></li><li><a href="#next-steps"><span>Next <wbr/>Steps</span></a></li></ul></div></details></div><div class="site-menu"><nav id="tsd-sidebar-links" class="tsd-navigation"><a href="https://plot.day" class="tsd-nav-link">Plot</a><a href="https://github.com/plotday/plot" class="tsd-nav-link">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister" class="tsd-nav-link">NPM</a></nav><nav class="tsd-navigation"><a href="../modules.html">Creating Plot Twists</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
@@ -1,9 +0,0 @@
1
- /**
2
- * Generated LLM documentation for @plotday/twister/source
3
- *
4
- * This file is auto-generated during build. Do not edit manually.
5
- * Generated from: prebuild.ts
6
- */
7
- declare const _default: "import { type Actor, type ActorId, type Link, type Note, type Thread } from \"./plot\";\nimport type { ScheduleContactStatus } from \"./schedule\";\nimport {\n type AuthProvider,\n type AuthToken,\n type Authorization,\n type Channel,\n type LinkTypeConfig,\n} from \"./tools/integrations\";\nimport { Twist } from \"./twist\";\n\n/**\n * Base class for sources \u2014 twists that sync data from external services.\n *\n * Sources declare a single OAuth provider and scopes, and implement channel\n * lifecycle methods for discovering and syncing external resources. They save\n * data directly via `integrations.saveLink()` instead of using the Plot tool.\n *\n * @example\n * ```typescript\n * class LinearSource extends Source<LinearSource> {\n * readonly provider = AuthProvider.Linear;\n * readonly scopes = [\"read\", \"write\"];\n * readonly linkTypes = [{\n * type: \"issue\",\n * label: \"Issue\",\n * statuses: [\n * { status: \"open\", label: \"Open\" },\n * { status: \"done\", label: \"Done\" },\n * ],\n * }];\n *\n * build(build: ToolBuilder) {\n * return {\n * integrations: build(Integrations),\n * };\n * }\n *\n * async getChannels(auth: Authorization, token: AuthToken): Promise<Channel[]> {\n * const teams = await this.listTeams(token);\n * return teams.map(t => ({ id: t.id, title: t.name }));\n * }\n *\n * async onChannelEnabled(channel: Channel) {\n * const issues = await this.fetchIssues(channel.id);\n * for (const issue of issues) {\n * await this.tools.integrations.saveLink(issue);\n * }\n * }\n *\n * async onChannelDisabled(channel: Channel) {\n * // Clean up webhooks, sync state, etc.\n * }\n * }\n * ```\n */\nexport abstract class Source<TSelf> extends Twist<TSelf> {\n /**\n * Static marker to identify Source subclasses without instanceof checks\n * across worker boundaries.\n */\n static readonly isSource = true;\n\n // ---- Identity (abstract \u2014 every source must declare) ----\n\n /** The OAuth provider this source authenticates with. */\n abstract readonly provider: AuthProvider;\n\n /** OAuth scopes to request for this source. */\n abstract readonly scopes: string[];\n\n // ---- Optional metadata ----\n\n /**\n * Registry of link types this source creates (e.g., issue, event, message).\n * Used for display in the UI (icons, labels, statuses).\n */\n readonly linkTypes?: LinkTypeConfig[];\n\n // ---- Channel lifecycle (abstract \u2014 every source must implement) ----\n\n /**\n * Returns available channels for the authorized actor.\n * Called after OAuth is complete, during the setup/edit modal.\n *\n * @param auth - The completed authorization with provider and actor info\n * @param token - The access token for making API calls\n * @returns Promise resolving to available channels for the user to select\n */\n abstract getChannels(\n auth: Authorization,\n token: AuthToken\n ): Promise<Channel[]>;\n\n /**\n * Called when a channel resource is enabled for syncing.\n * Should set up webhooks and start initial sync.\n *\n * @param channel - The channel that was enabled\n */\n abstract onChannelEnabled(channel: Channel): Promise<void>;\n\n /**\n * Called when a channel resource is disabled.\n * Should stop sync, clean up webhooks, and remove state.\n *\n * @param channel - The channel that was disabled\n */\n abstract onChannelDisabled(channel: Channel): Promise<void>;\n\n // ---- Write-back hooks (optional, default no-ops) ----\n\n /**\n * Called when a link created by this source is updated by the user.\n * Override to write back changes to the external service\n * (e.g., changing issue status in Linear when marked done in Plot).\n *\n * @param link - The updated link\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onLinkUpdated(link: Link): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a note is created on a thread owned by this source.\n * Override to write back comments to the external service\n * (e.g., adding a comment to a Linear issue).\n *\n * @param note - The created note\n * @param thread - The thread the note belongs to (includes thread.meta with source-specific data)\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onNoteCreated(note: Note, thread: Thread): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a user reads or unreads a thread owned by this source.\n * Override to write back read status to the external service\n * (e.g., marking an email as read in Gmail).\n *\n * @param thread - The thread that was read/unread (includes thread.meta with source-specific data)\n * @param actor - The user who performed the action\n * @param unread - false when marked as read, true when marked as unread\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onThreadRead(thread: Thread, actor: Actor, unread: boolean): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a user marks or unmarks a thread as todo.\n * Override to sync todo status to the external service\n * (e.g., starring an email in Gmail when marked as todo).\n *\n * @param thread - The thread (includes thread.meta with source-specific data)\n * @param actor - The user who changed the todo status\n * @param todo - true when marked as todo, false when done or removed\n * @param options - Additional context\n * @param options.date - The todo date (when todo=true)\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onThreadToDo(thread: Thread, actor: Actor, todo: boolean, options: { date?: Date }): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a schedule contact's RSVP status changes on a thread owned by this source.\n * Override to sync RSVP changes back to the external calendar.\n *\n * @param thread - The thread (includes thread.meta with source-specific data)\n * @param scheduleId - The schedule ID\n * @param contactId - The contact whose status changed\n * @param status - The new RSVP status ('attend', 'skip', or null)\n * @param actor - The user who changed the status\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onScheduleContactUpdated(thread: Thread, scheduleId: string, contactId: ActorId, status: ScheduleContactStatus | null, actor: Actor): Promise<void> {\n return Promise.resolve();\n }\n\n // ---- Activation ----\n\n /**\n * Called when the source is activated after OAuth is complete.\n *\n * Unlike Twist.activate() which receives a priority, Source.activate()\n * receives the authorization and actor since sources are not installed\n * in priorities.\n *\n * Default implementation does nothing. Override for custom setup.\n *\n * @param context - The activation context\n * @param context.auth - The completed OAuth authorization\n * @param context.actor - The actor who activated the source\n */\n // @ts-ignore - Source.activate() intentionally has a different signature than Twist.activate()\n activate(context: { auth: Authorization; actor: Actor }): Promise<void> {\n return Promise.resolve();\n }\n}\n";
8
- export default _default;
9
- //# sourceMappingURL=source.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/llm-docs/source.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;wBAEY,6qOAA8pO;AAA7qO,wBAA8qO"}
@@ -1,8 +0,0 @@
1
- /**
2
- * Generated LLM documentation for @plotday/twister/source
3
- *
4
- * This file is auto-generated during build. Do not edit manually.
5
- * Generated from: prebuild.ts
6
- */
7
- export default "import { type Actor, type ActorId, type Link, type Note, type Thread } from \"./plot\";\nimport type { ScheduleContactStatus } from \"./schedule\";\nimport {\n type AuthProvider,\n type AuthToken,\n type Authorization,\n type Channel,\n type LinkTypeConfig,\n} from \"./tools/integrations\";\nimport { Twist } from \"./twist\";\n\n/**\n * Base class for sources — twists that sync data from external services.\n *\n * Sources declare a single OAuth provider and scopes, and implement channel\n * lifecycle methods for discovering and syncing external resources. They save\n * data directly via `integrations.saveLink()` instead of using the Plot tool.\n *\n * @example\n * ```typescript\n * class LinearSource extends Source<LinearSource> {\n * readonly provider = AuthProvider.Linear;\n * readonly scopes = [\"read\", \"write\"];\n * readonly linkTypes = [{\n * type: \"issue\",\n * label: \"Issue\",\n * statuses: [\n * { status: \"open\", label: \"Open\" },\n * { status: \"done\", label: \"Done\" },\n * ],\n * }];\n *\n * build(build: ToolBuilder) {\n * return {\n * integrations: build(Integrations),\n * };\n * }\n *\n * async getChannels(auth: Authorization, token: AuthToken): Promise<Channel[]> {\n * const teams = await this.listTeams(token);\n * return teams.map(t => ({ id: t.id, title: t.name }));\n * }\n *\n * async onChannelEnabled(channel: Channel) {\n * const issues = await this.fetchIssues(channel.id);\n * for (const issue of issues) {\n * await this.tools.integrations.saveLink(issue);\n * }\n * }\n *\n * async onChannelDisabled(channel: Channel) {\n * // Clean up webhooks, sync state, etc.\n * }\n * }\n * ```\n */\nexport abstract class Source<TSelf> extends Twist<TSelf> {\n /**\n * Static marker to identify Source subclasses without instanceof checks\n * across worker boundaries.\n */\n static readonly isSource = true;\n\n // ---- Identity (abstract — every source must declare) ----\n\n /** The OAuth provider this source authenticates with. */\n abstract readonly provider: AuthProvider;\n\n /** OAuth scopes to request for this source. */\n abstract readonly scopes: string[];\n\n // ---- Optional metadata ----\n\n /**\n * Registry of link types this source creates (e.g., issue, event, message).\n * Used for display in the UI (icons, labels, statuses).\n */\n readonly linkTypes?: LinkTypeConfig[];\n\n // ---- Channel lifecycle (abstract — every source must implement) ----\n\n /**\n * Returns available channels for the authorized actor.\n * Called after OAuth is complete, during the setup/edit modal.\n *\n * @param auth - The completed authorization with provider and actor info\n * @param token - The access token for making API calls\n * @returns Promise resolving to available channels for the user to select\n */\n abstract getChannels(\n auth: Authorization,\n token: AuthToken\n ): Promise<Channel[]>;\n\n /**\n * Called when a channel resource is enabled for syncing.\n * Should set up webhooks and start initial sync.\n *\n * @param channel - The channel that was enabled\n */\n abstract onChannelEnabled(channel: Channel): Promise<void>;\n\n /**\n * Called when a channel resource is disabled.\n * Should stop sync, clean up webhooks, and remove state.\n *\n * @param channel - The channel that was disabled\n */\n abstract onChannelDisabled(channel: Channel): Promise<void>;\n\n // ---- Write-back hooks (optional, default no-ops) ----\n\n /**\n * Called when a link created by this source is updated by the user.\n * Override to write back changes to the external service\n * (e.g., changing issue status in Linear when marked done in Plot).\n *\n * @param link - The updated link\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onLinkUpdated(link: Link): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a note is created on a thread owned by this source.\n * Override to write back comments to the external service\n * (e.g., adding a comment to a Linear issue).\n *\n * @param note - The created note\n * @param thread - The thread the note belongs to (includes thread.meta with source-specific data)\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onNoteCreated(note: Note, thread: Thread): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a user reads or unreads a thread owned by this source.\n * Override to write back read status to the external service\n * (e.g., marking an email as read in Gmail).\n *\n * @param thread - The thread that was read/unread (includes thread.meta with source-specific data)\n * @param actor - The user who performed the action\n * @param unread - false when marked as read, true when marked as unread\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onThreadRead(thread: Thread, actor: Actor, unread: boolean): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a user marks or unmarks a thread as todo.\n * Override to sync todo status to the external service\n * (e.g., starring an email in Gmail when marked as todo).\n *\n * @param thread - The thread (includes thread.meta with source-specific data)\n * @param actor - The user who changed the todo status\n * @param todo - true when marked as todo, false when done or removed\n * @param options - Additional context\n * @param options.date - The todo date (when todo=true)\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onThreadToDo(thread: Thread, actor: Actor, todo: boolean, options: { date?: Date }): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Called when a schedule contact's RSVP status changes on a thread owned by this source.\n * Override to sync RSVP changes back to the external calendar.\n *\n * @param thread - The thread (includes thread.meta with source-specific data)\n * @param scheduleId - The schedule ID\n * @param contactId - The contact whose status changed\n * @param status - The new RSVP status ('attend', 'skip', or null)\n * @param actor - The user who changed the status\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onScheduleContactUpdated(thread: Thread, scheduleId: string, contactId: ActorId, status: ScheduleContactStatus | null, actor: Actor): Promise<void> {\n return Promise.resolve();\n }\n\n // ---- Activation ----\n\n /**\n * Called when the source is activated after OAuth is complete.\n *\n * Unlike Twist.activate() which receives a priority, Source.activate()\n * receives the authorization and actor since sources are not installed\n * in priorities.\n *\n * Default implementation does nothing. Override for custom setup.\n *\n * @param context - The activation context\n * @param context.auth - The completed OAuth authorization\n * @param context.actor - The actor who activated the source\n */\n // @ts-ignore - Source.activate() intentionally has a different signature than Twist.activate()\n activate(context: { auth: Authorization; actor: Actor }): Promise<void> {\n return Promise.resolve();\n }\n}\n";
8
- //# sourceMappingURL=source.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/llm-docs/source.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAe,8pOAA8pO,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,8BAAsB,MAAM,CAAC,KAAK,CAAE,SAAQ,KAAK,CAAC,KAAK,CAAC;IACtD;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,QAAQ,QAAQ;IAIhC,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAEzC,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAInC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAItC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAClB,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,OAAO,EAAE,CAAC;IAErB;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAE1D;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D;;;;;;OAMG;IAEH,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC;;;;;;;OAOG;IAEH,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD;;;;;;;;OAQG;IAEH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1E;;;;;;;;;;OAUG;IAEH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlG;;;;;;;;;OASG;IAEH,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnJ;;;;;;;;;;;;OAYG;IAEH,QAAQ,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,OAAgB,MAAc,SAAQ,KAAY;IACtD;;;OAGG;IACH,MAAM,CAAU,QAAQ,GAAG,IAAI,CAAC;IAUhC,8BAA8B;IAE9B;;;OAGG;IACM,SAAS,CAAoB;IAiCtC,wDAAwD;IAExD;;;;;;OAMG;IACH,6DAA6D;IAC7D,aAAa,CAAC,IAAU;QACtB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;;;OAOG;IACH,6DAA6D;IAC7D,aAAa,CAAC,IAAU,EAAE,MAAc;QACtC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;;;;OAQG;IACH,6DAA6D;IAC7D,YAAY,CAAC,MAAc,EAAE,KAAY,EAAE,MAAe;QACxD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;;;;;;OAUG;IACH,6DAA6D;IAC7D,YAAY,CAAC,MAAc,EAAE,KAAY,EAAE,IAAa,EAAE,OAAwB;QAChF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;;;;;OASG;IACH,6DAA6D;IAC7D,wBAAwB,CAAC,MAAc,EAAE,UAAkB,EAAE,SAAkB,EAAE,MAAoC,EAAE,KAAY;QACjI,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,uBAAuB;IAEvB;;;;;;;;;;;;OAYG;IACH,+FAA+F;IAC/F,QAAQ,CAAC,OAA8C;QACrD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC"}