@plotday/twister 0.27.0 → 0.28.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 (94) hide show
  1. package/README.md +10 -4
  2. package/bin/templates/AGENTS.template.md +91 -29
  3. package/cli/templates/AGENTS.template.md +91 -29
  4. package/dist/common/calendar.d.ts +18 -21
  5. package/dist/common/calendar.d.ts.map +1 -1
  6. package/dist/common/messaging.d.ts +14 -5
  7. package/dist/common/messaging.d.ts.map +1 -1
  8. package/dist/common/projects.d.ts +20 -10
  9. package/dist/common/projects.d.ts.map +1 -1
  10. package/dist/docs/assets/hierarchy.js +1 -1
  11. package/dist/docs/assets/search.js +1 -1
  12. package/dist/docs/classes/tool.Tool.html +20 -15
  13. package/dist/docs/classes/tools_ai.AI.html +1 -1
  14. package/dist/docs/classes/tools_callbacks.Callbacks.html +1 -1
  15. package/dist/docs/classes/tools_integrations.Integrations.html +1 -1
  16. package/dist/docs/classes/tools_network.Network.html +1 -1
  17. package/dist/docs/classes/tools_plot.Plot.html +2 -2
  18. package/dist/docs/classes/tools_store.Store.html +13 -4
  19. package/dist/docs/classes/tools_tasks.Tasks.html +1 -1
  20. package/dist/docs/classes/tools_twists.Twists.html +1 -1
  21. package/dist/docs/documents/Building_Custom_Tools.html +8 -2
  22. package/dist/docs/documents/Built-in_Tools.html +19 -8
  23. package/dist/docs/documents/Core_Concepts.html +14 -6
  24. package/dist/docs/documents/Getting_Started.html +11 -4
  25. package/dist/docs/enums/plot.ActorType.html +4 -4
  26. package/dist/docs/enums/tools_plot.ContactAccess.html +1 -1
  27. package/dist/docs/hierarchy.html +1 -1
  28. package/dist/docs/index.html +10 -0
  29. package/dist/docs/interfaces/common_calendar.CalendarTool.html +19 -11
  30. package/dist/docs/media/SYNC_STRATEGIES.md +651 -0
  31. package/dist/docs/types/plot.Activity.html +16 -7
  32. package/dist/docs/types/plot.ActivityCommon.html +4 -4
  33. package/dist/docs/types/plot.ActivityUpdate.html +4 -2
  34. package/dist/docs/types/plot.ActivityWithNotes.html +1 -1
  35. package/dist/docs/types/plot.Actor.html +5 -5
  36. package/dist/docs/types/plot.ContentType.html +1 -1
  37. package/dist/docs/types/plot.NewActivity.html +26 -12
  38. package/dist/docs/types/plot.NewActivityWithNotes.html +1 -1
  39. package/dist/docs/types/plot.NewActor.html +1 -1
  40. package/dist/docs/types/plot.NewContact.html +4 -4
  41. package/dist/docs/types/plot.NewNote.html +9 -4
  42. package/dist/docs/types/plot.Note.html +6 -3
  43. package/dist/docs/types/plot.NoteUpdate.html +4 -3
  44. package/dist/docs/types/plot.PickPriorityConfig.html +2 -2
  45. package/dist/docs/types/plot.SyncUpdate.html +1 -1
  46. package/dist/llm-docs/common/calendar.d.ts +1 -1
  47. package/dist/llm-docs/common/calendar.d.ts.map +1 -1
  48. package/dist/llm-docs/common/calendar.js +1 -1
  49. package/dist/llm-docs/common/calendar.js.map +1 -1
  50. package/dist/llm-docs/common/messaging.d.ts +1 -1
  51. package/dist/llm-docs/common/messaging.d.ts.map +1 -1
  52. package/dist/llm-docs/common/messaging.js +1 -1
  53. package/dist/llm-docs/common/messaging.js.map +1 -1
  54. package/dist/llm-docs/common/projects.d.ts +1 -1
  55. package/dist/llm-docs/common/projects.d.ts.map +1 -1
  56. package/dist/llm-docs/common/projects.js +1 -1
  57. package/dist/llm-docs/common/projects.js.map +1 -1
  58. package/dist/llm-docs/plot.d.ts +1 -1
  59. package/dist/llm-docs/plot.d.ts.map +1 -1
  60. package/dist/llm-docs/plot.js +1 -1
  61. package/dist/llm-docs/plot.js.map +1 -1
  62. package/dist/llm-docs/tool.d.ts +1 -1
  63. package/dist/llm-docs/tool.d.ts.map +1 -1
  64. package/dist/llm-docs/tool.js +1 -1
  65. package/dist/llm-docs/tool.js.map +1 -1
  66. package/dist/llm-docs/tools/plot.d.ts +1 -1
  67. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  68. package/dist/llm-docs/tools/plot.js +1 -1
  69. package/dist/llm-docs/tools/plot.js.map +1 -1
  70. package/dist/llm-docs/tools/store.d.ts +1 -1
  71. package/dist/llm-docs/tools/store.d.ts.map +1 -1
  72. package/dist/llm-docs/tools/store.js +1 -1
  73. package/dist/llm-docs/tools/store.js.map +1 -1
  74. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  75. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  76. package/dist/llm-docs/twist-guide-template.js +1 -1
  77. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  78. package/dist/plot.d.ts +109 -44
  79. package/dist/plot.d.ts.map +1 -1
  80. package/dist/plot.js.map +1 -1
  81. package/dist/tool.d.ts +16 -2
  82. package/dist/tool.d.ts.map +1 -1
  83. package/dist/tool.js +16 -2
  84. package/dist/tool.js.map +1 -1
  85. package/dist/tools/plot.d.ts +2 -2
  86. package/dist/tools/plot.d.ts.map +1 -1
  87. package/dist/tools/plot.js +1 -1
  88. package/dist/tools/plot.js.map +1 -1
  89. package/dist/tools/store.d.ts +16 -0
  90. package/dist/tools/store.d.ts.map +1 -1
  91. package/dist/tools/store.js.map +1 -1
  92. package/dist/twist-guide.d.ts +1 -1
  93. package/dist/twist-guide.d.ts.map +1 -1
  94. package/package.json +1 -1
@@ -4,6 +4,6 @@
4
4
  * This file is auto-generated during build. Do not edit manually.
5
5
  * Generated from: prebuild.ts
6
6
  */
7
- declare const _default: "import { ITool } from \"..\";\n\n/**\n * Built-in tool for persistent key-value storage.\n *\n * The Store tool provides twists and tools with a simple, persistent storage\n * mechanism that survives worker restarts and invocations. Each twist/tool\n * instance gets its own isolated storage namespace.\n *\n * **Note:** Store methods are also available directly on Twist and Tool classes\n * via `this.get()`, `this.set()`, `this.clear()`, and `this.clearAll()`.\n * This is the recommended approach for most use cases.\n *\n * **Storage Characteristics:**\n * - Persistent across worker restarts\n * - Isolated per twist/tool instance\n * - Supports any JSON-serializable data\n * - Async operations for scalability\n *\n * **Use Cases:**\n * - Storing authentication tokens\n * - Caching configuration data\n * - Maintaining sync state\n * - Persisting user preferences\n * - Tracking processing checkpoints\n *\n * @example\n * ```typescript\n * class CalendarTool extends Tool {\n * async saveAuthToken(provider: string, token: string) {\n * // Using built-in set method (recommended)\n * await this.set(`auth_token_${provider}`, token);\n * }\n *\n * async getAuthToken(provider: string): Promise<string | null> {\n * // Using built-in get method (recommended)\n * return await this.get<string>(`auth_token_${provider}`);\n * }\n *\n * async clearAllTokens() {\n * // Using built-in clearAll method (recommended)\n * await this.clearAll();\n * }\n * }\n * ```\n */\nexport abstract class Store extends ITool {\n /**\n * Retrieves a value from storage by key.\n *\n * Returns the stored value deserialized to the specified type,\n * or null if the key doesn't exist or the value is null.\n *\n * @template T - The expected type of the stored value\n * @param key - The storage key to retrieve\n * @returns Promise resolving to the stored value or null\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get<T>(key: string): Promise<T | null>;\n\n /**\n * Stores a value in persistent storage.\n *\n * The value will be JSON-serialized and stored persistently.\n * Any existing value at the same key will be overwritten.\n *\n * @template T - The type of value being stored\n * @param key - The storage key to use\n * @param value - The value to store (must be JSON-serializable)\n * @returns Promise that resolves when the value is stored\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract set<T>(key: string, value: T): Promise<void>;\n\n /**\n * Removes a specific key from storage.\n *\n * After this operation, get() calls for this key will return null.\n * No error is thrown if the key doesn't exist.\n *\n * @param key - The storage key to remove\n * @returns Promise that resolves when the key is removed\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract clear(key: string): Promise<void>;\n\n /**\n * Removes all keys from this storage instance.\n *\n * This operation clears all data stored by this twist/tool instance\n * but does not affect storage for other twists or tools.\n *\n * @returns Promise that resolves when all keys are removed\n */\n abstract clearAll(): Promise<void>;\n}\n";
7
+ declare const _default: "import { ITool } from \"..\";\n\n/**\n * Built-in tool for persistent key-value storage.\n *\n * The Store tool provides twists and tools with a simple, persistent storage\n * mechanism that survives worker restarts and invocations. Each twist/tool\n * instance gets its own isolated storage namespace.\n *\n * **Note:** Store methods are also available directly on Twist and Tool classes\n * via `this.get()`, `this.set()`, `this.clear()`, and `this.clearAll()`.\n * This is the recommended approach for most use cases.\n *\n * **Storage Characteristics:**\n * - Persistent across worker restarts\n * - Isolated per twist/tool instance\n * - Supports any JSON-serializable data\n * - Async operations for scalability\n *\n * **Use Cases:**\n * - Storing authentication tokens\n * - Caching configuration data\n * - Maintaining sync state\n * - Persisting user preferences\n * - Tracking processing checkpoints\n *\n * @example\n * ```typescript\n * class CalendarTool extends Tool {\n * async saveAuthToken(provider: string, token: string) {\n * // Using built-in set method (recommended)\n * await this.set(`auth_token_${provider}`, token);\n * }\n *\n * async getAuthToken(provider: string): Promise<string | null> {\n * // Using built-in get method (recommended)\n * return await this.get<string>(`auth_token_${provider}`);\n * }\n *\n * async clearAllTokens() {\n * // Using built-in clearAll method (recommended)\n * await this.clearAll();\n * }\n * }\n * ```\n */\nexport abstract class Store extends ITool {\n /**\n * Retrieves a value from storage by key.\n *\n * Returns the stored value deserialized to the specified type,\n * or null if the key doesn't exist or the value is null.\n *\n * @template T - The expected type of the stored value\n * @param key - The storage key to retrieve\n * @returns Promise resolving to the stored value or null\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get<T>(key: string): Promise<T | null>;\n\n /**\n * Stores a value in persistent storage.\n *\n * The value will be JSON-serialized and stored persistently.\n * Any existing value at the same key will be overwritten.\n *\n * **Handling undefined values:**\n * - Object keys with undefined values are automatically removed\n * - Arrays with undefined elements will throw a validation error\n * - Use null instead of undefined for array elements\n *\n * @template T - The type of value being stored\n * @param key - The storage key to use\n * @param value - The value to store (must be JSON-serializable)\n * @returns Promise that resolves when the value is stored\n *\n * @example\n * ```typescript\n * // Object keys with undefined are removed\n * await this.set('data', { name: 'test', optional: undefined });\n * // Stores: { name: 'test' }\n *\n * // Arrays with undefined throw errors - use null instead\n * await this.set('items', [1, null, 3]); // \u2705 Works\n * await this.set('items', [1, undefined, 3]); // \u274C Throws error\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract set<T>(key: string, value: T): Promise<void>;\n\n /**\n * Removes a specific key from storage.\n *\n * After this operation, get() calls for this key will return null.\n * No error is thrown if the key doesn't exist.\n *\n * @param key - The storage key to remove\n * @returns Promise that resolves when the key is removed\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract clear(key: string): Promise<void>;\n\n /**\n * Removes all keys from this storage instance.\n *\n * This operation clears all data stored by this twist/tool instance\n * but does not affect storage for other twists or tools.\n *\n * @returns Promise that resolves when all keys are removed\n */\n abstract clearAll(): Promise<void>;\n}\n";
8
8
  export default _default;
