@plotday/twister 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/bin/commands/generate.js +5 -5
  2. package/bin/commands/generate.js.map +1 -1
  3. package/bin/templates/AGENTS.template.md +8 -2
  4. package/bin/utils/bundle.js +14 -0
  5. package/bin/utils/bundle.js.map +1 -1
  6. package/cli/templates/AGENTS.template.md +8 -2
  7. package/dist/connector.d.ts +67 -7
  8. package/dist/connector.d.ts.map +1 -1
  9. package/dist/connector.js +15 -5
  10. package/dist/connector.js.map +1 -1
  11. package/dist/docs/assets/hierarchy.js +1 -1
  12. package/dist/docs/assets/navigation.js +1 -1
  13. package/dist/docs/assets/search.js +1 -1
  14. package/dist/docs/classes/index.Connector.html +58 -49
  15. package/dist/docs/classes/index.Imap.html +1 -1
  16. package/dist/docs/classes/index.Options.html +1 -1
  17. package/dist/docs/classes/index.Smtp.html +1 -1
  18. package/dist/docs/classes/tools_ai.AI.html +1 -1
  19. package/dist/docs/classes/tools_callbacks.Callbacks.html +1 -1
  20. package/dist/docs/classes/tools_integrations.Integrations.html +21 -5
  21. package/dist/docs/classes/tools_network.Network.html +1 -1
  22. package/dist/docs/classes/tools_plot.Plot.html +1 -1
  23. package/dist/docs/classes/tools_store.Store.html +1 -1
  24. package/dist/docs/classes/tools_tasks.Tasks.html +1 -1
  25. package/dist/docs/classes/tools_twists.Twists.html +1 -1
  26. package/dist/docs/classes/twist.Twist.html +28 -28
  27. package/dist/docs/documents/Building_Connectors.html +8 -1
  28. package/dist/docs/documents/CLI_Reference.html +6 -4
  29. package/dist/docs/enums/tag.Tag.html +11 -1
  30. package/dist/docs/enums/tools_integrations.AuthProvider.html +14 -12
  31. package/dist/docs/hierarchy.html +1 -1
  32. package/dist/docs/media/AGENTS.md +298 -775
  33. package/dist/docs/media/MULTI_USER_AUTH.md +6 -4
  34. package/dist/docs/media/SYNC_STRATEGIES.md +20 -14
  35. package/dist/docs/modules/index.html +1 -1
  36. package/dist/docs/types/index.CreateLinkDraft.html +7 -12
  37. package/dist/docs/types/index.NoteWriteBackResult.html +38 -0
  38. package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
  39. package/dist/docs/types/tools_integrations.AuthToken.html +4 -4
  40. package/dist/docs/types/tools_integrations.Authorization.html +4 -4
  41. package/dist/llm-docs/connector.d.ts +1 -1
  42. package/dist/llm-docs/connector.d.ts.map +1 -1
  43. package/dist/llm-docs/connector.js +1 -1
  44. package/dist/llm-docs/connector.js.map +1 -1
  45. package/dist/llm-docs/tag.d.ts +1 -1
  46. package/dist/llm-docs/tag.d.ts.map +1 -1
  47. package/dist/llm-docs/tag.js +1 -1
  48. package/dist/llm-docs/tag.js.map +1 -1
  49. package/dist/llm-docs/tools/integrations.d.ts +1 -1
  50. package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
  51. package/dist/llm-docs/tools/integrations.js +1 -1
  52. package/dist/llm-docs/tools/integrations.js.map +1 -1
  53. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  54. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  55. package/dist/llm-docs/twist-guide-template.js +1 -1
  56. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  57. package/dist/llm-docs/twist.d.ts +1 -1
  58. package/dist/llm-docs/twist.d.ts.map +1 -1
  59. package/dist/llm-docs/twist.js +1 -1
  60. package/dist/llm-docs/twist.js.map +1 -1
  61. package/dist/tag.d.ts +11 -1
  62. package/dist/tag.d.ts.map +1 -1
  63. package/dist/tag.js +10 -0
  64. package/dist/tag.js.map +1 -1
  65. package/dist/tools/integrations.d.ts +25 -1
  66. package/dist/tools/integrations.d.ts.map +1 -1
  67. package/dist/tools/integrations.js +2 -0
  68. package/dist/tools/integrations.js.map +1 -1
  69. package/dist/twist-guide.d.ts +1 -1
  70. package/dist/twist-guide.d.ts.map +1 -1
  71. package/dist/twist.d.ts +2 -1
  72. package/dist/twist.d.ts.map +1 -1
  73. package/dist/twist.js.map +1 -1
  74. package/dist/utils/markdown.d.ts +27 -0
  75. package/dist/utils/markdown.d.ts.map +1 -0
  76. package/dist/utils/markdown.js +82 -0
  77. package/dist/utils/markdown.js.map +1 -0
  78. package/package.json +7 -1
  79. package/src/connector.ts +427 -0
  80. package/src/creator-docs.ts +29 -0
  81. package/src/index.ts +10 -0
  82. package/src/llm-docs/connector.ts +8 -0
  83. package/src/llm-docs/index.ts +48 -0
  84. package/src/llm-docs/options.ts +8 -0
  85. package/src/llm-docs/plot.ts +8 -0
  86. package/src/llm-docs/schedule.ts +8 -0
  87. package/src/llm-docs/tag.ts +8 -0
  88. package/src/llm-docs/tool.ts +8 -0
  89. package/src/llm-docs/tools/ai.ts +8 -0
  90. package/src/llm-docs/tools/callbacks.ts +8 -0
  91. package/src/llm-docs/tools/imap.ts +8 -0
  92. package/src/llm-docs/tools/integrations.ts +8 -0
  93. package/src/llm-docs/tools/network.ts +8 -0
  94. package/src/llm-docs/tools/plot.ts +8 -0
  95. package/src/llm-docs/tools/smtp.ts +8 -0
  96. package/src/llm-docs/tools/store.ts +8 -0
  97. package/src/llm-docs/tools/tasks.ts +8 -0
  98. package/src/llm-docs/tools/twists.ts +8 -0
  99. package/src/llm-docs/twist-guide-template.ts +8 -0
  100. package/src/llm-docs/twist.ts +8 -0
  101. package/src/options.ts +115 -0
  102. package/src/plot.ts +1068 -0
  103. package/src/schedule.ts +203 -0
  104. package/src/tag.ts +54 -0
  105. package/src/tool.ts +377 -0
  106. package/src/tools/ai.ts +845 -0
  107. package/src/tools/callbacks.ts +134 -0
  108. package/src/tools/imap.ts +266 -0
  109. package/src/tools/index.ts +10 -0
  110. package/src/tools/integrations.ts +352 -0
  111. package/src/tools/network.ts +240 -0
  112. package/src/tools/plot.ts +692 -0
  113. package/src/tools/smtp.ts +166 -0
  114. package/src/tools/store.ts +149 -0
  115. package/src/tools/tasks.ts +137 -0
  116. package/src/tools/twists.ts +228 -0
  117. package/src/twist-guide.ts +9 -0
  118. package/src/twist.ts +436 -0
  119. package/src/utils/hash.ts +8 -0
  120. package/src/utils/markdown.ts +94 -0
  121. package/src/utils/serializable.ts +54 -0
  122. package/src/utils/types.ts +130 -0
  123. package/src/utils/uuid.ts +9 -0
