Twister, the Plot Twist Creator
The official package for building Plot Twists -
smart automations that connect, organize, and prioritize your work.
Quick Start
Choose your path:
- No Code - Write natural language, deploy in minutes
- TypeScript - Full control with code
No-Code Quick Start
Describe your twist and Plot will do the rest.
1. Create plot-twist.md:
# My Calendar Twist
I want a twist that:
- Syncs my Google Calendar events into Plot
- Creates tasks for upcoming meetings
- Sends reminders 10 minutes before meetings2. Deploy:
npx @plotday/twister login
npx @plotday/twister deployThat's it! Learn more →
Developer Quick Start
Build twists with TypeScript for maximum flexibility.
1. Create a new twist:
npx @plotday/twister create2. Implement your twist:
import { type ToolBuilder, Twist } from "@plotday/twister";
import { Plot, ThreadAccess } from "@plotday/twister/tools/plot";
export default class MyTwist extends Twist<MyTwist> {
build(build: ToolBuilder) {
return {
plot: build(Plot, {
thread: { access: ThreadAccess.Create },
}),
};
}
async activate() {
await this.tools.plot.createThread({
title: "Welcome! Your twist is now active.",
notes: [
{
content: "Your twist is ready to use. Check out the [documentation](https://twist.plot.day) to learn more.",
},
],
});
}
}3. Deploy:
npx plot login
npm run deploy
Core Concepts
Twists
Twists are smart automations that connect, organize, and prioritize your work. They implement opinionated workflows and respond to lifecycle events. A twist is installed at the workspace level and owned by a single user.
// Lifecycle methods
async activate(context?) // When the twist is installed
async deactivate() // When the twist is uninstalled
async upgrade() // When a new version is deployedTwist Tools
Twist tools provide capabilities to twists. They are usually unopinionated and do nothing on their own.
Built-in Tools:
- Plot - Manage threads, notes, and focuses
- Store - Persistent key-value storage
- AI - Language models with structured output
- Integrations - OAuth authentication and channel lifecycle
- Network - HTTP access and webhooks
- Tasks - Background task execution
- Callbacks - Persistent function references
External service integrations (Google Calendar, Slack, Linear, etc.) are built as Connectors — see Building Connectors.
Threads and Notes
A Thread represents something done or to be done (a task, event, or conversation). Notes represent updates and details on that thread.
Think of a Thread like a messaging thread and Notes as messages in that thread. Always create threads with an initial note, and add notes for updates rather than creating new threads.
Data sync: When syncing from external systems, connectors save Links (external items attached to threads) via integrations.saveLink(), using Link.sources for deduplication and Note.key for upsertable notes — no manual ID tracking needed. See the Sync Strategies guide for detailed patterns.
import { ActionType } from "@plotday/twister";
// Create a thread with an initial note
const threadId = await this.tools.plot.createThread({
title: "Review pull request",
notes: [
{
key: "description", // Use key for upsertable notes
content: "New PR ready for review",
actions: [
{
type: ActionType.external,
title: "View PR",
url: "https://github.com/org/repo/pull/123",
},
],
},
],
});
// Add or update a note using key (upserts if key exists)
await this.tools.plot.createNote({
thread: { id: threadId },
key: "approval", // Using key enables upserts
content: "LGTM! Approved ✅",
});
CLI Commands
# Authentication
plot login
# Twist management
plot create # Create new twist project
plot generate # Generate code from plot-twist.md
plot lint # Check for build or lint errors
plot build # Bundle without deploying
plot deploy # Deploy to Plot
plot logs # Stream real-time twist logs
# Priority management
plot priority list # List all priorities
plot priority create # Create new priority
Documentation
Full Documentation at twist.plot.day
Guides
- Getting Started - Complete walkthrough
- Core Concepts - Twists, tools, and architecture
- Sync Strategies - Data synchronization patterns (upserts, deduplication, ID management)
- Built-in Tools - Plot, Store, AI, and more
- Building Connectors - Build external service integrations
- Runtime Environment - Execution constraints and optimization
Reference
- CLI Reference - Complete command documentation
- API Reference - TypeDoc-generated API docs
Examples
Simple Note Twist
export default class WelcomeTwist extends Twist<WelcomeTwist> {
build(build: ToolBuilder) {
return {
plot: build(Plot, {
thread: { access: ThreadAccess.Create },
}),
};
}
async activate() {
await this.tools.plot.createThread({
title: "Welcome to Plot! 👋",
notes: [
{
content: "This twist will help you get started with Plot.",
},
],
});
}
}GitHub Connector
import { Connector, type ToolBuilder } from "@plotday/twister";
import {
AuthProvider,
type AuthToken,
type Authorization,
type Channel,
Integrations,
} from "@plotday/twister/tools/integrations";
export default class GitHubConnector extends Connector<GitHubConnector> {
readonly provider = AuthProvider.GitHub;
readonly scopes = ["repo"];
build(build: ToolBuilder) {
return {
integrations: build(Integrations),
};
}
async getChannels(
auth: Authorization | null,
token: AuthToken | null
): Promise<Channel[]> {
// Return repositories the user can sync
const repos = await this.listRepos(token);
return repos.map((repo) => ({
id: repo.id,
title: repo.full_name,
}));
}
async onChannelEnabled(channel: Channel) {
// Start syncing issues from this repository
}
async onChannelDisabled(channel: Channel) {
// Stop syncing and clean up
}
}
TypeScript Configuration
Extend the Twist Creator's base configuration in your tsconfig.json:
{
"extends": "@plotday/twister/tsconfig.base.json",
"include": ["src/*.ts"]
}Support
- Documentation: twist.plot.day
- Issues: github.com/plotday/plot/issues
- Website: plot.day
License
MIT Plot Technologies Inc.