9
9
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/llm-docs/tools/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;wBAEY,8uGAA8uG;AAA7vG,wBAA8vG"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/llm-docs/tools/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;wBAEY,g3HAAs2H;AAAr3H,wBAAs3H"}
@@ -4,5 +4,5 @@
4
4
  * This file is auto-generated during build. Do not edit manually.
5
5
  * Generated from: prebuild.ts
6
6
  */
7
- export default "import { ITool } from \"..\";\n\n/**\n * Built-in tool for persistent key-value storage.\n *\n * The Store tool provides twists and tools with a simple, persistent storage\n * mechanism that survives worker restarts and invocations. Each twist/tool\n * instance gets its own isolated storage namespace.\n *\n * **Note:** Store methods are also available directly on Twist and Tool classes\n * via `this.get()`, `this.set()`, `this.clear()`, and `this.clearAll()`.\n * This is the recommended approach for most use cases.\n *\n * **Storage Characteristics:**\n * - Persistent across worker restarts\n * - Isolated per twist/tool instance\n * - Supports any JSON-serializable data\n * - Async operations for scalability\n *\n * **Use Cases:**\n * - Storing authentication tokens\n * - Caching configuration data\n * - Maintaining sync state\n * - Persisting user preferences\n * - Tracking processing checkpoints\n *\n * @example\n * ```typescript\n * class CalendarTool extends Tool {\n * async saveAuthToken(provider: string, token: string) {\n * // Using built-in set method (recommended)\n * await this.set(`auth_token_${provider}`, token);\n * }\n *\n * async getAuthToken(provider: string): Promise<string | null> {\n * // Using built-in get method (recommended)\n * return await this.get<string>(`auth_token_${provider}`);\n * }\n *\n * async clearAllTokens() {\n * // Using built-in clearAll method (recommended)\n * await this.clearAll();\n * }\n * }\n * ```\n */\nexport abstract class Store extends ITool {\n /**\n * Retrieves a value from storage by key.\n *\n * Returns the stored value deserialized to the specified type,\n * or null if the key doesn't exist or the value is null.\n *\n * @template T - The expected type of the stored value\n * @param key - The storage key to retrieve\n * @returns Promise resolving to the stored value or null\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get<T>(key: string): Promise<T | null>;\n\n /**\n * Stores a value in persistent storage.\n *\n * The value will be JSON-serialized and stored persistently.\n * Any existing value at the same key will be overwritten.\n *\n * @template T - The type of value being stored\n * @param key - The storage key to use\n * @param value - The value to store (must be JSON-serializable)\n * @returns Promise that resolves when the value is stored\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract set<T>(key: string, value: T): Promise<void>;\n\n /**\n * Removes a specific key from storage.\n *\n * After this operation, get() calls for this key will return null.\n * No error is thrown if the key doesn't exist.\n *\n * @param key - The storage key to remove\n * @returns Promise that resolves when the key is removed\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract clear(key: string): Promise<void>;\n\n /**\n * Removes all keys from this storage instance.\n *\n * This operation clears all data stored by this twist/tool instance\n * but does not affect storage for other twists or tools.\n *\n * @returns Promise that resolves when all keys are removed\n */\n abstract clearAll(): Promise<void>;\n}\n";
7
+ export default "import { ITool } from \"..\";\n\n/**\n * Built-in tool for persistent key-value storage.\n *\n * The Store tool provides twists and tools with a simple, persistent storage\n * mechanism that survives worker restarts and invocations. Each twist/tool\n * instance gets its own isolated storage namespace.\n *\n * **Note:** Store methods are also available directly on Twist and Tool classes\n * via `this.get()`, `this.set()`, `this.clear()`, and `this.clearAll()`.\n * This is the recommended approach for most use cases.\n *\n * **Storage Characteristics:**\n * - Persistent across worker restarts\n * - Isolated per twist/tool instance\n * - Supports any JSON-serializable data\n * - Async operations for scalability\n *\n * **Use Cases:**\n * - Storing authentication tokens\n * - Caching configuration data\n * - Maintaining sync state\n * - Persisting user preferences\n * - Tracking processing checkpoints\n *\n * @example\n * ```typescript\n * class CalendarTool extends Tool {\n * async saveAuthToken(provider: string, token: string) {\n * // Using built-in set method (recommended)\n * await this.set(`auth_token_${provider}`, token);\n * }\n *\n * async getAuthToken(provider: string): Promise<string | null> {\n * // Using built-in get method (recommended)\n * return await this.get<string>(`auth_token_${provider}`);\n * }\n *\n * async clearAllTokens() {\n * // Using built-in clearAll method (recommended)\n * await this.clearAll();\n * }\n * }\n * ```\n */\nexport abstract class Store extends ITool {\n /**\n * Retrieves a value from storage by key.\n *\n * Returns the stored value deserialized to the specified type,\n * or null if the key doesn't exist or the value is null.\n *\n * @template T - The expected type of the stored value\n * @param key - The storage key to retrieve\n * @returns Promise resolving to the stored value or null\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get<T>(key: string): Promise<T | null>;\n\n /**\n * Stores a value in persistent storage.\n *\n * The value will be JSON-serialized and stored persistently.\n * Any existing value at the same key will be overwritten.\n *\n * **Handling undefined values:**\n * - Object keys with undefined values are automatically removed\n * - Arrays with undefined elements will throw a validation error\n * - Use null instead of undefined for array elements\n *\n * @template T - The type of value being stored\n * @param key - The storage key to use\n * @param value - The value to store (must be JSON-serializable)\n * @returns Promise that resolves when the value is stored\n *\n * @example\n * ```typescript\n * // Object keys with undefined are removed\n * await this.set('data', { name: 'test', optional: undefined });\n * // Stores: { name: 'test' }\n *\n * // Arrays with undefined throw errors - use null instead\n * await this.set('items', [1, null, 3]); // ✅ Works\n * await this.set('items', [1, undefined, 3]); // ❌ Throws error\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract set<T>(key: string, value: T): Promise<void>;\n\n /**\n * Removes a specific key from storage.\n *\n * After this operation, get() calls for this key will return null.\n * No error is thrown if the key doesn't exist.\n *\n * @param key - The storage key to remove\n * @returns Promise that resolves when the key is removed\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract clear(key: string): Promise<void>;\n\n /**\n * Removes all keys from this storage instance.\n *\n * This operation clears all data stored by this twist/tool instance\n * but does not affect storage for other twists or tools.\n *\n * @returns Promise that resolves when all keys are removed\n */\n abstract clearAll(): Promise<void>;\n}\n";
8
8
  //# sourceMappingURL=store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/llm-docs/tools/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAe,8uGAA8uG,CAAC"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/llm-docs/tools/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAe,s2HAAs2H,CAAC"}
@@ -4,6 +4,6 @@
4
4
  * This file is auto-generated during build. Do not edit manually.
5
5
  * Generated from: cli/templates/AGENTS.template.md
6
6
  */
