add-trigger
Skill1 starsChanged 2 months agoApache-2.0View on GitHub
Create webhook or polling triggers for a Sim integration
- Sends data out
We split this skill into the rules it gives an agent
26 rules. About 0 are common, 13 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈13
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 14
Create the trigger files using the generic builder (webhook) or manual config (polling)
- Distinctive: line 15
Create a provider handler (webhook) or polling handler (polling)
- Very specific: line 20
If the service docs do not clearly show the webhook payload JSON for an event, you MUST tell the user instead of guessing trigger outputs …
- Distinctive: line 22
Do NOT invent payload field names
- Distinctive: line 23
Do NOT guess nested event object paths
- Distinctive: line 24
Do NOT infer output fields from the UI or marketing docs
- Very specific: line 25
Do NOT write formatInput against unverified webhook bodies
- Distinctive: line 27
If the payload shape is unknown, do one of these instead:
- Distinctive: line 28
Ask the user for sample webhook payloads
- Distinctive: line 29
Ask the user for a test webhook source so you can inspect a real event
- Distinctive: line 31
Leave the trigger unimplemented and explicitly say which payload fields are unknown
- Very specific: line 168
Add
triggersproperty: after outputs with enabled: true and available: [...] - Very specific: line 198
All provider-specific webhook logic lives in a single handler file: apps/sim/lib/webhooks/providers/{service}.ts.
- Distinctive: line 214
If none apply, you don't need a handler.
- Distinctive: line 288
If they differ: the tag dropdown shows fields that don't exist, or actual data has fields users can't discover.
- Distinctive: line 293
No wrapper objects or duplication
- Very specific: line 294
Use null for missing optional data
- Very specific: line 298
If the service API supports programmatic webhook creation, implement createSubscription and deleteSubscription on the handler.
- Very specific: line 335
Never throw from deleteSubscription — log non-fatally
- Very specific: line 337
Add apiKey field to build{Service}ExtraFields with password: true
- Very specific: line 362
Use polling when the service lacks reliable webhooks (e.g., Google Sheets, Google Drive, Google Calendar, Gmail, RSS, IMAP).
- Very specific: line 419
Use pollingIdempotency.executeWithIdempotency(provider, key, callback) for dedup
- Very specific: line 420
Use processPolledWebhookEvent(webhookData, workflowData, payload, requestId) to fire the workflow
- Very specific: line 421
Use updateWebhookProviderConfig(webhookId, partialConfig, logger) for read-merge-write on state
- Distinctive: line 422
Use the latest server-side timestamp from API responses (not wall clock) to avoid clock skew
- Very specific: line 459
Add to helm/sim/values.yaml under the existing polling cron jobs:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Add Trigger
You are an expert at creating webhook and polling triggers for Sim. You understand the trigger system, the generic buildTriggerSubBlocks helper, polling infrastructure, and how triggers connect to blocks.
Your Task
- Research what webhook events the service supports — if the service lacks reliable webhooks, use polling
- Create the trigger files using the generic builder (webhook) or manual config (polling)
- Create a provider handler (webhook) or polling handler (polling)
- Register triggers and connect them to the block
Hard Rule: No Guessed Webhook Payload Schemas
If the service docs do not clearly show the webhook payload JSON for an event, you MUST tell the user instead of guessing trigger outputs or formatInput mappings.
- Do NOT invent payload field names
- Do NOT guess nested event object paths
- Do NOT infer output fields from the UI or marketing docs
- Do NOT write
formatInputagainst unverified webhook bodies
If the payload shape is unknown, do one of these instead:
- Ask the user for sample webhook payloads
- Ask the user for a test webhook source so you can inspect a real event
- Implement only the event registration/setup portions whose payloads are documented
- Leave the trigger unimplemented and explicitly say which payload fields are unknown
Directory Structure
apps/sim/triggers/{service}/
├── index.ts # Barrel exports
├── utils.ts # Service-specific helpers (options, instructions, extra fields, outputs)
├── {event_a}.ts # Primary trigger (includes dropdown)
├── {event_b}.ts # Secondary trigger (no dropdown)
└── webhook.ts # Generic webhook trigger (optional, for "all events")
apps/sim/lib/webhooks/
├── provider-subscription-utils.ts # Shared subscription helpers (getProviderConfig, getNotificationUrl)
├── providers/
│ ├── {service}.ts # Provider handler (auth, formatInput, matchEvent, subscriptions)
│ ├── types.ts # WebhookProviderHandler interface
│ ├── utils.ts # Shared helpers (createHmacVerifier, verifyTokenAuth, skipByEventTypes)
│ └── registry.ts # Handler map + default handlerStep 1: Create utils.ts
This file contains all service-specific helpers used by triggers.
import type { SubBlockConfig } from '@/blocks/types'
import type { TriggerOutput } from '@/triggers/types'
export const {service}TriggerOptions = [
{ label: 'Event A', id: '{service}_event_a' },
{ label: 'Event B', id: '{service}_event_b' },
]
export function {service}SetupInstructions(eventType: string): string {
const instructions = [
'Copy the <strong>Webhook URL</strong> above',
'Go to <strong>{Service} Settings > Webhooks</strong>',
`Select the <strong>${eventType}</strong> event type`,
'Paste the webhook URL and save',
'Click "Save" above to activate your trigger',
]
return instructions
.map((instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join('')
}
export function build{Service}ExtraFields(triggerId: string): SubBlockConfig[] {
return [
{
id: 'projectId',
title: 'Project ID (Optional)',
type: 'short-input',
placeholder: 'Leave empty for all projects',
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
},
]
}
export function build{Service}Outputs(): Record<string, TriggerOutput> {
return {
eventType: { type: 'string', description: 'The type of event' },
resourceId: { type: 'string', description: 'ID of the affected resource' },
resource: {
id: { type: 'string', description: 'Resource ID' },
name: { type: 'string', description: 'Resource name' },
},
}
}Step 2: Create Trigger Files
Primary trigger — MUST include includeDropdown: true:
import { {Service}Icon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import { build{Service}ExtraFields, build{Service}Outputs, {service}SetupInstructions, {service}TriggerOptions } from '@/triggers/{service}/utils'
import type { TriggerConfig } from '@/triggers/types'
export const {service}EventATrigger: TriggerConfig = {
id: '{service}_event_a',
name: '{Service} Event A',
provider: '{service}',
description: 'Trigger workflow when Event A occurs',
version: '1.0.0',
icon: {Service}Icon,
subBlocks: buildTriggerSubBlocks({
triggerId: '{service}_event_a',
triggerOptions: {service}TriggerOptions,
includeDropdown: true,
setupInstructions: {service}SetupInstructions('Event A'),
extraFields: build{Service}ExtraFields('{service}_event_a'),
}),
outputs: build{Service}Outputs(),
webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } },
}Secondary triggers — NO includeDropdown (it's already in the primary):
export const {service}EventBTrigger: TriggerConfig = {
// Same as above but: id: '{service}_event_b', no includeDropdown
}Step 3: Register and Wire
apps/sim/triggers/{service}/index.ts
export { {service}EventATrigger } from './event_a'
export { {service}EventBTrigger } from './event_b'apps/sim/triggers/registry.ts
import { {service}EventATrigger, {service}EventBTrigger } from '@/triggers/{service}'
export const TRIGGER_REGISTRY: TriggerRegistry = {
// ... existing ...
{service}_event_a: {service}EventATrigger,
{service}_event_b: {service}EventBTrigger,
}Block file (apps/sim/blocks/blocks/{service}.ts)
Wire triggers into the block so the trigger UI appears and generate-docs.ts discovers them. Two changes are needed:
- Spread trigger subBlocks at the end of the block's
subBlocksarray - Add
triggersproperty afteroutputswithenabled: trueandavailable: [...]
import { getTrigger } from '@/triggers'
export const {Service}Block: BlockConfig = {
// ...
subBlocks: [
// Regular tool subBlocks first...
...getTrigger('{service}_event_a').subBlocks,
...getTrigger('{service}_event_b').subBlocks,
],
// ... tools, inputs, outputs ...
triggers: {
enabled: true,
available: ['{service}_event_a', '{service}_event_b'],
},
}Versioned blocks (V1 + V2): Many integrations have a hidden V1 block and a visible V2 block. Where you add the trigger wiring depends on how V2 inherits from V1:
- V2 uses
...V1Blockspread (e.g., Google Calendar): Add trigger to V1 — V2 inherits bothsubBlocksandtriggersautomatically. - V2 defines its own
subBlocks(e.g., Google Sheets): Add trigger to V2 (the visible block). V1 is hidden and doesn't need it. - Single block, no V2 (e.g., Google Drive): Add trigger directly.
generate-docs.ts deduplicates by base type (first match wins). If V1 is processed first without triggers, the V2 triggers won't appear in integrations.json. Always verify by checking the output after running the script.
Provider Handler
All provider-specific webhook logic lives in a single handler file: apps/sim/lib/webhooks/providers/{service}.ts.
When to Create a Handler
| Behavior | Method | Examples |
| HMAC signature auth | verifyAuth via createHmacVerifier | Ashby, Jira, Linear, Typeform |
| Custom token auth | verifyAuth via verifyTokenAuth | Generic, Google Forms |
| Event filtering | matchEvent | GitHub, Jira, Attio, HubSpot |
| Idempotency dedup | extractIdempotencyId | Slack, Stripe, Linear, Jira |
| Custom input formatting | formatInput | Slack, Teams, Attio, Ashby |
| Auto webhook creation | createSubscription | Ashby, Grain, Calendly, Airtable |
| Auto webhook deletion | deleteSubscription | Ashby, Grain, Calendly, Airtable |
| Challenge/verification | handleChallenge | Slack, WhatsApp, Teams |
| Custom success response | formatSuccessResponse | Slack, Twilio Voice, Teams |
If none apply, you don't need a handler. The default handler provides bearer token auth.
Example Handler
import crypto from 'crypto'
import { createLogger } from '@sim/logger'
import { safeCompare } from '@/lib/core/security/encryption'
import type { EventMatchContext, FormatInputContext, FormatInputResult, WebhookProviderHandler } from '@/lib/webhooks/providers/types'
import { createHmacVerifier } from '@/lib/webhooks/providers/utils'
const logger = createLogger('WebhookProvider:{Service}')
function validate{Service}Signature(secret: string, signature: string, body: string): boolean {
if (!secret || !signature || !body) return false
const computed = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
return safeCompare(computed, signature)
}
export const {service}Handler: WebhookProviderHandler = {
verifyAuth: createHmacVerifier({
configKey: 'webhookSecret',
headerName: 'X-{Service}-Signature',
validateFn: validate{Service}Signature,
providerLabel: '{Service}',
}),
async matchEvent({ body, requestId, providerConfig }: EventMatchContext) {
const triggerId = providerConfig.triggerId as string | undefined
if (triggerId && triggerId !== '{service}_webhook') {
const { is{Service}EventMatch } = await import('@/triggers/{service}/utils')
if (!is{Service}EventMatch(triggerId, body as Record<string, unknown>)) return false
}
return true
},
async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
const b = body as Record<string, unknown>
return {
input: {
eventType: b.type,
resourceId: (b.data as Record<string, unknown>)?.id || '',
resource: b.data,
},
}
},
extractIdempotencyId(body: unknown) {
const obj = body as Record<string, unknown>
return obj.id && obj.type ? `${obj.type}:${obj.id}` : null
},
}Register the Handler
In apps/sim/lib/webhooks/providers/registry.ts:
import { {service}Handler } from '@/lib/webhooks/providers/{service}'
const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
// ... existing (alphabetical) ...
{service}: {service}Handler,
}Output Alignment (Critical)
There are two sources of truth that MUST be aligned:
- Trigger
outputs— schema defining what fields SHOULD be available (UI tag dropdown) formatInputon the handler — implementation that transforms raw payload into actual data
If they differ: the tag dropdown shows fields that don't exist, or actual data has fields users can't discover.
Rules for formatInput:
- Return
{ input: { ... } }where inner keys match triggeroutputsexactly - Return
{ input: ..., skip: { message: '...' } }to skip execution - No wrapper objects or duplication
- Use
nullfor missing optional data
Automatic Webhook Registration
If the service API supports programmatic webhook creation, implement createSubscription and deleteSubscription on the handler. The orchestration layer calls these automatically — no code touches route.ts, provider-subscriptions.ts, or deploy.ts.
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import type { DeleteSubscriptionContext, SubscriptionContext, SubscriptionResult } from '@/lib/webhooks/providers/types'
export const {service}Handler: WebhookProviderHandler = {
async createSubscription(ctx: SubscriptionContext): Promise<SubscriptionResult | undefined> {
const config = getProviderConfig(ctx.webhook)
const apiKey = config.apiKey as string
if (!apiKey) throw new Error('{Service} API Key is required.')
const res = await fetch('https://api.{service}.com/webhooks', {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ url: getNotificationUrl(ctx.webhook) }),
})
if (!res.ok) throw new Error(`{Service} error: ${res.status}`)
const { id } = (await res.json()) as { id: string }
return { providerConfigUpdates: { externalId: id } }
},
async deleteSubscription(ctx: DeleteSubscriptionContext): Promise<void> {
const config = getProviderConfig(ctx.webhook)
const { apiKey, externalId } = config as { apiKey?: string; externalId?: string }
if (!apiKey || !externalId) return
await fetch(`https://api.{service}.com/webhooks/${externalId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
}).catch(() => {})
},
}Key points:
- Throw from
createSubscription— orchestration rolls back the DB webhook - Never throw from
deleteSubscription— log non-fatally - Return
{ providerConfigUpdates: { externalId } }— orchestration merges intoproviderConfig - Add
apiKeyfield tobuild{Service}ExtraFieldswithpassword: true
Trigger Outputs Schema
Trigger outputs use the same schema as block outputs (NOT tool outputs).
Supported: type + description for leaf fields, nested objects for complex data.
NOT supported: optional: true, items (those are tool-output-only features).
export function buildOutputs(): Record<string, TriggerOutput> {
return {
eventType: { type: 'string', description: 'Event type' },
timestamp: { type: 'string', description: 'When it occurred' },
payload: { type: 'json', description: 'Full event payload' },
resource: {
id: { type: 'string', description: 'Resource ID' },
name: { type: 'string', description: 'Resource name' },
},
}
}Polling Triggers
Use polling when the service lacks reliable webhooks (e.g., Google Sheets, Google Drive, Google Calendar, Gmail, RSS, IMAP). Polling triggers do NOT use buildTriggerSubBlocks — they define subBlocks manually.
Directory Structure
apps/sim/triggers/{service}/
├── index.ts # Barrel export
└── poller.ts # TriggerConfig with polling: true
apps/sim/lib/webhooks/polling/
└── {service}.ts # PollingProviderHandler implementationPolling Handler (apps/sim/lib/webhooks/polling/{service}.ts)
import { pollingIdempotency } from '@/lib/core/idempotency/service'
import type { PollingProviderHandler, PollWebhookContext } from '@/lib/webhooks/polling/types'
import { markWebhookFailed, markWebhookSuccess, resolveOAuthCredential, updateWebhookProviderConfig } from '@/lib/webhooks/polling/utils'
import { processPolledWebhookEvent } from '@/lib/webhooks/processor'
export const {service}PollingHandler: PollingProviderHandler = {
provider: '{service}',
label: '{Service}',
async pollWebhook(ctx: PollWebhookContext): Promise<'success' | 'failure'> {
const { webhookData, workflowData, requestId, logger } = ctx
const webhookId = webhookData.id
try {
// For OAuth services:
const accessToken = await resolveOAuthCredential(webhookData, '{service}', requestId)
const config = webhookData.providerConfig as unknown as {Service}WebhookConfig
// First poll: seed state, emit nothing
if (!config.lastCheckedTimestamp) {
await updateWebhookProviderConfig(webhookId, { lastCheckedTimestamp: new Date().toISOString() }, logger)
await markWebhookSuccess(webhookId, logger)
return 'success'
}
// Fetch changes since last poll, process with idempotency
// ...
await markWebhookSuccess(webhookId, logger)
return 'success'
} catch (error) {
logger.error(`[${requestId}] Error processing {service} webhook ${webhookId}:`, error)
await markWebhookFailed(webhookId, logger)
return 'failure'
}
},
}Key patterns:
- First poll seeds state and emits nothing (avoids flooding with existing data)
- Use
pollingIdempotency.executeWithIdempotency(provider, key, callback)for dedup - Use
processPolledWebhookEvent(webhookData, workflowData, payload, requestId)to fire the workflow - Use
updateWebhookProviderConfig(webhookId, partialConfig, logger)for read-merge-write on state - Use the latest server-side timestamp from API responses (not wall clock) to avoid clock skew
Trigger Config (apps/sim/triggers/{service}/poller.ts)
import { {Service}Icon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const {service}PollingTrigger: TriggerConfig = {
id: '{service}_poller',
name: '{Service} Trigger',
provider: '{service}',
description: 'Triggers when ...',
version: '1.0.0',
icon: {Service}Icon,
polling: true, // REQUIRED — routes to polling infrastructure
subBlocks: [
{ id: 'triggerCredentials', type: 'oauth-input', title: 'Credentials', serviceId: '{service}', requiredScopes: [], required: true, mode: 'trigger' },
// ... service-specific config fields (dropdowns, inputs, switches) ...
{ id: 'triggerInstructions', type: 'text', title: 'Setup Instructions', hideFromPreview: true, mode: 'trigger', defaultValue: '...' },
],
outputs: {
// Must match the payload shape from processPolledWebhookEvent
},
}Registration (3 places)
apps/sim/triggers/constants.ts— add provider toPOLLING_PROVIDERSSetapps/sim/lib/webhooks/polling/registry.ts— import handler, add toPOLLING_HANDLERSapps/sim/triggers/registry.ts— import trigger config, add toTRIGGER_REGISTRY
Helm Cron Job
Add to helm/sim/values.yaml under the existing polling cron jobs:
{service}WebhookPoll:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
url: "http://sim:3000/api/webhooks/poll/{service}"Reference Implementations
- Simple:
apps/sim/lib/webhooks/polling/rss.ts+apps/sim/triggers/rss/poller.ts - Complex (OAuth, attachments):
apps/sim/lib/webhooks/polling/gmail.ts+apps/sim/triggers/gmail/poller.ts - Cursor-based (changes API):
apps/sim/lib/webhooks/polling/google-drive.ts - Timestamp-based:
apps/sim/lib/webhooks/polling/google-calendar.ts
Option Lists: selectorKey or options, never a per-block fetcher
A sub-block gets its choices from exactly one of two places. There is no third.
selectorKey — every remote list. Register the list in hooks/selectors/providers/<service>/selectors.ts, add its key to SelectorKey, and point the sub-block at it. A selector is parameterized by an explicit SelectorContext, so the same definition serves the canvas, the workspace-fork sync modal, and anything added later.
{ id: 'triggerCredentials', type: 'oauth-input', canonicalParamId: 'oauthCredential', mode: 'trigger' },
{ id: 'labelIds', type: 'dropdown', multiSelect: true,
selectorKey: 'gmail.labels', dependsOn: ['triggerCredentials'], mode: 'trigger' },
{ id: 'manualLabelIds', type: 'short-input', mode: 'trigger-advanced' },canonicalParamId: 'oauthCredential' on the credential sub-block is the line people forget. buildSelectorContextFromBlock keys the context on a sub-block's CANONICAL id, so without it context.oauthCredential is never set and the picker looks unfixable without reading the store. (A credential field is also recognised by its oauth-input TYPE as a fallback, so a block whose shipped param is already named something else does not have to rename it.)
options — everything else. A static array, or a pure function of the block's own values for a list that narrows to a sibling's selection. No I/O.
options: (params) => {
const model = params?.values.model
return typeof model === 'string' ? effortsFor(model) : DEFAULT_EFFORTS
}Never fetch inside options, and never reach into the stores from a block definition. A fetcher that resolves its credential with readSubBlockValue(blockId, ...) only works on the canvas — every surface that is not the editor gets an empty list. fetchOptions/fetchOptionById were removed for exactly this reason.
Two rules the checks enforce:
- A secret never enters a selector's
getQueryKey. A query key identifies a resource; a credential authorizes access to it. A credential id is fine; a typed password is not (seeimap.mailboxes). - A sub-block that
dependsOna credential / knowledge-base / table selector must be reconfigurable at fork-sync time — aselectorKey, a canonical pair whose basic member is a selector, or ashort-input/long-input.bun run check:fork-dependent-coveragefails otherwise, because a fork sync clears those fields on every push and an unofferable one can never be set anywhere that sticks.
Checklist
Trigger Definition
- Created
utils.tswith options, instructions, extra fields, and output builders - Primary trigger has
includeDropdown: true; secondary triggers do NOT - All triggers use
buildTriggerSubBlockshelper - Created
index.tsbarrel export
Registration
- All triggers in
triggers/registry.ts→TRIGGER_REGISTRY - Block has
triggers.enabled: trueand lists all trigger IDs intriggers.available - Block spreads all trigger subBlocks:
...getTrigger('id').subBlocks
Provider Handler (if needed)
- Handler file at
apps/sim/lib/webhooks/providers/{service}.ts - Registered in
providers/registry.ts(alphabetical) - Signature validator is a private function inside the handler file
formatInputoutput keys match triggeroutputsexactly- Event matching uses dynamic
await import()for trigger utils
Auto Registration (if supported)
createSubscriptionanddeleteSubscriptionon the handler- NO changes to
route.ts,provider-subscriptions.ts, ordeploy.ts - API key field uses
password: true
Polling Trigger (if applicable)
- Handler implements
PollingProviderHandleratlib/webhooks/polling/{service}.ts - Trigger config has
polling: trueand defines subBlocks manually (nobuildTriggerSubBlocks) - Provider string matches across: trigger config, handler,
POLLING_PROVIDERS, polling registry - First poll seeds state and emits nothing
- Added provider to
POLLING_PROVIDERSintriggers/constants.ts - Added handler to
POLLING_HANDLERSinlib/webhooks/polling/registry.ts - Added cron job to
helm/sim/values.yaml - Payload shape matches trigger
outputsschema
Testing
bun run type-checkpasses- Manually verify output keys match trigger
outputskeys - Trigger UI shows correctly in the block
- Ran
bun run scripts/generate-docs.tsand committed the refreshed pages — trigger sections render into the owning integration's docs page, and CI'sbun run docs:checkfails on stale pages
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 1
- simstudioai/sim★ 30k85% same
Wrote their own version · 1
- phase-rs/phase★ 306same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository54 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .cursor/rules/constitution.mdcCursor rule
- .cursor/rules/emcn-components.mdcCursor rule
- .cursor/rules/global.mdcCursor rule
- .cursor/rules/landing-seo-geo.mdcCursor rule
- .cursor/rules/sim-architecture.mdcCursor rule
- .cursor/rules/sim-components.mdcCursor rule
- .cursor/rules/sim-hooks.mdcCursor rule
- .cursor/rules/sim-imports.mdcCursor rule
- .cursor/rules/sim-integrations.mdcCursor rule
- .cursor/rules/sim-queries.mdcCursor rule
- .cursor/rules/sim-sandbox.mdcCursor rule
- .cursor/rules/sim-stores.mdcCursor rule
- .cursor/rules/sim-styling.mdcCursor rule
- .cursor/rules/sim-testing.mdcCursor rule
- .cursor/rules/sim-typescript.mdcCursor rule
- .cursor/rules/sim-ui-copy.mdcCursor rule
- add-block-previewSkill
- add-blockSkill
- add-column-typeSkill
- add-connectorSkill
- add-enrichmentSkill
- add-feature-flagSkill
- add-hosted-keySkill
- add-integrationSkill
- add-managed-cliSkill
- add-modelSkill
- add-toolsSkill
- babysitSkill
- cleanupSkill
- councilSkill
- db-migrateSkill
- design-taste-frontendSkill
- emcn-design-reviewSkill
- emil-design-engSkill
- make-interfaces-feel-betterSkill
- memory-load-checkSkill
- migrate-application-operationSkill
- react-query-best-practicesSkill
- shipSkill
- tool-registry-boundarySkill
- v2-api-conventionsSkill
- validate-connectorSkill
- validate-integrationSkill
- validate-modelSkill
- validate-triggerSkill
- you-might-not-need-a-callbackSkill
- you-might-not-need-a-commentSkill
- you-might-not-need-a-memoSkill
- you-might-not-need-an-effectSkill
- you-might-not-need-stateSkill
- you-might-not-need-url-stateSkill
- add-settings-pageSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