package/src/twist.ts ADDED
@@ -0,0 +1,436 @@
1
+ import type { NoteWriteBackResult } from "./connector";
2
+ import { type Action, type Actor, type ActorId, type Link, type Note, type Thread, Uuid } from "./plot";
3
+ import type { Tag } from "./tag";
4
+ import { type ITool } from "./tool";
5
+ import type { Callback } from "./tools/callbacks";
6
+ import type { Serializable } from "./utils/serializable";
7
+ import type { InferTools, ToolBuilder, ToolShed } from "./utils/types";
8
+
9
+ /**
10
+ * Base class for all twists.
11
+ *
12
+ * A twist is installed at the workspace level and is owned by a single user
13
+ * (see `this.userId`). It has no inherent priority scope: threads, notes, and
14
+ * links it creates are filed against the owner's priorities, with automatic
15
+ * priority matching when no explicit target is provided.
16
+ *
17
+ * Override `build()` to declare tool dependencies and lifecycle methods to
18
+ * handle events.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * class FlatteringTwist extends Twist<FlatteringTwist> {
23
+ * build(build: ToolBuilder) {
24
+ * return {
25
+ * plot: build(Plot),
26
+ * };
27
+ * }
28
+ *
29
+ * async activate() {
30
+ * await this.tools.plot.createThread({
31
+ * title: "Hello, good looking!",
32
+ * });
33
+ * }
34
+ * }
35
+ * ```
36
+ */
37
+ export abstract class Twist<TSelf> {
38
+ /**
39
+ * When `true`, users may install multiple instances of this twist within
40
+ * the same scope (personal workspace or team). Each instance must have a
41
+ * distinct name.
42
+ *
43
+ * Defaults to `false` (single instance per scope).
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * class WorkflowTwist extends Twist<WorkflowTwist> {
48
+ * static readonly multipleInstances = true;
49
+ * // ...
50
+ * }
51
+ * ```
52
+ */
53
+ static readonly multipleInstances?: boolean;
54
+
55
+ /**
56
+ * The user ID (`twist_instance.owner_id`) that installed this twist.
57
+ * Populated by the runtime before any lifecycle method runs.
58
+ */
59
+ protected userId!: Uuid;
60
+
61
+ constructor(protected id: Uuid, private toolShed: ToolShed) {}
62
+
63
+ /**
64
+ * Gets the initialized tools for this twist.
65
+ * @throws Error if called before initialization is complete
66
+ */
67
+ protected get tools(): InferTools<TSelf> {
68
+ return this.toolShed.getTools<InferTools<TSelf>>();
69
+ }
70
+
71
+ /**
72
+ * Declares tool dependencies for this twist.
73
+ * Return an object mapping tool names to build() promises.
74
+ *
75
+ * @param build - The build function to use for declaring dependencies
76
+ * @returns Object mapping tool names to tool promises
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * build(build: ToolBuilder) {
81
+ * return {
82
+ * plot: build(Plot),
83
+ * calendar: build(GoogleCalendar, { apiKey: "..." }),
84
+ * };
85
+ * }
86
+ * ```
87
+ */
88
+ abstract build(build: ToolBuilder): Record<string, Promise<ITool>>;
89
+
90
+ /**
91
+ * Creates a persistent callback to a method on this twist.
92
+ *
93
+ * ExtraArgs are strongly typed to match the method's signature. They must be serializable.
94
+ *
95
+ * @param fn - The method to callback
96
+ * @param extraArgs - Additional arguments to pass (type-checked, must be serializable)
97
+ * @returns Promise resolving to a persistent callback token
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const callback = await this.callback(this.onWebhook, "calendar", 123);
102
+ * ```
103
+ */
104
+ protected callback<
105
+ TArgs extends Serializable[],
106
+ Fn extends (...args: TArgs) => any
107
+ >(fn: Fn, ...extraArgs: TArgs): Promise<Callback>;
108
+ // Overload when caller provides the first argument
109
+ protected callback<
110
+ TArgs extends Serializable[],
111
+ Fn extends (arg1: any, ...extraArgs: TArgs) => any
112
+ >(fn: Fn, ...extraArgs: TArgs): Promise<Callback>;
113
+ protected async callback<
114
+ TArgs extends Serializable[],
115
+ Fn extends (...args: any[]) => any
116
+ >(fn: Fn, ...extraArgs: TArgs): Promise<Callback> {
117
+ return this.tools.callbacks.create(fn, ...extraArgs);
118
+ }
119
+
120
+ /**
121
+ * Like callback(), but for an Action, which receives the action as the first argument.
122
+ *
123
+ * @param fn - The method to callback
124
+ * @param extraArgs - Additional arguments to pass after the action
125
+ * @returns Promise resolving to a persistent callback token
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const callback = await this.actionCallback(this.doSomething, 123);
130
+ * const action: Action = {
131
+ * type: ActionType.callback,
132
+ * title: "Do Something",
133
+ * callback,
134
+ * };
135
+ * ```
136
+ */
137
+ protected async actionCallback<
138
+ TArgs extends Serializable[],
139
+ Fn extends (action: Action, ...extraArgs: TArgs) => any
140
+ >(fn: Fn, ...extraArgs: TArgs): Promise<Callback> {
141
+ return this.tools.callbacks.create(fn, ...extraArgs);
142
+ }
143
+
144
+ /**
145
+ * Deletes a specific callback by its token.
146
+ *
147
+ * @param token - The callback token to delete
148
+ * @returns Promise that resolves when the callback is deleted
149
+ */
150
+ protected async deleteCallback(token: Callback): Promise<void> {
151
+ return this.tools.callbacks.delete(token);
152
+ }
153
+
154
+ /**
155
+ * Deletes all callbacks for this twist.
156
+ *
157
+ * @returns Promise that resolves when all callbacks are deleted
158
+ */
159
+ protected async deleteAllCallbacks(): Promise<void> {
160
+ return this.tools.callbacks.deleteAll();
161
+ }
162
+
163
+ /**
164
+ * Executes a callback by its token inline in the current execution.
165
+ *
166
+ * **Use `this.runTask()` instead for batch continuations and long-running work.**
167
+ * `this.run()` executes inline, sharing the current request count (~1000 limit)
168
+ * and blocking the HTTP response. This causes timeouts when used in lifecycle
169
+ * methods like `onChannelEnabled` or `syncBatch` continuations.
170
+ *
171
+ * `this.run()` is appropriate when you need the callback's **return value** —
172
+ * e.g., running a parent callback token that returns data. For fire-and-forget
173
+ * work, always prefer `this.runTask()`.
174
+ *
175
+ * @param token - The callback token to execute
176
+ * @param args - Optional arguments to pass to the callback
177
+ * @returns Promise resolving to the callback result
178
+ */
179
+ protected async run(token: Callback, ...args: []): Promise<any> {
180
+ return this.tools.callbacks.run(token, ...args);
181
+ }
182
+
183
+ /**
184
+ * Retrieves a value from persistent storage by key.
185
+ *
186
+ * Values are automatically deserialized using SuperJSON, which
187
+ * properly restores Date objects, Maps, Sets, and other complex types.
188
+ *
189
+ * @template T - The expected type of the stored value (must be Serializable)
190
+ * @param key - The storage key to retrieve
191
+ * @returns Promise resolving to the stored value or null
192
+ */
193
+ protected async get<T extends import("./index").Serializable>(
194
+ key: string
195
+ ): Promise<T | null> {
196
+ return this.tools.store.get(key);
197
+ }
198
+
199
+ /**
200
+ * Stores a value in persistent storage.
201
+ *
202
+ * The value will be serialized using SuperJSON and stored persistently.
203
+ * SuperJSON automatically handles Date objects, Maps, Sets, undefined values,
204
+ * and other complex types that standard JSON doesn't support.
205
+ *
206
+ * **Important**: Functions and Symbols cannot be stored.
207
+ * **For function references**: Use callbacks instead of storing functions directly.
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * // ✅ Date objects are preserved
212
+ * await this.set("sync_state", {
213
+ * lastSync: new Date(),
214
+ * minDate: new Date(2024, 0, 1)
215
+ * });
216
+ *
217
+ * // ✅ undefined is now supported
218
+ * await this.set("data", { name: "test", optional: undefined });
219
+ *
220
+ * // ❌ WRONG: Cannot store functions directly
221
+ * await this.set("handler", this.myHandler);
222
+ *
223
+ * // ✅ CORRECT: Create a callback token first
224
+ * const token = await this.callback(this.myHandler, "arg1", "arg2");
225
+ * await this.set("handler_token", token);
226
+ *
227
+ * // Later, execute the callback
228
+ * const token = await this.get<string>("handler_token");
229
+ * await this.run(token, args);
230
+ * ```
231
+ *
232
+ * @template T - The type of value being stored (must be Serializable)
233
+ * @param key - The storage key to use
234
+ * @param value - The value to store (must be SuperJSON-serializable)
235
+ * @returns Promise that resolves when the value is stored
236
+ */
237
+ protected async set<T extends import("./index").Serializable>(
238
+ key: string,
239
+ value: T
240
+ ): Promise<void> {
241
+ return this.tools.store.set(key, value);
242
+ }
243
+
244
+ /**
245
+ * Removes a specific key from persistent storage.
246
+ *
247
+ * @param key - The storage key to remove
248
+ * @returns Promise that resolves when the key is removed
249
+ */
250
+ protected async clear(key: string): Promise<void> {
251
+ return this.tools.store.clear(key);
252
+ }
253
+
254
+ /**
255
+ * Removes all keys from this twist's storage.
256
+ *
257
+ * @returns Promise that resolves when all keys are removed
258
+ */
259
+ protected async clearAll(): Promise<void> {
260
+ return this.tools.store.clearAll();
261
+ }
262
+
263
+ /**
264
+ * Queues a callback to execute in a separate worker context.
265
+ *
266
+ * @param callback - The callback token created with `this.callback()`
267
+ * @param options - Optional configuration for the execution
268
+ * @param options.runAt - If provided, schedules execution at this time; otherwise runs immediately
269
+ * @returns Promise resolving to a cancellation token (only for scheduled executions)
270
+ */
271
+ protected async runTask(
272
+ callback: Callback,
273
+ options?: { runAt?: Date }
274
+ ): Promise<string | void> {
275
+ return this.tools.tasks.runTask(callback, options);
276
+ }
277
+
278
+ /**
279
+ * Cancels a previously scheduled execution.
280
+ *
281
+ * @param token - The cancellation token returned by runTask() with runAt option
282
+ * @returns Promise that resolves when the cancellation is processed
283
+ */
284
+ protected async cancelTask(token: string): Promise<void> {
285
+ return this.tools.tasks.cancelTask(token);
286
+ }
287
+
288
+ /**
289
+ * Cancels all scheduled executions for this twist.
290
+ *
291
+ * @returns Promise that resolves when all cancellations are processed
292
+ */
293
+ protected async cancelAllTasks(): Promise<void> {
294
+ return this.tools.tasks.cancelAllTasks();
295
+ }
296
+
297
+ /**
298
+ * Called when the twist is installed by a user.
299
+ *
300
+ * This method should contain initialization logic such as seeding
301
+ * initial threads, configuring webhooks, or establishing external
302
+ * connections. When it runs, `this.userId` is already populated with
303
+ * the installing user's ID.
304
+ *
305
+ * @param context - Optional context containing the actor who triggered activation
306
+ * @returns Promise that resolves when activation is complete
307
+ */
308
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
309
+ activate(context?: { actor: Actor }): Promise<void> {
310
+ return Promise.resolve();
311
+ }
312
+
313
+ /**
314
+ * Called when a new version of the twist is deployed.
315
+ *
316
+ * This method should contain migration logic for updating old data structures
317
+ * or setting up new resources that weren't needed by the previous version.
318
+ * It is called once per active twist_instance with the new version.
319
+ *
320
+ * @returns Promise that resolves when upgrade is complete
321
+ */
322
+ upgrade(): Promise<void> {
323
+ return Promise.resolve();
324
+ }
325
+
326
+ /**
327
+ * Called when the twist's options configuration changes.
328
+ *
329
+ * Override to react to option changes, e.g. archiving items when a sync
330
+ * type is toggled off, or starting sync when a type is toggled on.
331
+ *
332
+ * @param oldOptions - The previously resolved options
333
+ * @param newOptions - The newly resolved options
334
+ * @returns Promise that resolves when the change is handled
335
+ */
336
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
337
+ onOptionsChanged(
338
+ oldOptions: Record<string, any>,
339
+ newOptions: Record<string, any>
340
+ ): Promise<void> {
341
+ return Promise.resolve();
342
+ }
343
+
344
+ /**
345
+ * Called when the twist is uninstalled.
346
+ *
347
+ * This method should contain cleanup logic such as removing webhooks,
348
+ * cleaning up external resources, or performing final data operations.
349
+ *
350
+ * @returns Promise that resolves when deactivation is complete
351
+ */
352
+ deactivate(): Promise<void> {
353
+ return Promise.resolve();
354
+ }
355
+
356
+ /**
357
+ * Called when a thread created by this twist is updated.
358
+ * Override to implement two-way sync with an external system.
359
+ *
360
+ * @param thread - The updated thread
361
+ * @param changes - Tag additions and removals on the thread
362
+ */
363
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
364
+ onThreadUpdated(
365
+ thread: Thread,
366
+ changes: {
367
+ tagsAdded: Record<Tag, ActorId[]>;
368
+ tagsRemoved: Record<Tag, ActorId[]>;
369
+ }
370
+ ): Promise<void> {
371
+ return Promise.resolve();
372
+ }
373
+
374
+ /**
375
+ * Called when a note is created on a thread created by this twist.
376
+ * Override to implement two-way sync (e.g. syncing notes as comments).
377
+ *
378
+ * Notes created by the twist itself are filtered out to prevent loops.
379
+ *
380
+ * Returning a string sets the note's `key` for future upsert matching,
381
+ * linking the Plot note to its external counterpart so that subsequent
382
+ * syncs (reactions, edits) update the existing note instead of creating duplicates.
383
+ *
384
+ * @param note - The newly created note
385
+ * @returns Optional note key for external deduplication
386
+ */
387
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
388
+ onNoteCreated(note: Note, ...args: any[]): Promise<string | NoteWriteBackResult | void> {
389
+ return Promise.resolve();
390
+ }
391
+
392
+ /**
393
+ * Called when a link is created in a connected source channel.
394
+ * Requires `link: true` in Plot options.
395
+ *
396
+ * @param link - The newly created link
397
+ * @param notes - Notes on the link's thread
398
+ */
399
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
400
+ onLinkCreated(link: Link, notes: Note[]): Promise<void> {
401
+ return Promise.resolve();
402
+ }
403
+
404
+ /**
405
+ * Called when a link in a connected source channel is updated.
406
+ * Requires `link: true` in Plot options.
407
+ *
408
+ * @param link - The updated link
409
+ * @param notes - Notes on the link's thread (optional)
410
+ */
411
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
412
+ onLinkUpdated(link: Link, notes?: Note[]): Promise<void> {
413
+ return Promise.resolve();
414
+ }
415
+
416
+ /**
417
+ * Called when a note is created on a thread with a link from a connected channel.
418
+ * Requires `link: true` in Plot options.
419
+ *
420
+ * @param note - The newly created note
421
+ * @param link - The link associated with the thread
422
+ */
423
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
424
+ onLinkNoteCreated(note: Note, link: Link): Promise<void> {
425
+ return Promise.resolve();
426
+ }
427
+
428
+ /**
429
+ * Waits for tool initialization to complete.
430
+ * Called automatically by the entrypoint before lifecycle methods.
431
+ * @internal
432
+ */
433
+ async waitForReady(): Promise<void> {
434
+ await this.toolShed.waitForReady();
435
+ }
436
+ }
@@ -0,0 +1,8 @@
1
+ export function quickHash(str: string) {
2
+ let hash = 0x811c9dc5;
3
+ for (let i = 0; i < str.length; ++i) {
4
+ hash ^= str.charCodeAt(i);
5
+ hash = (hash * 0x01000193) >>> 0;
6
+ }
7
+ return hash.toString(16).padStart(8, "0");
8
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Conversion helpers for connectors that bridge Plot markdown to external
3
+ * systems that store content as plain text (Google Drive comments, Todoist
4
+ * comments, Airtable cells, Attio notes, etc.).
5
+ *
6
+ * Both functions are pure and run in-process inside the connector's twist
7
+ * runtime — no RPC boundary is crossed, so they are cheap to call on every
8
+ * `onNoteCreated` / `onNoteUpdated`.
9
+ */
10
+
11
+ /**
12
+ * Render Markdown as plain text while preserving document structure.
13
+ *
14
+ * Numbered lists authored as `1. / 1. / 1.` in markdown are renumbered to
15
+ * `1. / 2. / 3.` so the plain-text reader sees real numbers. Bullet
16
+ * markers, headings content, blockquotes, and paragraph breaks survive;
17
+ * emphasis markers, code fences, and image syntax are dropped.
18
+ *
19
+ * Mentions `[Name](#@UUID)` render as `@Name`. Links `[text](url)`
20
+ * collapse to their label, falling back to the URL when the label is
21
+ * empty or identical to the URL.
22
+ *
23
+ * Use this when writing a Plot note to an external API that stores
24
+ * content verbatim as plain text. Pair the result with a matching
25
+ * `NoteWriteBackResult.externalContent` so the sync baseline round-trips.
26
+ */
27
+ export function markdownToPlainText(markdown: string): string {
28
+ let text = markdown;
29
+
30
+ text = text.replace(/```[a-zA-Z0-9]*\n([\s\S]*?)```/g, "$1");
31
+ text = text.replace(/`([^`]+)`/g, "$1");
32
+
33
+ text = text.replace(/!\[[^\]]*\]\([^)]*\)/g, "");
34
+
35
+ text = text.replace(/\[([^\]]+)\]\(#@[^)]+\)/g, "@$1");
36
+
37
+ text = text.replace(/\[([^\]]*)\]\(<?([^>)]+)>?\)/g, (_, label, url) => {
38
+ if (!label) return url;
39
+ if (label === url) return url;
40
+ return label;
41
+ });
42
+
43
+ text = text.replace(/^\s{0,3}#{1,6}\s+/gm, "");
44
+ text = text.replace(/^\s{0,3}>\s?/gm, "");
45
+
46
+ text = text.replace(/(\*\*|__)(.+?)\1/g, "$2");
47
+ text = text.replace(/(?<![*_])([*_])([^*_\n]+)\1(?![*_])/g, "$2");
48
+ text = text.replace(/~~(.+?)~~/g, "$1");
49
+
50
+ text = text.replace(/^\s{0,3}(?:-\s?){3,}\s*$/gm, "");
51
+ text = text.replace(/^\s{0,3}(?:\*\s?){3,}\s*$/gm, "");
52
+ text = text.replace(/^\s{0,3}(?:_\s?){3,}\s*$/gm, "");
53
+
54
+ text = renumberNumberedLists(text);
55
+
56
+ text = text
57
+ .replace(/&amp;/g, "&")
58
+ .replace(/&lt;/g, "<")
59
+ .replace(/&gt;/g, ">")
60
+ .replace(/&quot;/g, '"')
61
+ .replace(/&#39;/g, "'")
62
+ .replace(/&nbsp;/g, " ");
63
+
64
+ text = text.replace(/\n{3,}/g, "\n\n");
65
+
66
+ return text.trim();
67
+ }
68
+
69
+ function renumberNumberedLists(text: string): string {
70
+ const lines = text.split("\n");
71
+ const numberedRe = /^(\s*)(\d+)\.(\s+)(.*)$/;
72
+
73
+ let currentIndent: string | null = null;
74
+ let counter = 0;
75
+
76
+ for (let i = 0; i < lines.length; i++) {
77
+ const match = numberedRe.exec(lines[i]);
78
+ if (!match) {
79
+ currentIndent = null;
80
+ counter = 0;
81
+ continue;
82
+ }
83
+ const [, indent, , space, content] = match;
84
+ if (indent !== currentIndent) {
85
+ currentIndent = indent;
86
+ counter = 1;
87
+ } else {
88
+ counter += 1;
89
+ }
90
+ lines[i] = `${indent}${counter}.${space}${content}`;
91
+ }
92
+
93
+ return lines.join("\n");
94
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Types supported by SuperJSON serialization.
3
+ *
4
+ * SuperJSON extends standard JSON serialization to support additional JavaScript types
5
+ * while maintaining type safety and preventing common serialization errors.
6
+ *
7
+ * Supported types:
8
+ * - Primitives: string, number, boolean, null, undefined
9
+ * - Complex types: Date, RegExp, Map, Set, Error, URL, BigInt
10
+ * - Collections: Arrays and objects (recursively)
11
+ *
12
+ * NOT supported (will throw validation errors):
13
+ * - Functions
14
+ * - Symbols
15
+ * - Circular references
16
+ * - Custom class instances (unless explicitly registered)
17
+ */
18
+ export type Serializable =
19
+ | string
20
+ | number
21
+ | boolean
22
+ | null
23
+ | undefined
24
+ | Date
25
+ | RegExp
26
+ | Error
27
+ | URL
28
+ | bigint
29
+ | SerializableArray
30
+ | SerializableObject
31
+ | SerializableMap
32
+ | SerializableSet;
33
+
34
+ /**
35
+ * Array of serializable values
36
+ */
37
+ export interface SerializableArray extends Array<Serializable> {}
38
+
39
+ /**
40
+ * Object with string keys and serializable values
41
+ */
42
+ export interface SerializableObject {
43
+ [key: string]: Serializable;
44
+ }
45
+
46
+ /**
47
+ * Map with serializable keys and values
48
+ */
49
+ export interface SerializableMap extends Map<Serializable, Serializable> {}
50
+
51
+ /**
52
+ * Set with serializable values
53
+ */
54
+ export interface SerializableSet extends Set<Serializable> {}