7
- declare const _default: "# Twist Implementation Guide for LLMs\n\nThis document provides context for AI assistants generating or modifying twists.\n\n## Architecture Overview\n\nPlot Twists are TypeScript classes that extend the `Twist` base class. Twists interact with external services and Plot's core functionality through a tool-based architecture.\n\n### Runtime Environment\n\n**Critical**: All Twists and tool functions are executed in a sandboxed, ephemeral environment with limited resources:\n\n- **Memory is temporary**: Anything stored in memory (e.g. as a variable in the twist/tool object) is lost after the function completes. Use the Store tool instead. Only use memory for temporary caching.\n- **Limited CPU time**: Each execution has limited CPU time (typically 10 seconds) and memory (128MB)\n- **Use the Run tool**: Queue separate chunks of work with `run.now(functionName, context)`\n- **Break long operations**: Split large operations into smaller batches that can be processed independently\n- **Store intermediate state**: Use the Store tool to persist state between batches\n- **Examples**: Syncing large datasets, processing many API calls, or performing batch operations\n\n## Understanding Activities and Notes\n\n**CRITICAL CONCEPT**: An **Activity** represents something done or to be done (a task, event, or conversation), while **Notes** represent the updates and details on that activity.\n\n**Think of an Activity as a thread** on a messaging platform, and **Notes as the messages in that thread**.\n\n### Key Guidelines\n\n1. **Always create Activities with an initial Note** - The title is just a summary; detailed content goes in Notes\n2. **Add Notes to existing Activities for updates** - Don't create a new Activity for each related message\n3. **Track external items using generated UUIDs** - Store mappings between external IDs and Plot UUIDs for deduplication\n4. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `doneAt`, use `Event` only for items with `start`/`end`\n\n### Decision Tree\n\n```\nNew event/task/conversation?\n \u251C\u2500 Yes \u2192 Generate UUID with Uuid.Generate()\n \u2502 Create new Activity with that UUID\n \u2502 Store mapping: external_id \u2192 activity_uuid\n \u2502\n \u2514\u2500 No (update/reply/comment) \u2192 Look up mapping by external_id\n \u251C\u2500 Found \u2192 Add Note to existing Activity using stored UUID\n \u2514\u2500 Not found \u2192 Create new Activity with UUID + store mapping\n```\n\n## Twist Structure Pattern\n\n```typescript\nimport {\n type Activity,\n type Priority,\n type ToolBuilder,\n twist,\n} from \"@plotday/twister\";\nimport { Plot } from \"@plotday/twister/tools/plot\";\nimport { Uuid } from \"@plotday/twister/utils/uuid\";\n\nexport default class MyTwist extends Twist<MyTwist> {\n build(build: ToolBuilder) {\n return {\n plot: build(Plot),\n };\n }\n\n async activate(priority: Pick<Priority, \"id\">) {\n // Called when twist is enabled for a priority\n // Common actions: request auth, create setup activities\n }\n\n async activity(activity: Activity) {\n // Called when an activity is routed to this twist\n // Common actions: process external events, update activities\n }\n}\n```\n\n## Tool System\n\n### Accessing Tools\n\nAll tools are declared in the `build` method:\n\n```typescript\nbuild(build: ToolBuilder) {\n return {\n toolName: build(ToolClass),\n };\n}\n```\n\nAll `build()` calls must occur in the `build` method as they are used for dependency analysis.\n\nIMPORTANT: HTTP access is restricted to URLs requested via `build(Network, { urls: [url1, url2, ...] })` in the `build` method. Wildcards are supported. Use `build(Network, { urls: ['*'] })` if full access is needed.\n\n### Built-in Tools (Always Available)\n\nFor complete API documentation of built-in tools including all methods, types, and detailed examples, see the TypeScript definitions in your installed package at `node_modules/@plotday/twister/src/tools/*.ts`. Each tool file contains comprehensive JSDoc documentation.\n\n**Quick reference - Available tools:**\n\n- `@plotday/twister/tools/plot` - Core data layer (create/update activities, priorities, contacts)\n- `@plotday/twister/tools/ai` - LLM integration (text generation, structured output, reasoning)\n - Use ModelPreferences to specify `speed` (fast/balanced/capable) and `cost` (low/medium/high)\n- `@plotday/twister/tools/store` - Persistent key-value storage (also via `this.set()`, `this.get()`)\n- `@plotday/twister/tools/tasks` - Queue batched work (also via `this.run()`)\n- `@plotday/twister/tools/callbacks` - Persistent function references (also via `this.callback()`)\n- `@plotday/twister/tools/integrations` - OAuth2 authentication flows\n- `@plotday/twister/tools/network` - HTTP access permissions and webhook management\n- `@plotday/twister/tools/twists` - Manage other Twists\n\n**Critical**: Never use instance variables for state. They are lost after function execution. Always use Store methods.\n\n### External Tools (Add to package.json)\n\nAdd tool dependencies to `package.json`:\n\n```json\n{\n \"dependencies\": {\n \"@plotday/twister\": \"workspace:^\",\n \"@plotday/tool-google-calendar\": \"workspace:^\"\n }\n}\n```\n\n#### Common External Tools\n\n- `@plotday/tool-google-calendar`: Google Calendar integration\n- `@plotday/tool-outlook-calendar`: Outlook Calendar integration\n- `@plotday/tool-google-contacts`: Google Contacts integration\n\n## Lifecycle Methods\n\n### activate(priority: Pick<Priority, \"id\">)\n\nCalled when the twist is enabled for a priority. Common patterns:\n\n**Request Authentication:**\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n const authLink = await this.tools.externalTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect your account\",\n notes: [\n {\n content: \"Click the link below to connect your account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n}\n```\n\n**Store Parent Activity for Later:**\n\n```typescript\nconst activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Setup\",\n notes: [\n {\n content: \"Your twist is being set up. Configuration steps will appear here.\",\n },\n ],\n});\n\nawait this.set(\"setup_activity_id\", activity.id);\n```\n\n### activity(activity: Activity)\n\nCalled when an activity is routed to the twist. Common patterns:\n\n**Create Activities from External Events:**\n\n```typescript\nasync activity(activity: Activity) {\n await this.tools.plot.createActivity(activity);\n}\n```\n\n**Update Based on User Action:**\n\n```typescript\nasync activity(activity: Activity) {\n if (activity.completed) {\n await this.handleCompletion(activity);\n }\n}\n```\n\n## Activity Links\n\nActivity links enable user interaction:\n\n```typescript\nimport { type ActivityLink, ActivityLinkType } from \"@plotday/twister\";\n\n// URL link\nconst urlLink: ActivityLink = {\n title: \"Open website\",\n type: ActivityLinkType.url,\n url: \"https://example.com\",\n};\n\n// Callback link (uses Callbacks tool)\nconst token = await this.callback(this.onLinkClicked, \"context\");\nconst callbackLink: ActivityLink = {\n title: \"Click me\",\n type: ActivityLinkType.callback,\n token: token,\n};\n\n// Add to activity note\nawait this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Task with links\",\n notes: [\n {\n content: \"Click the links below to take action.\",\n links: [urlLink, callbackLink],\n },\n ],\n});\n```\n\n## Authentication Pattern\n\nCommon pattern for OAuth authentication:\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n // Request auth link from tool with callback\n const authLink = await this.tools.googleTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n // Create activity with auth link\n const activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect Google account\",\n notes: [\n {\n content: \"Click below to connect your Google account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n\n // Store for later use\n await this.set(\"auth_activity_id\", activity.id);\n}\n\nasync onAuthComplete(authResult: { authToken: string }, provider: string) {\n // Store auth token\n await this.set(`${provider}_auth`, authResult.authToken);\n\n // Continue setup flow\n await this.setupSyncOptions(authResult.authToken);\n}\n```\n\n## Sync Pattern\n\nPattern for syncing external data - demonstrates adding Notes to existing Activities:\n\n```typescript\nasync startSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n\n await this.tools.calendarTool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n calendarId\n );\n}\n\nasync handleEvent(\n incomingActivity: NewActivityWithNotes,\n calendarId: string\n): Promise<void> {\n // Extract external event ID from meta (adapt based on your tool's data)\n const externalId = incomingActivity.meta?.eventId;\n\n if (!externalId) {\n console.error(\"Event missing external ID\");\n return;\n }\n\n // Check if we've already synced this event\n const mappingKey = `event_mapping:${calendarId}:${externalId}`;\n const existingActivityId = await this.get<Uuid>(mappingKey);\n\n if (existingActivityId) {\n // Event already exists - add update as a Note (add message to thread)\n if (incomingActivity.notes?.[0]?.content) {\n await this.tools.plot.createNote({\n activity: { id: existingActivityId },\n content: incomingActivity.notes[0].content,\n });\n }\n return;\n }\n\n // New event - generate UUID and store mapping\n const activityId = Uuid.Generate();\n await this.set(mappingKey, activityId);\n\n // Create new Activity with initial Note (new thread with first message)\n await this.tools.plot.createActivity({\n ...incomingActivity,\n id: activityId,\n });\n}\n\nasync stopSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n await this.tools.calendarTool.stopSync(authToken, calendarId);\n}\n```\n\n## Calendar Selection Pattern\n\nPattern for letting users select from multiple calendars/accounts:\n\n```typescript\nprivate async createCalendarSelectionActivity(\n provider: string,\n calendars: Calendar[],\n authToken: string\n): Promise<void> {\n const links: ActivityLink[] = [];\n\n for (const calendar of calendars) {\n const token = await this.callback(\n this.onCalendarSelected,\n provider,\n calendar.id,\n calendar.name,\n authToken\n );\n\n links.push({\n title: `\uD83D\uDCC5 ${calendar.name}${calendar.primary ? \" (Primary)\" : \"\"}`,\n type: ActivityLinkType.callback,\n token: token,\n });\n }\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Which calendars would you like to connect?\",\n notes: [\n {\n content: \"Select the calendars you want to sync:\",\n links,\n },\n ],\n });\n}\n\nasync onCalendarSelected(\n link: ActivityLink,\n provider: string,\n calendarId: string,\n calendarName: string,\n authToken: string\n): Promise<void> {\n // Start sync for selected calendar\n await this.tools.tool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n provider,\n calendarId\n );\n}\n```\n\n## Batch Processing Pattern\n\n**Important**: Because Twists run in an ephemeral environment with limited execution time, you must break long operations into batches. Each batch runs independently in a new execution context.\n\n### Key Principles\n\n1. **Store state between batches**: Use the Store tool to persist progress\n2. **Queue next batch**: Use the Run tool to schedule the next chunk\n3. **Clean up when done**: Delete stored state after completion\n4. **Handle failures**: Store enough state to resume if a batch fails\n\n### Example Implementation\n\n```typescript\nasync startSync(resourceId: string): Promise<void> {\n // Initialize state in Store (persists between executions)\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: null,\n batchNumber: 1,\n itemsProcessed: 0,\n });\n\n // Queue first batch using runTask method\n const callback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(callback);\n}\n\nasync syncBatch(args: any, resourceId: string): Promise<void> {\n // Load state from Store (set by previous execution)\n const state = await this.get(`sync_state_${resourceId}`);\n\n // Process one batch (keep under time limit)\n const result = await this.fetchBatch(state.nextPageToken);\n\n // Process results (create activities with Notes)\n for (const item of result.items) {\n // Check if already synced\n const mappingKey = `item_mapping:${resourceId}:${item.id}`;\n const existingId = await this.get<Uuid>(mappingKey);\n\n if (!existingId) {\n // New item - generate UUID and store mapping\n const activityId = Uuid.Generate();\n await this.set(mappingKey, activityId);\n\n await this.tools.plot.createActivity({\n id: activityId,\n type: ActivityType.Note,\n title: item.title,\n notes: [{ id: Uuid.Generate(), content: item.description }],\n });\n }\n }\n\n if (result.nextPageToken) {\n // Update state in Store for next batch\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: result.nextPageToken,\n batchNumber: state.batchNumber + 1,\n itemsProcessed: state.itemsProcessed + result.items.length,\n });\n\n // Queue next batch (runs in new execution context)\n const nextCallback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(nextCallback);\n } else {\n // Cleanup when complete\n await this.clear(`sync_state_${resourceId}`);\n\n // Optionally notify user of completion\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Sync complete\",\n notes: [\n {\n content: `Successfully processed ${state.itemsProcessed + result.items.length} items.`,\n },\n ],\n });\n }\n}\n```\n\n## Error Handling\n\nAlways handle errors gracefully and communicate them to users:\n\n```typescript\ntry {\n await this.externalOperation();\n} catch (error) {\n console.error(\"Operation failed:\", error);\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Operation failed\",\n notes: [\n {\n content: `Failed to complete operation: ${error.message}`,\n },\n ],\n });\n}\n```\n\n## Common Pitfalls\n\n- **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.\n- **Processing self-created activities** - Other users may change an Activity created by the twist, resulting in an \\`activity\\` call. Be sure to check the \\`changes === null\\` and/or \\`activity.author.id !== this.id\\` to avoid re-processing.\n- **Always create Activities with Notes** - See \"Understanding Activities and Notes\" section above for the thread/message pattern and decision tree.\n- **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `doneAt`, and `Event` for items with `start`/`end`.\n- **Track external items with UUID mappings** - Generate UUIDs with `Uuid.Generate()` and store mappings (`external_id \u2192 uuid`) for deduplication. Never rely on the `source` field.\n- **Add Notes to existing Activities** - Look up stored UUID mappings before creating new Activities. Think thread replies, not new threads.\n- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.\n- **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Tasks tool. Process enough items per batch to be efficient, but few enough to stay under time limits.\n- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.\n- **Store auth tokens** - Don't re-request authentication unnecessarily.\n- **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.\n- **Handle missing auth gracefully** - Check for stored auth before operations.\n\n## Testing\n\nBefore deploying, verify:\n\n1. Linting passes: `{{packageManager}} lint`\n2. All dependencies are in package.json\n3. Authentication flow works end-to-end\n4. Batch operations handle pagination correctly\n5. Error cases are handled gracefully\n";
7
+ declare const _default: "# Twist Implementation Guide for LLMs\n\nThis document provides context for AI assistants generating or modifying twists.\n\n## Architecture Overview\n\nPlot Twists are TypeScript classes that extend the `Twist` base class. Twists interact with external services and Plot's core functionality through a tool-based architecture.\n\n### Runtime Environment\n\n**Critical**: All Twists and tool functions are executed in a sandboxed, ephemeral environment with limited resources:\n\n- **Memory is temporary**: Anything stored in memory (e.g. as a variable in the twist/tool object) is lost after the function completes. Use the Store tool instead. Only use memory for temporary caching.\n- **Limited CPU time**: Each execution has limited CPU time (typically 10 seconds) and memory (128MB)\n- **Use the Run tool**: Queue separate chunks of work with `run.now(functionName, context)`\n- **Break long operations**: Split large operations into smaller batches that can be processed independently\n- **Store intermediate state**: Use the Store tool to persist state between batches\n- **Examples**: Syncing large datasets, processing many API calls, or performing batch operations\n\n## Understanding Activities and Notes\n\n**CRITICAL CONCEPT**: An **Activity** represents something done or to be done (a task, event, or conversation), while **Notes** represent the updates and details on that activity.\n\n**Think of an Activity as a thread** on a messaging platform, and **Notes as the messages in that thread**.\n\n### Key Guidelines\n\n1. **Always create Activities with an initial Note** - The title is just a summary; detailed content goes in Notes\n2. **Add Notes to existing Activities for updates** - Don't create a new Activity for each related message\n3. **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for deduplication, and use Note.key for upsertable note content. No manual ID tracking needed.\n4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot activities per external item (see SYNC_STRATEGIES.md)\n5. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `done`, use `Event` only for items with `start`/`end`\n\n### Recommended Decision Tree (Strategy 2: Upsert via Source/Key)\n\n```\nNew event/task/conversation from external system?\n \u251C\u2500 Has stable URL or ID?\n \u2502 \u2514\u2500 Yes \u2192 Set Activity.source to the canonical URL/ID\n \u2502 Create Activity (Plot handles deduplication automatically)\n \u2502 Use Note.key for different note types:\n \u2502 - \"description\" for main content\n \u2502 - \"metadata\" for status/priority/assignee\n \u2502 - \"comment-{id}\" for individual comments\n \u2502\n \u2514\u2500 No stable identifier OR need multiple Plot activities per external item?\n \u2514\u2500 Use Advanced Pattern (Strategy 3: Generate and Store IDs)\n See SYNC_STRATEGIES.md for details\n```\n\n### Advanced Decision Tree (Strategy 3: Generate and Store IDs)\n\nOnly use when source/key upserts aren't sufficient (e.g., creating multiple activities from one external item):\n\n```\nNew event/task/conversation?\n \u251C\u2500 Yes \u2192 Generate UUID with Uuid.Generate()\n \u2502 Create new Activity with that UUID\n \u2502 Store mapping: external_id \u2192 activity_uuid\n \u2502\n \u2514\u2500 No (update/reply/comment) \u2192 Look up mapping by external_id\n \u251C\u2500 Found \u2192 Add Note to existing Activity using stored UUID\n \u2514\u2500 Not found \u2192 Create new Activity with UUID + store mapping\n```\n\n## Twist Structure Pattern\n\n```typescript\nimport {\n type Activity,\n type Priority,\n type ToolBuilder,\n twist,\n} from \"@plotday/twister\";\nimport { Plot } from \"@plotday/twister/tools/plot\";\nimport { Uuid } from \"@plotday/twister/utils/uuid\";\n\nexport default class MyTwist extends Twist<MyTwist> {\n build(build: ToolBuilder) {\n return {\n plot: build(Plot),\n };\n }\n\n async activate(priority: Pick<Priority, \"id\">) {\n // Called when twist is enabled for a priority\n // Common actions: request auth, create setup activities\n }\n\n async activity(activity: Activity) {\n // Called when an activity is routed to this twist\n // Common actions: process external events, update activities\n }\n}\n```\n\n## Tool System\n\n### Accessing Tools\n\nAll tools are declared in the `build` method:\n\n```typescript\nbuild(build: ToolBuilder) {\n return {\n toolName: build(ToolClass),\n };\n}\n```\n\nAll `build()` calls must occur in the `build` method as they are used for dependency analysis.\n\nIMPORTANT: HTTP access is restricted to URLs requested via `build(Network, { urls: [url1, url2, ...] })` in the `build` method. Wildcards are supported. Use `build(Network, { urls: ['*'] })` if full access is needed.\n\n### Built-in Tools (Always Available)\n\nFor complete API documentation of built-in tools including all methods, types, and detailed examples, see the TypeScript definitions in your installed package at `node_modules/@plotday/twister/src/tools/*.ts`. Each tool file contains comprehensive JSDoc documentation.\n\n**Quick reference - Available tools:**\n\n- `@plotday/twister/tools/plot` - Core data layer (create/update activities, priorities, contacts)\n- `@plotday/twister/tools/ai` - LLM integration (text generation, structured output, reasoning)\n - Use ModelPreferences to specify `speed` (fast/balanced/capable) and `cost` (low/medium/high)\n- `@plotday/twister/tools/store` - Persistent key-value storage (also via `this.set()`, `this.get()`)\n- `@plotday/twister/tools/tasks` - Queue batched work (also via `this.run()`)\n- `@plotday/twister/tools/callbacks` - Persistent function references (also via `this.callback()`)\n- `@plotday/twister/tools/integrations` - OAuth2 authentication flows\n- `@plotday/twister/tools/network` - HTTP access permissions and webhook management\n- `@plotday/twister/tools/twists` - Manage other Twists\n\n**Critical**: Never use instance variables for state. They are lost after function execution. Always use Store methods.\n\n### External Tools (Add to package.json)\n\nAdd tool dependencies to `package.json`:\n\n```json\n{\n \"dependencies\": {\n \"@plotday/twister\": \"workspace:^\",\n \"@plotday/tool-google-calendar\": \"workspace:^\"\n }\n}\n```\n\n#### Common External Tools\n\n- `@plotday/tool-google-calendar`: Google Calendar integration\n- `@plotday/tool-outlook-calendar`: Outlook Calendar integration\n- `@plotday/tool-google-contacts`: Google Contacts integration\n\n## Lifecycle Methods\n\n### activate(priority: Pick<Priority, \"id\">)\n\nCalled when the twist is enabled for a priority. Common patterns:\n\n**Request Authentication:**\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n const authLink = await this.tools.externalTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect your account\",\n notes: [\n {\n content: \"Click the link below to connect your account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n}\n```\n\n**Store Parent Activity for Later:**\n\n```typescript\nconst activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Setup\",\n notes: [\n {\n content: \"Your twist is being set up. Configuration steps will appear here.\",\n },\n ],\n});\n\nawait this.set(\"setup_activity_id\", activity.id);\n```\n\n### activity(activity: Activity)\n\nCalled when an activity is routed to the twist. Common patterns:\n\n**Create Activities from External Events:**\n\n```typescript\nasync activity(activity: Activity) {\n await this.tools.plot.createActivity(activity);\n}\n```\n\n**Update Based on User Action:**\n\n```typescript\nasync activity(activity: Activity) {\n if (activity.completed) {\n await this.handleCompletion(activity);\n }\n}\n```\n\n## Activity Links\n\nActivity links enable user interaction:\n\n```typescript\nimport { type ActivityLink, ActivityLinkType } from \"@plotday/twister\";\n\n// URL link\nconst urlLink: ActivityLink = {\n title: \"Open website\",\n type: ActivityLinkType.url,\n url: \"https://example.com\",\n};\n\n// Callback link (uses Callbacks tool)\nconst token = await this.callback(this.onLinkClicked, \"context\");\nconst callbackLink: ActivityLink = {\n title: \"Click me\",\n type: ActivityLinkType.callback,\n token: token,\n};\n\n// Add to activity note\nawait this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Task with links\",\n notes: [\n {\n content: \"Click the links below to take action.\",\n links: [urlLink, callbackLink],\n },\n ],\n});\n```\n\n## Authentication Pattern\n\nCommon pattern for OAuth authentication:\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n // Request auth link from tool with callback\n const authLink = await this.tools.googleTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n // Create activity with auth link\n const activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect Google account\",\n notes: [\n {\n content: \"Click below to connect your Google account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n\n // Store for later use\n await this.set(\"auth_activity_id\", activity.id);\n}\n\nasync onAuthComplete(authResult: { authToken: string }, provider: string) {\n // Store auth token\n await this.set(`${provider}_auth`, authResult.authToken);\n\n // Continue setup flow\n await this.setupSyncOptions(authResult.authToken);\n}\n```\n\n## Sync Pattern\n\n### Recommended: Upsert via Source/Key (Strategy 2)\n\nPattern for syncing external data using automatic upserts - **no manual ID tracking needed**:\n\n```typescript\nasync startSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n\n await this.tools.calendarTool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n calendarId\n );\n}\n\nasync handleEvent(\n event: ExternalEvent,\n calendarId: string\n): Promise<void> {\n // Use the event's canonical URL as the source for automatic deduplication\n const activity: NewActivityWithNotes = {\n source: event.htmlLink, // or event.url, depending on your external system\n type: ActivityType.Event,\n title: event.summary || \"(No title)\",\n start: event.start?.dateTime || event.start?.date || null,\n end: event.end?.dateTime || event.end?.date || null,\n notes: [],\n };\n\n // Add description as an upsertable note\n if (event.description) {\n activity.notes.push({\n activity: { source: event.htmlLink },\n key: \"description\", // This key enables upserts - same key updates the note\n content: event.description,\n });\n }\n\n // Add attendees as an upsertable note\n if (event.attendees?.length) {\n const attendeeList = event.attendees\n .map(a => `- ${a.email}${a.displayName ? ` (${a.displayName})` : ''}`)\n .join('\\n');\n\n activity.notes.push({\n activity: { source: event.htmlLink },\n key: \"attendees\", // Different key for different note types\n content: `## Attendees\\n${attendeeList}`,\n });\n }\n\n // Create or update - Plot automatically handles deduplication based on source\n await this.tools.plot.createActivity(activity);\n}\n\nasync stopSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n await this.tools.calendarTool.stopSync(authToken, calendarId);\n}\n```\n\n### Advanced: Generate and Store IDs (Strategy 3)\n\nOnly use this pattern when you need to create multiple Plot activities from a single external item, or when the external system doesn't provide stable identifiers. See SYNC_STRATEGIES.md for details.\n\n```typescript\nasync handleEventAdvanced(\n incomingActivity: NewActivityWithNotes,\n calendarId: string\n): Promise<void> {\n // Extract external event ID from meta (adapt based on your tool's data)\n const externalId = incomingActivity.meta?.eventId;\n\n if (!externalId) {\n console.error(\"Event missing external ID\");\n return;\n }\n\n // Check if we've already synced this event\n const mappingKey = `event_mapping:${calendarId}:${externalId}`;\n const existingActivityId = await this.get<Uuid>(mappingKey);\n\n if (existingActivityId) {\n // Event already exists - add update as a Note (add message to thread)\n if (incomingActivity.notes?.[0]?.content) {\n await this.tools.plot.createNote({\n activity: { id: existingActivityId },\n content: incomingActivity.notes[0].content,\n });\n }\n return;\n }\n\n // New event - generate UUID and store mapping\n const activityId = Uuid.Generate();\n await this.set(mappingKey, activityId);\n\n // Create new Activity with initial Note (new thread with first message)\n await this.tools.plot.createActivity({\n ...incomingActivity,\n id: activityId,\n });\n}\n```\n\n## Calendar Selection Pattern\n\nPattern for letting users select from multiple calendars/accounts:\n\n```typescript\nprivate async createCalendarSelectionActivity(\n provider: string,\n calendars: Calendar[],\n authToken: string\n): Promise<void> {\n const links: ActivityLink[] = [];\n\n for (const calendar of calendars) {\n const token = await this.callback(\n this.onCalendarSelected,\n provider,\n calendar.id,\n calendar.name,\n authToken\n );\n\n links.push({\n title: `\uD83D\uDCC5 ${calendar.name}${calendar.primary ? \" (Primary)\" : \"\"}`,\n type: ActivityLinkType.callback,\n token: token,\n });\n }\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Which calendars would you like to connect?\",\n notes: [\n {\n content: \"Select the calendars you want to sync:\",\n links,\n },\n ],\n });\n}\n\nasync onCalendarSelected(\n link: ActivityLink,\n provider: string,\n calendarId: string,\n calendarName: string,\n authToken: string\n): Promise<void> {\n // Start sync for selected calendar\n await this.tools.tool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n provider,\n calendarId\n );\n}\n```\n\n## Batch Processing Pattern\n\n**Important**: Because Twists run in an ephemeral environment with limited execution time, you must break long operations into batches. Each batch runs independently in a new execution context.\n\n### Key Principles\n\n1. **Store state between batches**: Use the Store tool to persist progress\n2. **Queue next batch**: Use the Run tool to schedule the next chunk\n3. **Clean up when done**: Delete stored state after completion\n4. **Handle failures**: Store enough state to resume if a batch fails\n\n### Example Implementation\n\n```typescript\nasync startSync(resourceId: string): Promise<void> {\n // Initialize state in Store (persists between executions)\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: null,\n batchNumber: 1,\n itemsProcessed: 0,\n });\n\n // Queue first batch using runTask method\n const callback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(callback);\n}\n\nasync syncBatch(args: any, resourceId: string): Promise<void> {\n // Load state from Store (set by previous execution)\n const state = await this.get(`sync_state_${resourceId}`);\n\n // Process one batch (keep under time limit)\n const result = await this.fetchBatch(state.nextPageToken);\n\n // Process results using source/key pattern (automatic upserts, no manual tracking)\n for (const item of result.items) {\n await this.tools.plot.createActivity({\n source: item.url, // Use item's canonical URL for automatic deduplication\n type: ActivityType.Note,\n title: item.title,\n notes: [{\n activity: { source: item.url },\n key: \"description\", // Use key for upsertable notes\n content: item.description,\n }],\n });\n }\n\n if (result.nextPageToken) {\n // Update state in Store for next batch\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: result.nextPageToken,\n batchNumber: state.batchNumber + 1,\n itemsProcessed: state.itemsProcessed + result.items.length,\n });\n\n // Queue next batch (runs in new execution context)\n const nextCallback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(nextCallback);\n } else {\n // Cleanup when complete\n await this.clear(`sync_state_${resourceId}`);\n\n // Optionally notify user of completion\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Sync complete\",\n notes: [\n {\n content: `Successfully processed ${state.itemsProcessed + result.items.length} items.`,\n },\n ],\n });\n }\n}\n```\n\n## Error Handling\n\nAlways handle errors gracefully and communicate them to users:\n\n```typescript\ntry {\n await this.externalOperation();\n} catch (error) {\n console.error(\"Operation failed:\", error);\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Operation failed\",\n notes: [\n {\n content: `Failed to complete operation: ${error.message}`,\n },\n ],\n });\n}\n```\n\n## Common Pitfalls\n\n- **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.\n- **Processing self-created activities** - Other users may change an Activity created by the twist, resulting in an \\`activity\\` call. Be sure to check the \\`changes === null\\` and/or \\`activity.author.id !== this.id\\` to avoid re-processing.\n- **Always create Activities with Notes** - See \"Understanding Activities and Notes\" section above for the thread/message pattern and decision tree.\n- **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `done`, and `Event` for items with `start`/`end`.\n- **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for automatic deduplication. Only use UUID generation and storage for advanced cases (see SYNC_STRATEGIES.md).\n- **Add Notes to existing Activities** - For source/key pattern, reference activities by source. For UUID pattern, look up stored mappings before creating new Activities. Think thread replies, not new threads.\n- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.\n- **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Tasks tool. Process enough items per batch to be efficient, but few enough to stay under time limits.\n- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.\n- **Store auth tokens** - Don't re-request authentication unnecessarily.\n- **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.\n- **Handle missing auth gracefully** - Check for stored auth before operations.\n\n## Testing\n\nBefore deploying, verify:\n\n1. Linting passes: `{{packageManager}} lint`\n2. All dependencies are in package.json\n3. Authentication flow works end-to-end\n4. Batch operations handle pagination correctly\n5. Error cases are handled gracefully\n";
8
8
  export default _default;
9
9
  //# sourceMappingURL=twist-guide-template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"twist-guide-template.d.ts","sourceRoot":"","sources":["../../src/llm-docs/twist-guide-template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;wBAEY,0lhBAA2/gB;AAA1ghB,wBAA2ghB"}
1
+ {"version":3,"file":"twist-guide-template.d.ts","sourceRoot":"","sources":["../../src/llm-docs/twist-guide-template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;wBAEY,+4mBAAqumB;AAApvmB,wBAAqvmB"}
@@ -4,5 +4,5 @@
4
4
  * This file is auto-generated during build. Do not edit manually.
5
5
  * Generated from: cli/templates/AGENTS.template.md
6
6
  */
7
- export default "# Twist Implementation Guide for LLMs\n\nThis document provides context for AI assistants generating or modifying twists.\n\n## Architecture Overview\n\nPlot Twists are TypeScript classes that extend the `Twist` base class. Twists interact with external services and Plot's core functionality through a tool-based architecture.\n\n### Runtime Environment\n\n**Critical**: All Twists and tool functions are executed in a sandboxed, ephemeral environment with limited resources:\n\n- **Memory is temporary**: Anything stored in memory (e.g. as a variable in the twist/tool object) is lost after the function completes. Use the Store tool instead. Only use memory for temporary caching.\n- **Limited CPU time**: Each execution has limited CPU time (typically 10 seconds) and memory (128MB)\n- **Use the Run tool**: Queue separate chunks of work with `run.now(functionName, context)`\n- **Break long operations**: Split large operations into smaller batches that can be processed independently\n- **Store intermediate state**: Use the Store tool to persist state between batches\n- **Examples**: Syncing large datasets, processing many API calls, or performing batch operations\n\n## Understanding Activities and Notes\n\n**CRITICAL CONCEPT**: An **Activity** represents something done or to be done (a task, event, or conversation), while **Notes** represent the updates and details on that activity.\n\n**Think of an Activity as a thread** on a messaging platform, and **Notes as the messages in that thread**.\n\n### Key Guidelines\n\n1. **Always create Activities with an initial Note** - The title is just a summary; detailed content goes in Notes\n2. **Add Notes to existing Activities for updates** - Don't create a new Activity for each related message\n3. **Track external items using generated UUIDs** - Store mappings between external IDs and Plot UUIDs for deduplication\n4. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `doneAt`, use `Event` only for items with `start`/`end`\n\n### Decision Tree\n\n```\nNew event/task/conversation?\n ├─ Yes → Generate UUID with Uuid.Generate()\n │ Create new Activity with that UUID\n │ Store mapping: external_id → activity_uuid\n │\n └─ No (update/reply/comment) → Look up mapping by external_id\n ├─ Found → Add Note to existing Activity using stored UUID\n └─ Not found → Create new Activity with UUID + store mapping\n```\n\n## Twist Structure Pattern\n\n```typescript\nimport {\n type Activity,\n type Priority,\n type ToolBuilder,\n twist,\n} from \"@plotday/twister\";\nimport { Plot } from \"@plotday/twister/tools/plot\";\nimport { Uuid } from \"@plotday/twister/utils/uuid\";\n\nexport default class MyTwist extends Twist<MyTwist> {\n build(build: ToolBuilder) {\n return {\n plot: build(Plot),\n };\n }\n\n async activate(priority: Pick<Priority, \"id\">) {\n // Called when twist is enabled for a priority\n // Common actions: request auth, create setup activities\n }\n\n async activity(activity: Activity) {\n // Called when an activity is routed to this twist\n // Common actions: process external events, update activities\n }\n}\n```\n\n## Tool System\n\n### Accessing Tools\n\nAll tools are declared in the `build` method:\n\n```typescript\nbuild(build: ToolBuilder) {\n return {\n toolName: build(ToolClass),\n };\n}\n```\n\nAll `build()` calls must occur in the `build` method as they are used for dependency analysis.\n\nIMPORTANT: HTTP access is restricted to URLs requested via `build(Network, { urls: [url1, url2, ...] })` in the `build` method. Wildcards are supported. Use `build(Network, { urls: ['*'] })` if full access is needed.\n\n### Built-in Tools (Always Available)\n\nFor complete API documentation of built-in tools including all methods, types, and detailed examples, see the TypeScript definitions in your installed package at `node_modules/@plotday/twister/src/tools/*.ts`. Each tool file contains comprehensive JSDoc documentation.\n\n**Quick reference - Available tools:**\n\n- `@plotday/twister/tools/plot` - Core data layer (create/update activities, priorities, contacts)\n- `@plotday/twister/tools/ai` - LLM integration (text generation, structured output, reasoning)\n - Use ModelPreferences to specify `speed` (fast/balanced/capable) and `cost` (low/medium/high)\n- `@plotday/twister/tools/store` - Persistent key-value storage (also via `this.set()`, `this.get()`)\n- `@plotday/twister/tools/tasks` - Queue batched work (also via `this.run()`)\n- `@plotday/twister/tools/callbacks` - Persistent function references (also via `this.callback()`)\n- `@plotday/twister/tools/integrations` - OAuth2 authentication flows\n- `@plotday/twister/tools/network` - HTTP access permissions and webhook management\n- `@plotday/twister/tools/twists` - Manage other Twists\n\n**Critical**: Never use instance variables for state. They are lost after function execution. Always use Store methods.\n\n### External Tools (Add to package.json)\n\nAdd tool dependencies to `package.json`:\n\n```json\n{\n \"dependencies\": {\n \"@plotday/twister\": \"workspace:^\",\n \"@plotday/tool-google-calendar\": \"workspace:^\"\n }\n}\n```\n\n#### Common External Tools\n\n- `@plotday/tool-google-calendar`: Google Calendar integration\n- `@plotday/tool-outlook-calendar`: Outlook Calendar integration\n- `@plotday/tool-google-contacts`: Google Contacts integration\n\n## Lifecycle Methods\n\n### activate(priority: Pick<Priority, \"id\">)\n\nCalled when the twist is enabled for a priority. Common patterns:\n\n**Request Authentication:**\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n const authLink = await this.tools.externalTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect your account\",\n notes: [\n {\n content: \"Click the link below to connect your account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n}\n```\n\n**Store Parent Activity for Later:**\n\n```typescript\nconst activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Setup\",\n notes: [\n {\n content: \"Your twist is being set up. Configuration steps will appear here.\",\n },\n ],\n});\n\nawait this.set(\"setup_activity_id\", activity.id);\n```\n\n### activity(activity: Activity)\n\nCalled when an activity is routed to the twist. Common patterns:\n\n**Create Activities from External Events:**\n\n```typescript\nasync activity(activity: Activity) {\n await this.tools.plot.createActivity(activity);\n}\n```\n\n**Update Based on User Action:**\n\n```typescript\nasync activity(activity: Activity) {\n if (activity.completed) {\n await this.handleCompletion(activity);\n }\n}\n```\n\n## Activity Links\n\nActivity links enable user interaction:\n\n```typescript\nimport { type ActivityLink, ActivityLinkType } from \"@plotday/twister\";\n\n// URL link\nconst urlLink: ActivityLink = {\n title: \"Open website\",\n type: ActivityLinkType.url,\n url: \"https://example.com\",\n};\n\n// Callback link (uses Callbacks tool)\nconst token = await this.callback(this.onLinkClicked, \"context\");\nconst callbackLink: ActivityLink = {\n title: \"Click me\",\n type: ActivityLinkType.callback,\n token: token,\n};\n\n// Add to activity note\nawait this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Task with links\",\n notes: [\n {\n content: \"Click the links below to take action.\",\n links: [urlLink, callbackLink],\n },\n ],\n});\n```\n\n## Authentication Pattern\n\nCommon pattern for OAuth authentication:\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n // Request auth link from tool with callback\n const authLink = await this.tools.googleTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n // Create activity with auth link\n const activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect Google account\",\n notes: [\n {\n content: \"Click below to connect your Google account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n\n // Store for later use\n await this.set(\"auth_activity_id\", activity.id);\n}\n\nasync onAuthComplete(authResult: { authToken: string }, provider: string) {\n // Store auth token\n await this.set(`${provider}_auth`, authResult.authToken);\n\n // Continue setup flow\n await this.setupSyncOptions(authResult.authToken);\n}\n```\n\n## Sync Pattern\n\nPattern for syncing external data - demonstrates adding Notes to existing Activities:\n\n```typescript\nasync startSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n\n await this.tools.calendarTool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n calendarId\n );\n}\n\nasync handleEvent(\n incomingActivity: NewActivityWithNotes,\n calendarId: string\n): Promise<void> {\n // Extract external event ID from meta (adapt based on your tool's data)\n const externalId = incomingActivity.meta?.eventId;\n\n if (!externalId) {\n console.error(\"Event missing external ID\");\n return;\n }\n\n // Check if we've already synced this event\n const mappingKey = `event_mapping:${calendarId}:${externalId}`;\n const existingActivityId = await this.get<Uuid>(mappingKey);\n\n if (existingActivityId) {\n // Event already exists - add update as a Note (add message to thread)\n if (incomingActivity.notes?.[0]?.content) {\n await this.tools.plot.createNote({\n activity: { id: existingActivityId },\n content: incomingActivity.notes[0].content,\n });\n }\n return;\n }\n\n // New event - generate UUID and store mapping\n const activityId = Uuid.Generate();\n await this.set(mappingKey, activityId);\n\n // Create new Activity with initial Note (new thread with first message)\n await this.tools.plot.createActivity({\n ...incomingActivity,\n id: activityId,\n });\n}\n\nasync stopSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n await this.tools.calendarTool.stopSync(authToken, calendarId);\n}\n```\n\n## Calendar Selection Pattern\n\nPattern for letting users select from multiple calendars/accounts:\n\n```typescript\nprivate async createCalendarSelectionActivity(\n provider: string,\n calendars: Calendar[],\n authToken: string\n): Promise<void> {\n const links: ActivityLink[] = [];\n\n for (const calendar of calendars) {\n const token = await this.callback(\n this.onCalendarSelected,\n provider,\n calendar.id,\n calendar.name,\n authToken\n );\n\n links.push({\n title: `📅 ${calendar.name}${calendar.primary ? \" (Primary)\" : \"\"}`,\n type: ActivityLinkType.callback,\n token: token,\n });\n }\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Which calendars would you like to connect?\",\n notes: [\n {\n content: \"Select the calendars you want to sync:\",\n links,\n },\n ],\n });\n}\n\nasync onCalendarSelected(\n link: ActivityLink,\n provider: string,\n calendarId: string,\n calendarName: string,\n authToken: string\n): Promise<void> {\n // Start sync for selected calendar\n await this.tools.tool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n provider,\n calendarId\n );\n}\n```\n\n## Batch Processing Pattern\n\n**Important**: Because Twists run in an ephemeral environment with limited execution time, you must break long operations into batches. Each batch runs independently in a new execution context.\n\n### Key Principles\n\n1. **Store state between batches**: Use the Store tool to persist progress\n2. **Queue next batch**: Use the Run tool to schedule the next chunk\n3. **Clean up when done**: Delete stored state after completion\n4. **Handle failures**: Store enough state to resume if a batch fails\n\n### Example Implementation\n\n```typescript\nasync startSync(resourceId: string): Promise<void> {\n // Initialize state in Store (persists between executions)\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: null,\n batchNumber: 1,\n itemsProcessed: 0,\n });\n\n // Queue first batch using runTask method\n const callback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(callback);\n}\n\nasync syncBatch(args: any, resourceId: string): Promise<void> {\n // Load state from Store (set by previous execution)\n const state = await this.get(`sync_state_${resourceId}`);\n\n // Process one batch (keep under time limit)\n const result = await this.fetchBatch(state.nextPageToken);\n\n // Process results (create activities with Notes)\n for (const item of result.items) {\n // Check if already synced\n const mappingKey = `item_mapping:${resourceId}:${item.id}`;\n const existingId = await this.get<Uuid>(mappingKey);\n\n if (!existingId) {\n // New item - generate UUID and store mapping\n const activityId = Uuid.Generate();\n await this.set(mappingKey, activityId);\n\n await this.tools.plot.createActivity({\n id: activityId,\n type: ActivityType.Note,\n title: item.title,\n notes: [{ id: Uuid.Generate(), content: item.description }],\n });\n }\n }\n\n if (result.nextPageToken) {\n // Update state in Store for next batch\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: result.nextPageToken,\n batchNumber: state.batchNumber + 1,\n itemsProcessed: state.itemsProcessed + result.items.length,\n });\n\n // Queue next batch (runs in new execution context)\n const nextCallback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(nextCallback);\n } else {\n // Cleanup when complete\n await this.clear(`sync_state_${resourceId}`);\n\n // Optionally notify user of completion\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Sync complete\",\n notes: [\n {\n content: `Successfully processed ${state.itemsProcessed + result.items.length} items.`,\n },\n ],\n });\n }\n}\n```\n\n## Error Handling\n\nAlways handle errors gracefully and communicate them to users:\n\n```typescript\ntry {\n await this.externalOperation();\n} catch (error) {\n console.error(\"Operation failed:\", error);\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Operation failed\",\n notes: [\n {\n content: `Failed to complete operation: ${error.message}`,\n },\n ],\n });\n}\n```\n\n## Common Pitfalls\n\n- **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.\n- **Processing self-created activities** - Other users may change an Activity created by the twist, resulting in an \\`activity\\` call. Be sure to check the \\`changes === null\\` and/or \\`activity.author.id !== this.id\\` to avoid re-processing.\n- **Always create Activities with Notes** - See \"Understanding Activities and Notes\" section above for the thread/message pattern and decision tree.\n- **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `doneAt`, and `Event` for items with `start`/`end`.\n- **Track external items with UUID mappings** - Generate UUIDs with `Uuid.Generate()` and store mappings (`external_id → uuid`) for deduplication. Never rely on the `source` field.\n- **Add Notes to existing Activities** - Look up stored UUID mappings before creating new Activities. Think thread replies, not new threads.\n- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.\n- **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Tasks tool. Process enough items per batch to be efficient, but few enough to stay under time limits.\n- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.\n- **Store auth tokens** - Don't re-request authentication unnecessarily.\n- **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.\n- **Handle missing auth gracefully** - Check for stored auth before operations.\n\n## Testing\n\nBefore deploying, verify:\n\n1. Linting passes: `{{packageManager}} lint`\n2. All dependencies are in package.json\n3. Authentication flow works end-to-end\n4. Batch operations handle pagination correctly\n5. Error cases are handled gracefully\n";
7
+ export default "# Twist Implementation Guide for LLMs\n\nThis document provides context for AI assistants generating or modifying twists.\n\n## Architecture Overview\n\nPlot Twists are TypeScript classes that extend the `Twist` base class. Twists interact with external services and Plot's core functionality through a tool-based architecture.\n\n### Runtime Environment\n\n**Critical**: All Twists and tool functions are executed in a sandboxed, ephemeral environment with limited resources:\n\n- **Memory is temporary**: Anything stored in memory (e.g. as a variable in the twist/tool object) is lost after the function completes. Use the Store tool instead. Only use memory for temporary caching.\n- **Limited CPU time**: Each execution has limited CPU time (typically 10 seconds) and memory (128MB)\n- **Use the Run tool**: Queue separate chunks of work with `run.now(functionName, context)`\n- **Break long operations**: Split large operations into smaller batches that can be processed independently\n- **Store intermediate state**: Use the Store tool to persist state between batches\n- **Examples**: Syncing large datasets, processing many API calls, or performing batch operations\n\n## Understanding Activities and Notes\n\n**CRITICAL CONCEPT**: An **Activity** represents something done or to be done (a task, event, or conversation), while **Notes** represent the updates and details on that activity.\n\n**Think of an Activity as a thread** on a messaging platform, and **Notes as the messages in that thread**.\n\n### Key Guidelines\n\n1. **Always create Activities with an initial Note** - The title is just a summary; detailed content goes in Notes\n2. **Add Notes to existing Activities for updates** - Don't create a new Activity for each related message\n3. **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for deduplication, and use Note.key for upsertable note content. No manual ID tracking needed.\n4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot activities per external item (see SYNC_STRATEGIES.md)\n5. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `done`, use `Event` only for items with `start`/`end`\n\n### Recommended Decision Tree (Strategy 2: Upsert via Source/Key)\n\n```\nNew event/task/conversation from external system?\n ├─ Has stable URL or ID?\n │ └─ Yes → Set Activity.source to the canonical URL/ID\n │ Create Activity (Plot handles deduplication automatically)\n │ Use Note.key for different note types:\n │ - \"description\" for main content\n │ - \"metadata\" for status/priority/assignee\n │ - \"comment-{id}\" for individual comments\n │\n └─ No stable identifier OR need multiple Plot activities per external item?\n └─ Use Advanced Pattern (Strategy 3: Generate and Store IDs)\n See SYNC_STRATEGIES.md for details\n```\n\n### Advanced Decision Tree (Strategy 3: Generate and Store IDs)\n\nOnly use when source/key upserts aren't sufficient (e.g., creating multiple activities from one external item):\n\n```\nNew event/task/conversation?\n ├─ Yes → Generate UUID with Uuid.Generate()\n │ Create new Activity with that UUID\n │ Store mapping: external_id → activity_uuid\n │\n └─ No (update/reply/comment) → Look up mapping by external_id\n ├─ Found → Add Note to existing Activity using stored UUID\n └─ Not found → Create new Activity with UUID + store mapping\n```\n\n## Twist Structure Pattern\n\n```typescript\nimport {\n type Activity,\n type Priority,\n type ToolBuilder,\n twist,\n} from \"@plotday/twister\";\nimport { Plot } from \"@plotday/twister/tools/plot\";\nimport { Uuid } from \"@plotday/twister/utils/uuid\";\n\nexport default class MyTwist extends Twist<MyTwist> {\n build(build: ToolBuilder) {\n return {\n plot: build(Plot),\n };\n }\n\n async activate(priority: Pick<Priority, \"id\">) {\n // Called when twist is enabled for a priority\n // Common actions: request auth, create setup activities\n }\n\n async activity(activity: Activity) {\n // Called when an activity is routed to this twist\n // Common actions: process external events, update activities\n }\n}\n```\n\n## Tool System\n\n### Accessing Tools\n\nAll tools are declared in the `build` method:\n\n```typescript\nbuild(build: ToolBuilder) {\n return {\n toolName: build(ToolClass),\n };\n}\n```\n\nAll `build()` calls must occur in the `build` method as they are used for dependency analysis.\n\nIMPORTANT: HTTP access is restricted to URLs requested via `build(Network, { urls: [url1, url2, ...] })` in the `build` method. Wildcards are supported. Use `build(Network, { urls: ['*'] })` if full access is needed.\n\n### Built-in Tools (Always Available)\n\nFor complete API documentation of built-in tools including all methods, types, and detailed examples, see the TypeScript definitions in your installed package at `node_modules/@plotday/twister/src/tools/*.ts`. Each tool file contains comprehensive JSDoc documentation.\n\n**Quick reference - Available tools:**\n\n- `@plotday/twister/tools/plot` - Core data layer (create/update activities, priorities, contacts)\n- `@plotday/twister/tools/ai` - LLM integration (text generation, structured output, reasoning)\n - Use ModelPreferences to specify `speed` (fast/balanced/capable) and `cost` (low/medium/high)\n- `@plotday/twister/tools/store` - Persistent key-value storage (also via `this.set()`, `this.get()`)\n- `@plotday/twister/tools/tasks` - Queue batched work (also via `this.run()`)\n- `@plotday/twister/tools/callbacks` - Persistent function references (also via `this.callback()`)\n- `@plotday/twister/tools/integrations` - OAuth2 authentication flows\n- `@plotday/twister/tools/network` - HTTP access permissions and webhook management\n- `@plotday/twister/tools/twists` - Manage other Twists\n\n**Critical**: Never use instance variables for state. They are lost after function execution. Always use Store methods.\n\n### External Tools (Add to package.json)\n\nAdd tool dependencies to `package.json`:\n\n```json\n{\n \"dependencies\": {\n \"@plotday/twister\": \"workspace:^\",\n \"@plotday/tool-google-calendar\": \"workspace:^\"\n }\n}\n```\n\n#### Common External Tools\n\n- `@plotday/tool-google-calendar`: Google Calendar integration\n- `@plotday/tool-outlook-calendar`: Outlook Calendar integration\n- `@plotday/tool-google-contacts`: Google Contacts integration\n\n## Lifecycle Methods\n\n### activate(priority: Pick<Priority, \"id\">)\n\nCalled when the twist is enabled for a priority. Common patterns:\n\n**Request Authentication:**\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n const authLink = await this.tools.externalTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect your account\",\n notes: [\n {\n content: \"Click the link below to connect your account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n}\n```\n\n**Store Parent Activity for Later:**\n\n```typescript\nconst activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Setup\",\n notes: [\n {\n content: \"Your twist is being set up. Configuration steps will appear here.\",\n },\n ],\n});\n\nawait this.set(\"setup_activity_id\", activity.id);\n```\n\n### activity(activity: Activity)\n\nCalled when an activity is routed to the twist. Common patterns:\n\n**Create Activities from External Events:**\n\n```typescript\nasync activity(activity: Activity) {\n await this.tools.plot.createActivity(activity);\n}\n```\n\n**Update Based on User Action:**\n\n```typescript\nasync activity(activity: Activity) {\n if (activity.completed) {\n await this.handleCompletion(activity);\n }\n}\n```\n\n## Activity Links\n\nActivity links enable user interaction:\n\n```typescript\nimport { type ActivityLink, ActivityLinkType } from \"@plotday/twister\";\n\n// URL link\nconst urlLink: ActivityLink = {\n title: \"Open website\",\n type: ActivityLinkType.url,\n url: \"https://example.com\",\n};\n\n// Callback link (uses Callbacks tool)\nconst token = await this.callback(this.onLinkClicked, \"context\");\nconst callbackLink: ActivityLink = {\n title: \"Click me\",\n type: ActivityLinkType.callback,\n token: token,\n};\n\n// Add to activity note\nawait this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Task with links\",\n notes: [\n {\n content: \"Click the links below to take action.\",\n links: [urlLink, callbackLink],\n },\n ],\n});\n```\n\n## Authentication Pattern\n\nCommon pattern for OAuth authentication:\n\n```typescript\nasync activate(_priority: Pick<Priority, \"id\">) {\n // Request auth link from tool with callback\n const authLink = await this.tools.googleTool.requestAuth(\n this.onAuthComplete,\n \"google\"\n );\n\n // Create activity with auth link\n const activity = await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Connect Google account\",\n notes: [\n {\n content: \"Click below to connect your Google account and start syncing.\",\n links: [authLink],\n },\n ],\n });\n\n // Store for later use\n await this.set(\"auth_activity_id\", activity.id);\n}\n\nasync onAuthComplete(authResult: { authToken: string }, provider: string) {\n // Store auth token\n await this.set(`${provider}_auth`, authResult.authToken);\n\n // Continue setup flow\n await this.setupSyncOptions(authResult.authToken);\n}\n```\n\n## Sync Pattern\n\n### Recommended: Upsert via Source/Key (Strategy 2)\n\nPattern for syncing external data using automatic upserts - **no manual ID tracking needed**:\n\n```typescript\nasync startSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n\n await this.tools.calendarTool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n calendarId\n );\n}\n\nasync handleEvent(\n event: ExternalEvent,\n calendarId: string\n): Promise<void> {\n // Use the event's canonical URL as the source for automatic deduplication\n const activity: NewActivityWithNotes = {\n source: event.htmlLink, // or event.url, depending on your external system\n type: ActivityType.Event,\n title: event.summary || \"(No title)\",\n start: event.start?.dateTime || event.start?.date || null,\n end: event.end?.dateTime || event.end?.date || null,\n notes: [],\n };\n\n // Add description as an upsertable note\n if (event.description) {\n activity.notes.push({\n activity: { source: event.htmlLink },\n key: \"description\", // This key enables upserts - same key updates the note\n content: event.description,\n });\n }\n\n // Add attendees as an upsertable note\n if (event.attendees?.length) {\n const attendeeList = event.attendees\n .map(a => `- ${a.email}${a.displayName ? ` (${a.displayName})` : ''}`)\n .join('\\n');\n\n activity.notes.push({\n activity: { source: event.htmlLink },\n key: \"attendees\", // Different key for different note types\n content: `## Attendees\\n${attendeeList}`,\n });\n }\n\n // Create or update - Plot automatically handles deduplication based on source\n await this.tools.plot.createActivity(activity);\n}\n\nasync stopSync(calendarId: string): Promise<void> {\n const authToken = await this.get<string>(\"auth_token\");\n await this.tools.calendarTool.stopSync(authToken, calendarId);\n}\n```\n\n### Advanced: Generate and Store IDs (Strategy 3)\n\nOnly use this pattern when you need to create multiple Plot activities from a single external item, or when the external system doesn't provide stable identifiers. See SYNC_STRATEGIES.md for details.\n\n```typescript\nasync handleEventAdvanced(\n incomingActivity: NewActivityWithNotes,\n calendarId: string\n): Promise<void> {\n // Extract external event ID from meta (adapt based on your tool's data)\n const externalId = incomingActivity.meta?.eventId;\n\n if (!externalId) {\n console.error(\"Event missing external ID\");\n return;\n }\n\n // Check if we've already synced this event\n const mappingKey = `event_mapping:${calendarId}:${externalId}`;\n const existingActivityId = await this.get<Uuid>(mappingKey);\n\n if (existingActivityId) {\n // Event already exists - add update as a Note (add message to thread)\n if (incomingActivity.notes?.[0]?.content) {\n await this.tools.plot.createNote({\n activity: { id: existingActivityId },\n content: incomingActivity.notes[0].content,\n });\n }\n return;\n }\n\n // New event - generate UUID and store mapping\n const activityId = Uuid.Generate();\n await this.set(mappingKey, activityId);\n\n // Create new Activity with initial Note (new thread with first message)\n await this.tools.plot.createActivity({\n ...incomingActivity,\n id: activityId,\n });\n}\n```\n\n## Calendar Selection Pattern\n\nPattern for letting users select from multiple calendars/accounts:\n\n```typescript\nprivate async createCalendarSelectionActivity(\n provider: string,\n calendars: Calendar[],\n authToken: string\n): Promise<void> {\n const links: ActivityLink[] = [];\n\n for (const calendar of calendars) {\n const token = await this.callback(\n this.onCalendarSelected,\n provider,\n calendar.id,\n calendar.name,\n authToken\n );\n\n links.push({\n title: `📅 ${calendar.name}${calendar.primary ? \" (Primary)\" : \"\"}`,\n type: ActivityLinkType.callback,\n token: token,\n });\n }\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Which calendars would you like to connect?\",\n notes: [\n {\n content: \"Select the calendars you want to sync:\",\n links,\n },\n ],\n });\n}\n\nasync onCalendarSelected(\n link: ActivityLink,\n provider: string,\n calendarId: string,\n calendarName: string,\n authToken: string\n): Promise<void> {\n // Start sync for selected calendar\n await this.tools.tool.startSync(\n authToken,\n calendarId,\n this.handleEvent,\n provider,\n calendarId\n );\n}\n```\n\n## Batch Processing Pattern\n\n**Important**: Because Twists run in an ephemeral environment with limited execution time, you must break long operations into batches. Each batch runs independently in a new execution context.\n\n### Key Principles\n\n1. **Store state between batches**: Use the Store tool to persist progress\n2. **Queue next batch**: Use the Run tool to schedule the next chunk\n3. **Clean up when done**: Delete stored state after completion\n4. **Handle failures**: Store enough state to resume if a batch fails\n\n### Example Implementation\n\n```typescript\nasync startSync(resourceId: string): Promise<void> {\n // Initialize state in Store (persists between executions)\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: null,\n batchNumber: 1,\n itemsProcessed: 0,\n });\n\n // Queue first batch using runTask method\n const callback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(callback);\n}\n\nasync syncBatch(args: any, resourceId: string): Promise<void> {\n // Load state from Store (set by previous execution)\n const state = await this.get(`sync_state_${resourceId}`);\n\n // Process one batch (keep under time limit)\n const result = await this.fetchBatch(state.nextPageToken);\n\n // Process results using source/key pattern (automatic upserts, no manual tracking)\n for (const item of result.items) {\n await this.tools.plot.createActivity({\n source: item.url, // Use item's canonical URL for automatic deduplication\n type: ActivityType.Note,\n title: item.title,\n notes: [{\n activity: { source: item.url },\n key: \"description\", // Use key for upsertable notes\n content: item.description,\n }],\n });\n }\n\n if (result.nextPageToken) {\n // Update state in Store for next batch\n await this.set(`sync_state_${resourceId}`, {\n nextPageToken: result.nextPageToken,\n batchNumber: state.batchNumber + 1,\n itemsProcessed: state.itemsProcessed + result.items.length,\n });\n\n // Queue next batch (runs in new execution context)\n const nextCallback = await this.callback(this.syncBatch, resourceId);\n await this.runTask(nextCallback);\n } else {\n // Cleanup when complete\n await this.clear(`sync_state_${resourceId}`);\n\n // Optionally notify user of completion\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Sync complete\",\n notes: [\n {\n content: `Successfully processed ${state.itemsProcessed + result.items.length} items.`,\n },\n ],\n });\n }\n}\n```\n\n## Error Handling\n\nAlways handle errors gracefully and communicate them to users:\n\n```typescript\ntry {\n await this.externalOperation();\n} catch (error) {\n console.error(\"Operation failed:\", error);\n\n await this.tools.plot.createActivity({\n type: ActivityType.Note,\n title: \"Operation failed\",\n notes: [\n {\n content: `Failed to complete operation: ${error.message}`,\n },\n ],\n });\n}\n```\n\n## Common Pitfalls\n\n- **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.\n- **Processing self-created activities** - Other users may change an Activity created by the twist, resulting in an \\`activity\\` call. Be sure to check the \\`changes === null\\` and/or \\`activity.author.id !== this.id\\` to avoid re-processing.\n- **Always create Activities with Notes** - See \"Understanding Activities and Notes\" section above for the thread/message pattern and decision tree.\n- **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `done`, and `Event` for items with `start`/`end`.\n- **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for automatic deduplication. Only use UUID generation and storage for advanced cases (see SYNC_STRATEGIES.md).\n- **Add Notes to existing Activities** - For source/key pattern, reference activities by source. For UUID pattern, look up stored mappings before creating new Activities. Think thread replies, not new threads.\n- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.\n- **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Tasks tool. Process enough items per batch to be efficient, but few enough to stay under time limits.\n- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.\n- **Store auth tokens** - Don't re-request authentication unnecessarily.\n- **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.\n- **Handle missing auth gracefully** - Check for stored auth before operations.\n\n## Testing\n\nBefore deploying, verify:\n\n1. Linting passes: `{{packageManager}} lint`\n2. All dependencies are in package.json\n3. Authentication flow works end-to-end\n4. Batch operations handle pagination correctly\n5. Error cases are handled gracefully\n";
8
8
  //# sourceMappingURL=twist-guide-template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"twist-guide-template.js","sourceRoot":"","sources":["../../src/llm-docs/twist-guide-template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAe,2/gBAA2/gB,CAAC"}
1
+ {"version":3,"file":"twist-guide-template.js","sourceRoot":"","sources":["../../src/llm-docs/twist-guide-template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAe,qumBAAqumB,CAAC"}
package/dist/plot.d.ts CHANGED
@@ -224,7 +224,7 @@ export type ActivityCommon = {
224
224
  *
225
225
  * Defaults to the current time when creating new activities.
226
226
  */
227
- createdAt: Date;
227
+ created: Date;
228
228
  /** Information about who created the activity */
229
229
  author: Actor;
230
230
  /** Whether this activity is in draft state (not shown in do now view) */
@@ -239,6 +239,12 @@ export type ActivityCommon = {
239
239
  mentions: ActorId[];
240
240
  };
241
241
  export type Activity = ActivityCommon & {
242
+ /**
243
+ * Canonical URL for the item in an external system.
244
+ * For example, https://acme.atlassian.net/browse/PROJ-42 could represent a Jira issue.
245
+ * When set, it uniquely identifies the activity within a priority tree.
246
+ */
247
+ source: string | null;
242
248
  /** The display title/summary of the activity */
243
249
  title: string;
244
250
  /** The type of activity (Note, Task, or Event) */
@@ -246,59 +252,66 @@ export type Activity = ActivityCommon & {
246
252
  /**
247
253
  * The actor assigned to this activity.
248
254
  *
249
- * **For actions (tasks):** An assignee is required. If not explicitly provided when creating
250
- * an action, the assignee will default to the user who installed the twist (the twist owner).
255
+ * **For actions (tasks):**
256
+ * - If not provided (undefined), defaults to the user who installed the twist (twist owner)
257
+ * - To create an **unassigned action**, explicitly set `assignee: null`
258
+ * - For synced tasks from external systems, typically set `assignee: null` for unassigned items
251
259
  *
252
260
  * **For notes and events:** Assignee is optional and typically null.
253
261
  *
254
262
  * @example
255
263
  * ```typescript
256
- * // Create action with explicit assignee
264
+ * // Create action assigned to twist owner (default behavior)
257
265
  * const task: NewActivity = {
258
266
  * type: ActivityType.Action,
259
- * title: "Review PR #123",
260
- * assignee: {
261
- * id: userId as ActorId,
262
- * type: ActorType.User,
263
- * name: "Alice"
264
- * }
267
+ * title: "Follow up on email"
268
+ * // assignee omitted → defaults to twist owner
265
269
  * };
266
270
  *
267
- * // Create action with auto-assignment (defaults to twist owner)
268
- * const task: NewActivity = {
271
+ * // Create UNASSIGNED action (for backlog items)
272
+ * const backlogTask: NewActivity = {
269
273
  * type: ActivityType.Action,
270
- * title: "Follow up on email"
271
- * // assignee will be set automatically to twist owner
274
+ * title: "Review PR #123",
275
+ * assignee: null // Explicitly set to null
272
276
  * };
273
277
  *
274
- * // Update assignee
275
- * await plot.updateActivity({
276
- * id: activityId,
278
+ * // Create action with explicit assignee
279
+ * const assignedTask: NewActivity = {
280
+ * type: ActivityType.Action,
281
+ * title: "Deploy to production",
277
282
  * assignee: {
278
- * id: newUserId as ActorId,
283
+ * id: userId as ActorId,
279
284
  * type: ActorType.User,
280
- * name: "Bob"
285
+ * name: "Alice"
281
286
  * }
282
- * });
287
+ * };
283
288
  * ```
284
289
  */
285
290
  assignee: Actor | null;
286
291
  /** Timestamp when the activity was marked as complete. Null if not completed. */
287
- doneAt: Date | null;
292
+ done: Date | null;
288
293
  /**
289
294
  * Start time of a scheduled activity. Notes are not typically scheduled unless they're about specific times.
290
295
  * For recurring events, this represents the start of the first occurrence.
291
296
  * Can be a Date object for timed events or a date string in "YYYY-MM-DD" format for all-day events.
292
297
  *
293
298
  * **Activity Scheduling States** (for Actions):
294
- * - **Do Now** (current/actionable): When creating a NewActivity of type Action, omitting `start` defaults to current time
299
+ * - **Do Now** (current/actionable): When creating an Action, omitting `start` defaults to current time
295
300
  * - **Do Later** (future scheduled): Set `start` to a future Date or date string
296
301
  * - **Do Someday** (unscheduled backlog): Explicitly set `start: null`
297
302
  *
303
+ * **Important for synced tasks**: When syncing unassigned backlog items from external systems,
304
+ * set BOTH `start: null` AND `assignee: null` to create unscheduled, unassigned actions.
305
+ *
298
306
  * @example
299
307
  * ```typescript
300
- * // "Do Now" - defaults to current time when start is omitted
301
- * await this.tools.plot.createActivity({ type: ActivityType.Action, title: "Urgent task" });
308
+ * // "Do Now" - assigned to twist owner, actionable immediately
309
+ * await this.tools.plot.createActivity({
310
+ * type: ActivityType.Action,
311
+ * title: "Urgent task"
312
+ * // start omitted → defaults to now
313
+ * // assignee omitted → defaults to twist owner
314
+ * });
302
315
  *
303
316
  * // "Do Later" - scheduled for a specific time
304
317
  * await this.tools.plot.createActivity({
@@ -307,11 +320,12 @@ export type Activity = ActivityCommon & {
307
320
  * start: new Date("2025-02-01")
308
321
  * });
309
322
  *
310
- * // "Do Someday" - unscheduled backlog item
323
+ * // "Do Someday" - unassigned backlog item (common for synced tasks)
311
324
  * await this.tools.plot.createActivity({
312
325
  * type: ActivityType.Action,
313
326
  * title: "Backlog task",
314
- * start: null
327
+ * start: null, // Explicitly unscheduled
328
+ * assignee: null // Explicitly unassigned
315
329
  * });
316
330
  * ```
317
331
  */
@@ -404,15 +418,20 @@ export type PickPriorityConfig = {
404
418
  * the current execution context. The ID can be optionally provided by
405
419
  * tools for tracking and update detection purposes.
406
420
  *
407
- * **Important: Scheduling Defaults for Actions**
421
+ * **Important: Defaults for Actions**
422
+ *
423
+ * When creating an Activity of type `Action`:
424
+ * - **`start` omitted** → Defaults to current time (now) → "Do Now"
425
+ * - **`assignee` omitted** → Defaults to twist owner → Assigned action
408
426
  *
409
- * When creating an Activity of type `Action`, the `start` field determines its scheduling state:
410
- * - **Omit `start`** Defaults to current time → "Do Now" (appears in today's actionable list)
411
- * - **Set `start: null`**Unscheduled → "Do Someday" (backlog item, no specific time)
412
- * - **Set `start` to future Date** → Scheduled → "Do Later" (appears on that date)
427
+ * To create unassigned backlog items (common for synced tasks), you MUST explicitly set BOTH:
428
+ * - `start: null` → "Do Someday" (unscheduled)
429
+ * - `assignee: null`Unassigned
413
430
  *
414
- * For most external task integrations (project management, issue trackers), use `start: null`
415
- * to create backlog items unless the task is explicitly marked as current/active.
431
+ * **Scheduling States**:
432
+ * - **"Do Now"** (actionable today): Omit `start` or set to current time
433
+ * - **"Do Later"** (scheduled): Set `start` to a future Date
434
+ * - **"Do Someday"** (backlog): Set `start: null`
416
435
  *
417
436
  * Priority can be specified in three ways:
418
437
  * 1. Explicit priority: `priority: { id: "..." }` - Use specific priority (disables pickPriority)
@@ -421,18 +440,20 @@ export type PickPriorityConfig = {
421
440
  *
422
441
  * @example
423
442
  * ```typescript
424
- * // "Do Now" - Action defaults to current time (actionable today)
443
+ * // "Do Now" - Assigned to twist owner, actionable immediately
425
444
  * const urgentTask: NewActivity = {
426
445
  * type: ActivityType.Action,
427
446
  * title: "Review pull request"
428
- * // Omitting start defaults to new Date()
447
+ * // start omitted → defaults to now
448
+ * // assignee omitted → defaults to twist owner
429
449
  * };
430
450
  *
431
- * // "Do Someday" - Backlog item (recommended for most synced tasks)
451
+ * // "Do Someday" - UNASSIGNED backlog item (for synced tasks)
432
452
  * const backlogTask: NewActivity = {
433
453
  * type: ActivityType.Action,
434
454
  * title: "Refactor user service",
435
- * start: null // Explicitly set to null for backlog
455
+ * start: null, // Must explicitly set to null
456
+ * assignee: null // Must explicitly set to null
436
457
  * };
437
458
  *
438
459
  * // "Do Later" - Scheduled for specific date
@@ -459,13 +480,27 @@ export type PickPriorityConfig = {
459
480
  * };
460
481
  * ```
461
482
  */
462
- export type NewActivity = Pick<Activity, "type"> & Partial<Omit<Activity, "author" | "assignee" | "type" | "priority" | "tags" | "mentions">> & ({
483
+ export type NewActivity = Pick<Activity, "type"> & Partial<Omit<Activity, "author" | "assignee" | "type" | "priority" | "tags" | "mentions" | "id" | "source">> & ({
484
+ /**
485
+ * Unique identifier for the activity, generated by Uuid.Generate().
486
+ * Specifying an ID allows tools to track and upsert activities.
487
+ */
488
+ id: Uuid;
489
+ } | {
490
+ /**
491
+ * Canonical URL for the item in an external system.
492
+ * For example, https://acme.atlassian.net/browse/PROJ-42 could represent a Jira issue.
493
+ * When set, it uniquely identifies the activity within a priority tree. This performs
494
+ * an upsert.
495
+ */
496
+ source: string;
497
+ } | {}) & ({
463
498
  /** Explicit priority (required when specified) - disables automatic priority matching */
464
499
  priority: Pick<Priority, "id">;
465
500
  } | {
466
501
  /** Configuration for automatic priority selection based on similarity */
467
502
  pickPriority?: PickPriorityConfig;
468
- } | {}) & {
503
+ }) & {
469
504
  /**
470
505
  * The person that created the item. By default, it will be the twist itself.
471
506
  */
@@ -487,7 +522,17 @@ export type NewActivity = Pick<Activity, "type"> & Partial<Omit<Activity, "autho
487
522
  */
488
523
  unread?: boolean;
489
524
  };
490
- export type ActivityUpdate = Pick<Activity, "id"> & Partial<Pick<Activity, "type" | "start" | "end" | "doneAt" | "title" | "assignee" | "draft" | "private" | "meta" | "recurrenceRule" | "recurrenceDates" | "recurrenceExdates" | "recurrenceUntil" | "recurrenceCount" | "occurrence">> & {
525
+ export type ActivityUpdate = ({
526
+ /**
527
+ * Unique identifier for the activity.
528
+ */
529
+ id: Uuid;
530
+ } | {
531
+ /**
532
+ * Canonical URL for the item in an external system.
533
+ */
534
+ source: string;
535
+ }) & Partial<Pick<Activity, "type" | "start" | "end" | "done" | "title" | "assignee" | "draft" | "private" | "meta" | "recurrenceRule" | "recurrenceDates" | "recurrenceExdates" | "recurrenceUntil" | "recurrenceCount" | "occurrence">> & {
491
536
  /**
492
537
  * Tags to change on the activity. Use an empty array of NewActor to remove a tag.
493
538
  * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.
@@ -543,6 +588,12 @@ export type SyncUpdate = NewActivityWithNotes | {
543
588
  * They are always ordered by creation time within their parent activity.
544
589
  */
545
590
  export type Note = ActivityCommon & {
591
+ /**
592
+ * Unique identifier for the note within its activity.
593
+ * Can be used to upsert without knowing the id.
594
+ * For example, "description" could identify the description note for a Jira issue.
595
+ */
596
+ key: string | null;
546
597
  /** The parent activity this note belongs to */
547
598
  activity: Activity;
548
599
  /** Primary content for the note (markdown) */
@@ -554,11 +605,20 @@ export type Note = ActivityCommon & {
554
605
  * Type for creating new notes.
555
606
  *
556
607
  * Requires the activity reference, with all other fields optional.
557
- * The id field can be optionally provided by tools for tracking purposes.
608
+ * Can provide id, key, or neither for note identification:
609
+ * - id: Provide a specific UUID for the note
610
+ * - key: Provide an external identifier for upsert within the activity
611
+ * - neither: A new note with auto-generated UUID will be created
558
612
  */
559
- export type NewNote = Partial<Omit<Note, "author" | "activity" | "tags" | "mentions">> & {
613
+ export type NewNote = Partial<Omit<Note, "author" | "activity" | "tags" | "mentions" | "id" | "key">> & ({
614
+ id: Uuid;
615
+ } | {
616
+ key: string;
617
+ } | {}) & {
560
618
  /** Reference to the parent activity (required) */
561
- activity: Pick<Activity, "id">;
619
+ activity: Pick<Activity, "id"> | {
620
+ source: string;
621
+ };
562
622
  /**
563
623
  * The person that created the item, or leave undefined to use the twist as author.
564
624
  */
@@ -590,8 +650,13 @@ export type NewNote = Partial<Omit<Note, "author" | "activity" | "tags" | "menti
590
650
  };
591
651
  /**
592
652
  * Type for updating existing notes.
653
+ * Must provide either id or key to identify the note to update.
593
654
  */
594
- export type NoteUpdate = Pick<Note, "id"> & Partial<Pick<Note, "draft" | "private" | "content" | "links">> & {
655
+ export type NoteUpdate = ({
656
+ id: Uuid;
657
+ } | {
658
+ key: string;
659
+ }) & Partial<Pick<Note, "draft" | "private" | "content" | "links">> & {
595
660
  /**
596
661
  * Format of the note content. Determines how the note is processed:
597
662
  * - 'text': Plain text that will be converted to markdown (auto-links URLs, preserves line breaks)
@@ -1 +1 @@
1
- {"version":3,"file":"plot.d.ts","sourceRoot":"","sources":["../src/plot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plot.d.ts","sourceRoot":"","sources":["../src/plot.ts"],"names":[],"mappings":"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"}
package/dist/plot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"plot.js","sourceRoot":"","sources":["../src/plot.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAqCpC;;;;;GAKG;AACH,MAAM,CAAN,IAAY,YAOX;AAPD,WAAY,YAAY;IACtB,qEAAqE;IACrE,+CAAI,CAAA;IACJ,+CAA+C;IAC/C,mDAAM,CAAA;IACN,8DAA8D;IAC9D,iDAAK,CAAA;AACP,CAAC,EAPW,YAAY,KAAZ,YAAY,QAOvB;AAED;;;;;GAKG;AACH,MAAM,CAAN,IAAY,gBASX;AATD,WAAY,gBAAgB;IAC1B,8CAA8C;IAC9C,yCAAqB,CAAA;IACrB,mDAAmD;IACnD,iCAAa,CAAA;IACb,6DAA6D;IAC7D,yCAAqB,CAAA;IACrB,+DAA+D;IAC/D,iDAA6B,CAAA;AAC/B,CAAC,EATW,gBAAgB,KAAhB,gBAAgB,QAS3B;AAED;;;;;GAKG;AACH,MAAM,CAAN,IAAY,oBAWX;AAXD,WAAY,oBAAoB;IAC9B,kBAAkB;IAClB,iDAAyB,CAAA;IACzB,WAAW;IACX,qCAAa,CAAA;IACb,sBAAsB;IACtB,yDAAiC,CAAA;IACjC,kBAAkB;IAClB,uCAAe,CAAA;IACf,6CAA6C;IAC7C,uCAAe,CAAA;AACjB,CAAC,EAXW,oBAAoB,KAApB,oBAAoB,QAW/B;AAwnBD;;;;;GAKG;AACH,MAAM,CAAN,IAAY,SAOX;AAPD,WAAY,SAAS;IACnB,wCAAwC;IACxC,yCAAI,CAAA;IACJ,8CAA8C;IAC9C,+CAAO,CAAA;IACP,6CAA6C;IAC7C,2CAAK,CAAA;AACP,CAAC,EAPW,SAAS,KAAT,SAAS,QAOpB"}
1
+ {"version":3,"file":"plot.js","sourceRoot":"","sources":["../src/plot.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAqCpC;;;;;GAKG;AACH,MAAM,CAAN,IAAY,YAOX;AAPD,WAAY,YAAY;IACtB,qEAAqE;IACrE,+CAAI,CAAA;IACJ,+CAA+C;IAC/C,mDAAM,CAAA;IACN,8DAA8D;IAC9D,iDAAK,CAAA;AACP,CAAC,EAPW,YAAY,KAAZ,YAAY,QAOvB;AAED;;;;;GAKG;AACH,MAAM,CAAN,IAAY,gBASX;AATD,WAAY,gBAAgB;IAC1B,8CAA8C;IAC9C,yCAAqB,CAAA;IACrB,mDAAmD;IACnD,iCAAa,CAAA;IACb,6DAA6D;IAC7D,yCAAqB,CAAA;IACrB,+DAA+D;IAC/D,iDAA6B,CAAA;AAC/B,CAAC,EATW,gBAAgB,KAAhB,gBAAgB,QAS3B;AAED;;;;;GAKG;AACH,MAAM,CAAN,IAAY,oBAWX;AAXD,WAAY,oBAAoB;IAC9B,kBAAkB;IAClB,iDAAyB,CAAA;IACzB,WAAW;IACX,qCAAa,CAAA;IACb,sBAAsB;IACtB,yDAAiC,CAAA;IACjC,kBAAkB;IAClB,uCAAe,CAAA;IACf,6CAA6C;IAC7C,uCAAe,CAAA;AACjB,CAAC,EAXW,oBAAoB,KAApB,oBAAoB,QAW/B;AAosBD;;;;;GAKG;AACH,MAAM,CAAN,IAAY,SAOX;AAPD,WAAY,SAAS;IACnB,wCAAwC;IACxC,yCAAI,CAAA;IACJ,8CAA8C;IAC9C,+CAAO,CAAA;IACP,6CAA6C;IAC7C,2CAAK,CAAA;AACP,CAAC,EAPW,SAAS,KAAT,SAAS,QAOpB"}