From 0cbce7a92c1e7cd986225fa89204855dae79a882 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 01:18:08 -0700 Subject: [PATCH 1/4] improvement(integrations): add Managed Agents templates and guard docs links The Claude Managed Agents block shipped without templates or suggested skills, so its integration detail page had nothing to offer and the "Add to Sim" chat handoff was the only affordance. Add nine templates and seven skills, each grounded in an operation the block actually exposes. Its docsLink also pointed at integrations/managed-agent while the page is managed_agent, so the link 404'd. Five more blocks had the same class of bug via a stale tools/ prefix. Nothing validated these, because the catalog check only compares deployment fields. Add that validation, and collapse the three copies of the docs-URL contract onto one exported helper so the checker and the generator cannot drift apart. --- apps/sim/blocks/blocks/datagma.ts | 2 +- apps/sim/blocks/blocks/dropcontact.ts | 2 +- apps/sim/blocks/blocks/icypeas.ts | 2 +- apps/sim/blocks/blocks/leadmagic.ts | 2 +- apps/sim/blocks/blocks/managed_agent.ts | 144 +++++++++++++++++++- apps/sim/blocks/blocks/thrive.ts | 2 +- apps/sim/lib/integrations/integrations.json | 12 +- scripts/check-integration-catalog.ts | 64 +++++++++ scripts/generate-docs.ts | 18 ++- 9 files changed, 233 insertions(+), 15 deletions(-) diff --git a/apps/sim/blocks/blocks/datagma.ts b/apps/sim/blocks/blocks/datagma.ts index ddb34da9640..fe29ea7eb8f 100644 --- a/apps/sim/blocks/blocks/datagma.ts +++ b/apps/sim/blocks/blocks/datagma.ts @@ -9,7 +9,7 @@ export const DatagmaBlock: BlockConfig = { authMode: AuthMode.ApiKey, longDescription: 'Integrate Datagma to find verified work emails from a name and company, enrich person profiles via email or LinkedIn URL, enrich company data from a domain or name, look up mobile phone numbers from LinkedIn, and check your credit balance.', - docsLink: 'https://docs.sim.ai/tools/datagma', + docsLink: 'https://docs.sim.ai/integrations/datagma', category: 'tools', integrationType: IntegrationType.Sales, bgColor: '#FFFFFF', diff --git a/apps/sim/blocks/blocks/dropcontact.ts b/apps/sim/blocks/blocks/dropcontact.ts index 1ca6c2a9825..e1cd61c7821 100644 --- a/apps/sim/blocks/blocks/dropcontact.ts +++ b/apps/sim/blocks/blocks/dropcontact.ts @@ -14,7 +14,7 @@ export const DropcontactBlock: BlockConfig = { description: 'Enrich B2B contacts with verified email, phone, and company data', longDescription: 'Use Dropcontact to verify and enrich B2B contacts. Submit a contact with their name, company, website, or LinkedIn URL and receive a verified professional email, phone number, company firmographics, and LinkedIn profile. Enrichment is async: Dropcontact processes the request, then Sim polls until the result is ready. Credits are only charged when a verified email is returned.', - docsLink: 'https://docs.sim.ai/tools/dropcontact', + docsLink: 'https://docs.sim.ai/integrations/dropcontact', category: 'tools', bgColor: '#0ABA9F', iconColor: '#0ABA9F', diff --git a/apps/sim/blocks/blocks/icypeas.ts b/apps/sim/blocks/blocks/icypeas.ts index 1dbdfe7a364..176a2963ab3 100644 --- a/apps/sim/blocks/blocks/icypeas.ts +++ b/apps/sim/blocks/blocks/icypeas.ts @@ -8,7 +8,7 @@ export const IcypeasBlock: BlockConfig = { description: 'Find and verify professional email addresses', longDescription: 'Integrate Icypeas to find a professional email address from a name and company domain, or verify whether an existing email is valid and deliverable. Results are returned asynchronously via polling.', - docsLink: 'https://docs.sim.ai/tools/icypeas', + docsLink: 'https://docs.sim.ai/integrations/icypeas', category: 'tools', integrationType: IntegrationType.Sales, bgColor: '#d4d4d4', diff --git a/apps/sim/blocks/blocks/leadmagic.ts b/apps/sim/blocks/blocks/leadmagic.ts index a2254283448..24af8201d70 100644 --- a/apps/sim/blocks/blocks/leadmagic.ts +++ b/apps/sim/blocks/blocks/leadmagic.ts @@ -18,7 +18,7 @@ export const LeadMagicBlock: BlockConfig = { authMode: AuthMode.ApiKey, longDescription: 'Integrate LeadMagic to find verified work emails by name or company, validate email deliverability, find direct mobile numbers, enrich LinkedIn profiles, reverse-lookup profiles from emails, search companies by domain, identify role holders at accounts, and check account credit balance.', - docsLink: 'https://docs.sim.ai/tools/leadmagic', + docsLink: 'https://docs.sim.ai/integrations/leadmagic', category: 'tools', integrationType: IntegrationType.Sales, bgColor: '#FFFFFF', diff --git a/apps/sim/blocks/blocks/managed_agent.ts b/apps/sim/blocks/blocks/managed_agent.ts index bda4a08d084..d8a5abe14e9 100644 --- a/apps/sim/blocks/blocks/managed_agent.ts +++ b/apps/sim/blocks/blocks/managed_agent.ts @@ -136,7 +136,7 @@ export const ManagedAgentBlock: BlockConfig = { "Invoke a Claude Platform Managed Agent from a workflow. Select a Claude Platform account, pick an agent and environment from that workspace, optionally attach vaults, a memory store, and files, and add metadata tags. Returns the assistant's final text.", category: 'tools', integrationType: IntegrationType.AI, - docsLink: 'https://docs.sim.ai/integrations/managed-agent', + docsLink: 'https://docs.sim.ai/integrations/managed_agent', bgColor: '#DA7756', iconColor: '#DA7756', icon: ClaudeIcon, @@ -620,4 +620,146 @@ export const ManagedAgentBlock: BlockConfig = { export const ManagedAgentBlockMeta = { tags: ['agentic', 'llm'], url: 'https://platform.claude.com/', + templates: [ + { + icon: ClaudeIcon, + title: 'Claude Managed Agents overnight refactor', + prompt: + 'Build a workflow that runs nightly, opens a Claude Managed Agents session against a mounted repository, asks it to work through the migration backlog, and posts the branches it pushed to Slack in the morning.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'engineering', + tags: ['engineering', 'automation'], + alsoIntegrations: ['slack'], + featured: true, + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents PR reviewer', + prompt: + 'Create a workflow that triggers on a new pull request, runs a Claude Managed Agents session over the diff, and posts the findings back as a review comment on the PR.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['engineering', 'code-review'], + alsoIntegrations: ['github'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents approval gate', + prompt: + 'Build a workflow that creates a Claude Managed Agents session, polls it for tool calls waiting on permission, posts each one to Slack for a human decision, and sends the allow or deny answer back to the session so it keeps working.', + modules: ['agent', 'workflows'], + category: 'operations', + tags: ['human-in-the-loop', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents research desk', + prompt: + 'Create a workflow that takes a research question, runs a Claude Managed Agents session to gather and cross-check sources, and writes the findings plus every citation into a table.', + modules: ['tables', 'agent', 'workflows'], + category: 'operations', + tags: ['research', 'automation'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents weekly report', + prompt: + 'Build a scheduled workflow that runs a Claude Managed Agents session every Monday to compile last week’s metrics into a spreadsheet, then emails the finished file to the leadership list.', + modules: ['scheduled', 'files', 'agent', 'workflows'], + category: 'operations', + tags: ['reporting', 'automation'], + alsoIntegrations: ['gmail'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents support triage', + prompt: + 'Create a workflow that opens a Claude Managed Agents session per incoming ticket, has it reproduce the issue and draft a reply, and files a Linear issue when it finds a real bug.', + modules: ['agent', 'workflows'], + category: 'support', + tags: ['support', 'automation'], + alsoIntegrations: ['linear'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents conversational assistant', + prompt: + 'Build a workflow that creates a Claude Managed Agents session on the first Slack message in a thread, stores the session id, and sends every later reply in that thread to the same session so the agent keeps its context across runs.', + modules: ['tables', 'agent', 'workflows'], + category: 'productivity', + tags: ['messaging', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents run auditor', + prompt: + 'Create a workflow that reads the event history of a finished Claude Managed Agents session, extracts every tool call and its result, and writes a per-run audit row into a table for compliance review.', + modules: ['tables', 'agent', 'workflows'], + category: 'operations', + tags: ['compliance', 'monitoring'], + }, + { + icon: ClaudeIcon, + title: 'Claude Managed Agents runaway stopper', + prompt: + 'Build a scheduled workflow that checks long-running Claude Managed Agents sessions, interrupts any that have been working past a threshold, and archives the ones that already finished.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['monitoring', 'automation'], + alsoIntegrations: ['slack'], + }, + ], + skills: [ + { + name: 'run-managed-agent-task', + description: + 'Send one task to a Claude Platform Managed Agent and wait for its final answer. Use for a self-contained job where you only need the result, not the intermediate steps.', + content: + '# Run Managed Agent Task\n\nHand a Claude Platform Managed Agent a single task and return what it produced.\n\n## Steps\n1. Choose the **Run session** operation — it creates a session, sends one message, waits for the agent to finish, and returns its text in one block.\n2. Select the Claude Platform account, then pick the Agent and Environment from that workspace. The agent already carries its own model, system prompt, and tools — you supply the task, not the configuration.\n3. Write the User message as a complete brief: the goal, the constraints, and what "done" looks like. The agent cannot ask a follow-up question mid-run, so anything you leave out it has to guess.\n4. Attach what the task needs: credential vaults for any MCP servers the agent calls, files it should read, and a memory store if it should carry context from earlier sessions.\n\n## Output\nReturn the agent\'s final text, plus the session id so the run can be traced later. Report the outcome as the agent stated it — if it says a step failed or was skipped, pass that through rather than summarizing it as success.', + }, + { + name: 'resume-managed-agent-conversation', + description: + 'Keep one Managed Agent session alive across separate workflow runs so follow-up turns retain earlier context. Use for chat threads, ticket conversations, and any multi-turn exchange.', + content: + "# Resume Managed Agent Conversation\n\nHold a conversation with a Managed Agent across runs instead of starting over each time.\n\n## Steps\n1. On the first turn, use **Create session** — it opens the session and returns its id without waiting for a reply. Store that id somewhere durable (a table row keyed by the thread or ticket).\n2. On every later turn, use **Send message** with the stored Session ID. The agent still has its earlier turns; do not re-send the history.\n3. Use **Get session** when you need to know whether the agent is still working, is waiting on you, or has finished before you send the next message.\n4. When the conversation ends, use **Archive session** to make it read-only, or **Delete session** to remove it and its history entirely.\n\n## Output\nReturn the agent's reply for this turn and the session id you used. If the session turned out to be already terminated or archived, say so plainly rather than silently opening a new one — a fresh session loses every earlier turn.", + }, + { + name: 'approve-managed-agent-tool-calls', + description: + 'Answer the permission prompts a Managed Agent raises before running a gated tool. Use when the agent is configured to ask before acting and a human or policy decides each call.', + content: + '# Approve Managed Agent Tool Calls\n\nUnblock a session that has paused waiting for permission to run a tool.\n\n## Steps\n1. Use **Get session** to see the session status and the tool calls currently waiting on a decision. A session sitting idle with pending calls is blocked until you answer.\n2. For each pending call, decide allow or deny. Route it to a person when the action is hard to reverse — sending mail, deleting data, pushing to a shared branch.\n3. Use **Respond to tool confirmation** with the tool use event IDs and the decision. Answer every pending call; one left unanswered keeps the session blocked.\n4. On a deny, write a Deny reason saying what to do instead. The agent reads it and adjusts, rather than retrying the same call.\n\n## Output\nReturn which calls were allowed and which were denied, with the reason for each denial, and confirm the session resumed. If it did not resume, report which calls are still pending instead of assuming the answers landed.', + }, + { + name: 'answer-managed-agent-custom-tool', + description: + 'Execute a custom tool a Managed Agent invoked and return the result to the session. Use when the agent calls a tool your own workflow implements rather than one the platform runs.', + content: + "# Answer Managed Agent Custom Tool\n\nRun a tool the agent asked for and hand back what it produced.\n\n## Steps\n1. Use **Get session** or **List events** to find the custom tool call the agent is waiting on, and read the arguments it passed.\n2. Do the work in your workflow — call the API, query the table, run the function. This is the point of a custom tool: the credential and the logic stay on your side, never inside the agent's sandbox.\n3. Use **Respond to custom tool** with the custom tool use event ID and the result. Keep the result focused — the agent reads it directly, so return the fields it needs rather than a raw dump.\n4. If the work failed, still respond, and mark it as an error with the reason. The agent can then try a different approach instead of waiting on a result that will never come.\n\n## Output\nReturn what the tool did and what you sent back, and confirm the session resumed. Never fabricate a result to unblock a session — an invented answer propagates into everything the agent does next.", + }, + { + name: 'audit-managed-agent-session', + description: + 'Reconstruct what a Managed Agent actually did from its event history. Use for compliance review, debugging a bad run, or reporting cost and token usage per run.', + content: + '# Audit Managed Agent Session\n\nBuild an evidence trail for one session from the events it emitted.\n\n## Steps\n1. Use **Get session** for the summary: status, title, metadata, and cumulative input and output tokens.\n2. Use **List events** to read the history. Narrow with Event types when you only care about one kind — tool calls, messages, status changes — and raise Max events when the run was long enough that the default page truncates it.\n3. Walk the events in order and pair each tool call with its result, so the record shows what the agent attempted and what came back, not just what it said afterwards.\n4. Write the reconstructed trail wherever it needs to live — a table row per run, a file, a message to the reviewing channel.\n\n## Output\nReturn the ordered list of what the agent did, the token totals, and the final status. Base every claim on an event you actually read; if the history was truncated, say where it stops rather than describing the run as complete.', + }, + { + name: 'stop-runaway-managed-agent-session', + description: + 'Halt a Managed Agent session that is working longer than it should and clean it up. Use for cost control and for cancelling work a user changed their mind about.', + content: + '# Stop Runaway Managed Agent Session\n\nStop work in progress without losing what the session already produced.\n\n## Steps\n1. Use **Get session** to confirm the session is genuinely running rather than idle and waiting on you — a session blocked on a tool confirmation needs an answer, not an interrupt.\n2. Use **Interrupt session** to stop it. The agent halts at a safe point and goes idle; it does not treat the interrupt as a message, so its history and outputs survive.\n3. Send a follow-up message if you want it to continue differently. The session is still usable after an interrupt.\n4. When you are finished with it, use **Archive session** to keep the record read-only, or **Delete session** to remove the session and its history for good. Deletion is not reversible.\n\n## Output\nReturn why the session was stopped, what it had completed at that point, and whether you archived or deleted it. Prefer archive over delete when anyone may need the history later.', + }, + { + name: 'give-managed-agent-persistent-memory', + description: + 'Attach a memory store so an agent carries learnings between sessions. Use when repeated runs should build on earlier ones instead of starting cold.', + content: + "# Give Managed Agent Persistent Memory\n\nLet an agent remember across sessions rather than only within one.\n\n## Steps\n1. Set the Environment type to cloud — memory stores and file attachments are cloud-only, and a self-hosted environment rejects them.\n2. On **Run session** or **Create session**, select the Memory store the agent should mount. Choose read-only when the store is shared reference material the agent must not edit; choose read-write when it should record what it learns.\n3. Write Memory instructions saying what lives in the store and when to consult it. The agent reads that description to decide whether the store is relevant, so describe the contents, not the mechanics.\n4. Keep secrets out of the store. Memories are replayed verbatim into every later session that mounts it — put API keys and tokens in a credential vault instead.\n\n## Output\nReturn the agent's result and note whether it read from or wrote to the store. If the agent reports that expected context was missing, say so rather than assuming the store was mounted correctly.", + }, + ], } as const satisfies BlockMeta diff --git a/apps/sim/blocks/blocks/thrive.ts b/apps/sim/blocks/blocks/thrive.ts index c70539a473b..bb3b36bd713 100644 --- a/apps/sim/blocks/blocks/thrive.ts +++ b/apps/sim/blocks/blocks/thrive.ts @@ -75,7 +75,7 @@ export const ThriveBlock: BlockConfig = { description: 'Manage users, audiences, learning and CPD on Thrive', longDescription: 'Integrate Thrive Learning into the workflow. Manage user lifecycle, audiences and their members and managers, content assignments and enrolments, learning completions, content and activity records, CPD, tags, and skills.', - docsLink: 'https://docs.sim.ai/tools/thrive', + docsLink: 'https://docs.sim.ai/integrations/thrive', category: 'tools', integrationType: IntegrationType.HR, bgColor: '#FFFFFF', diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 8895c5a4c87..c5cbca6a1a6 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -2972,7 +2972,7 @@ "longDescription": "Invoke a Claude Platform Managed Agent from a workflow. Select a Claude Platform account, pick an agent and environment from that workspace, optionally attach vaults, a memory store, and files, and add metadata tags. Returns the assistant's final text.", "bgColor": "#DA7756", "iconName": "ClaudeIcon", - "docsUrl": "https://docs.sim.ai/integrations/managed-agent", + "docsUrl": "https://docs.sim.ai/integrations/managed_agent", "operations": [ { "name": "Run session (create, send, wait for reply)", @@ -4797,7 +4797,7 @@ "longDescription": "Integrate Datagma to find verified work emails from a name and company, enrich person profiles via email or LinkedIn URL, enrich company data from a domain or name, look up mobile phone numbers from LinkedIn, and check your credit balance.", "bgColor": "#FFFFFF", "iconName": "DatagmaIcon", - "docsUrl": "https://docs.sim.ai/tools/datagma", + "docsUrl": "https://docs.sim.ai/integrations/datagma", "operations": [ { "name": "Find Email", @@ -5344,7 +5344,7 @@ "longDescription": "Use Dropcontact to verify and enrich B2B contacts. Submit a contact with their name, company, website, or LinkedIn URL and receive a verified professional email, phone number, company firmographics, and LinkedIn profile. Enrichment is async: Dropcontact processes the request, then Sim polls until the result is ready. Credits are only charged when a verified email is returned.", "bgColor": "#0ABA9F", "iconName": "DropcontactIcon", - "docsUrl": "https://docs.sim.ai/tools/dropcontact", + "docsUrl": "https://docs.sim.ai/integrations/dropcontact", "operations": [ { "name": "Enrich Contact", @@ -9691,7 +9691,7 @@ "longDescription": "Integrate Icypeas to find a professional email address from a name and company domain, or verify whether an existing email is valid and deliverable. Results are returned asynchronously via polling.", "bgColor": "#d4d4d4", "iconName": "IcypeasIcon", - "docsUrl": "https://docs.sim.ai/tools/icypeas", + "docsUrl": "https://docs.sim.ai/integrations/icypeas", "operations": [ { "name": "Find Email", @@ -11246,7 +11246,7 @@ "longDescription": "Integrate LeadMagic to find verified work emails by name or company, validate email deliverability, find direct mobile numbers, enrich LinkedIn profiles, reverse-lookup profiles from emails, search companies by domain, identify role holders at accounts, and check account credit balance.", "bgColor": "#FFFFFF", "iconName": "LeadMagicIcon", - "docsUrl": "https://docs.sim.ai/tools/leadmagic", + "docsUrl": "https://docs.sim.ai/integrations/leadmagic", "operations": [ { "name": "Find Email", @@ -20387,7 +20387,7 @@ "longDescription": "Integrate Thrive Learning into the workflow. Manage user lifecycle, audiences and their members and managers, content assignments and enrolments, learning completions, content and activity records, CPD, tags, and skills.", "bgColor": "#FFFFFF", "iconName": "ThriveIcon", - "docsUrl": "https://docs.sim.ai/tools/thrive", + "docsUrl": "https://docs.sim.ai/integrations/thrive", "operations": [ { "name": "Create User", diff --git a/scripts/check-integration-catalog.ts b/scripts/check-integration-catalog.ts index 2524f70c010..546feebe5b5 100644 --- a/scripts/check-integration-catalog.ts +++ b/scripts/check-integration-catalog.ts @@ -1,4 +1,6 @@ #!/usr/bin/env bun +import { existsSync } from 'node:fs' +import { dirname, join } from 'node:path' import { stripVersionSuffix } from '@sim/utils/string' /** * Verifies the registry-free integration catalog matches the executable block @@ -7,6 +9,7 @@ import { stripVersionSuffix } from '@sim/utils/string' import { BLOCK_REGISTRY } from '../apps/sim/blocks/registry-maps' import { AuthMode, type BlockConfig } from '../apps/sim/blocks/types' import integrationsJson from '../apps/sim/lib/integrations/integrations.json' +import { DOCS_ORIGIN, DOCS_OUTPUT_PATH, defaultIntegrationDocsUrl } from './generate-docs' type CatalogAuthType = 'oauth' | 'api-key' | 'none' @@ -70,10 +73,71 @@ function expectedEntry(block: BlockConfig): CatalogEntry { } } +/** + * A `docsLink` path carries its own section segment (`integrations/…`), so it + * resolves against the locale root rather than the integrations directory. + */ +const DOCS_LOCALE_ROOT = dirname(DOCS_OUTPUT_PATH) + +/** + * Integrations that deliberately send readers to the vendor's own documentation + * instead of Sim's generated page. Listed explicitly so a newly pasted vendor + * URL — or a typo in one of these — fails the check rather than passing as + * "probably intentional". + */ +const VENDOR_DOCS_INTEGRATIONS: ReadonlySet = new Set([ + 'cursor_v2', + 'enrich', + 'enrow', + 'google_groups', + 'qdrant', + 'similarweb', +]) + +/** + * Verifies every visible integration's `docsLink` resolves to a real docs page. + * + * A hand-written `docsLink` overrides {@link defaultIntegrationDocsUrl}, so a + * typo — a hyphen where the page uses an underscore, or a stale `tools/` prefix + * — silently ships a 404 that no other check looks at. The catalog comparison + * below only covers deployment fields. + */ +function verifyDocsLinks(blocks: readonly BlockConfig[]): void { + const issues: string[] = [] + for (const block of blocks) { + const docsLink = block.docsLink ?? defaultIntegrationDocsUrl(block.type) + if (!docsLink.startsWith(DOCS_ORIGIN)) { + if (!VENDOR_DOCS_INTEGRATIONS.has(block.type)) { + issues.push( + `"${block.type}" docsLink points outside ${DOCS_ORIGIN} (${docsLink}) — add it to VENDOR_DOCS_INTEGRATIONS if that is intentional` + ) + } + continue + } + const page = docsLink.slice(DOCS_ORIGIN.length) + if (!existsSync(join(DOCS_LOCALE_ROOT, `${page}.mdx`))) { + issues.push(`"${block.type}" docsLink 404s — no docs page at en/${page}.mdx (${docsLink})`) + } + } + const staleAllowlist = [...VENDOR_DOCS_INTEGRATIONS].filter( + (type) => + !blocks.some((block) => block.type === type && !block.docsLink?.startsWith(DOCS_ORIGIN)) + ) + for (const type of staleAllowlist) { + issues.push(`"${type}" is in VENDOR_DOCS_INTEGRATIONS but no longer links to vendor docs`) + } + if (issues.length > 0) { + throw new Error( + `Integration docs links are broken:\n- ${issues.join('\n- ')}\nPoint docsLink at an existing page, or drop it to use the generated default.` + ) + } +} + function verifyIntegrationCatalog(): void { const expected = Object.values(BLOCK_REGISTRY).filter( (block) => block.category === 'tools' && !block.hideFromToolbar && !block.preview ) + verifyDocsLinks(expected) const expectedBaseTypes = new Map() for (const block of expected) { const baseType = stripVersionSuffix(block.type) diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 12af628583c..1a07fbfbc22 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -19,7 +19,19 @@ const __dirname = path.dirname(__filename) const rootDir = path.resolve(__dirname, '..') const BLOCKS_PATH = path.join(rootDir, 'apps/sim/blocks/blocks') -const DOCS_OUTPUT_PATH = path.join(rootDir, 'apps/docs/content/docs/en/integrations') +export const DOCS_OUTPUT_PATH = path.join(rootDir, 'apps/docs/content/docs/en/integrations') + +export const DOCS_ORIGIN = 'https://docs.sim.ai/' + +/** + * The docs URL a block gets when it declares no `docsLink` — one generated page + * per service, named for the block's base type. Exported so the catalog checker + * validates the same contract this generator emits rather than a second copy of + * it that can silently drift. + */ +export function defaultIntegrationDocsUrl(blockType: string): string { + return `${DOCS_ORIGIN}integrations/${stripVersionSuffix(blockType)}` +} const ICONS_PATH = path.join(rootDir, 'apps/sim/components/icons.tsx') const DOCS_ICONS_PATH = path.join(rootDir, 'apps/docs/components/icons.tsx') const INTEGRATIONS_DATA_PATH = path.join(rootDir, 'apps/sim/lib/integrations') @@ -1041,7 +1053,7 @@ async function writeIntegrationsJson(iconMapping: Record): Prom const triggers: TriggerInfo[] = triggerIds .map((id) => triggerRegistry.get(id)) .filter((t): t is TriggerInfo => t !== undefined) - const docsUrl = (config as any).docsLink || `https://docs.sim.ai/integrations/${baseType}` + const docsUrl = (config as any).docsLink || defaultIntegrationDocsUrl(baseType) const slug = config.name .toLowerCase() @@ -1259,7 +1271,7 @@ function extractBlockConfigFromContent( const docsLink = extractStringPropertyFromContent(blockContent, 'docsLink', true) || baseConfig?.docsLink || - `https://docs.sim.ai/integrations/${stripVersionSuffix(blockType)}` + defaultIntegrationDocsUrl(blockType) const integrationType = extractEnumPropertyFromContent(blockContent, 'integrationType') || From 12dc12bd5923165d32b6b04efa507011d464cb1a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 01:23:29 -0700 Subject: [PATCH 2/4] fix(integrations): resolve docsLink once when checking the vendor allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stale-allowlist predicate read block.docsLink directly while the main loop read the resolved link, so an allowlisted block that dropped its explicit docsLink produced undefined from the optional chain, negated to true, and was treated as still vendor-linked — the stale entry went undetected. Record vendor-linked types during the single pass that already resolves each link, so both checks agree by construction. --- scripts/check-integration-catalog.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/check-integration-catalog.ts b/scripts/check-integration-catalog.ts index 546feebe5b5..18f24fff7b1 100644 --- a/scripts/check-integration-catalog.ts +++ b/scripts/check-integration-catalog.ts @@ -104,9 +104,11 @@ const VENDOR_DOCS_INTEGRATIONS: ReadonlySet = new Set([ */ function verifyDocsLinks(blocks: readonly BlockConfig[]): void { const issues: string[] = [] + const vendorLinked = new Set() for (const block of blocks) { const docsLink = block.docsLink ?? defaultIntegrationDocsUrl(block.type) if (!docsLink.startsWith(DOCS_ORIGIN)) { + vendorLinked.add(block.type) if (!VENDOR_DOCS_INTEGRATIONS.has(block.type)) { issues.push( `"${block.type}" docsLink points outside ${DOCS_ORIGIN} (${docsLink}) — add it to VENDOR_DOCS_INTEGRATIONS if that is intentional` @@ -119,12 +121,10 @@ function verifyDocsLinks(blocks: readonly BlockConfig[]): void { issues.push(`"${block.type}" docsLink 404s — no docs page at en/${page}.mdx (${docsLink})`) } } - const staleAllowlist = [...VENDOR_DOCS_INTEGRATIONS].filter( - (type) => - !blocks.some((block) => block.type === type && !block.docsLink?.startsWith(DOCS_ORIGIN)) - ) - for (const type of staleAllowlist) { - issues.push(`"${type}" is in VENDOR_DOCS_INTEGRATIONS but no longer links to vendor docs`) + for (const type of VENDOR_DOCS_INTEGRATIONS) { + if (!vendorLinked.has(type)) { + issues.push(`"${type}" is in VENDOR_DOCS_INTEGRATIONS but no longer links to vendor docs`) + } } if (issues.length > 0) { throw new Error( From 3d7c1eb3aa0d0fbb14fd40fb166663bc39f33eb8 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 01:27:18 -0700 Subject: [PATCH 3/4] fix(managed-agents): name every integration a template's alsoIntegrations claims alsoIntegrations is documented as the blocks a template's prompt references, and it drives which catalog pages the template cross-lists on plus the icon cluster on the detail page. Three prompts named a service only implicitly, or not at all: the runaway stopper claimed Slack without mentioning it, the PR reviewer said "pull request" rather than GitHub, and the weekly report said "emails" rather than Gmail. Name the service in each prompt so the field is accurate and the templates surface on the right pages. --- apps/sim/blocks/blocks/managed_agent.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/sim/blocks/blocks/managed_agent.ts b/apps/sim/blocks/blocks/managed_agent.ts index d8a5abe14e9..4b17314ab61 100644 --- a/apps/sim/blocks/blocks/managed_agent.ts +++ b/apps/sim/blocks/blocks/managed_agent.ts @@ -636,7 +636,7 @@ export const ManagedAgentBlockMeta = { icon: ClaudeIcon, title: 'Claude Managed Agents PR reviewer', prompt: - 'Create a workflow that triggers on a new pull request, runs a Claude Managed Agents session over the diff, and posts the findings back as a review comment on the PR.', + 'Create a workflow that triggers on a new GitHub pull request, runs a Claude Managed Agents session over the diff, and posts the findings back as a review comment on the PR.', modules: ['agent', 'workflows'], category: 'engineering', tags: ['engineering', 'code-review'], @@ -665,7 +665,7 @@ export const ManagedAgentBlockMeta = { icon: ClaudeIcon, title: 'Claude Managed Agents weekly report', prompt: - 'Build a scheduled workflow that runs a Claude Managed Agents session every Monday to compile last week’s metrics into a spreadsheet, then emails the finished file to the leadership list.', + 'Build a scheduled workflow that runs a Claude Managed Agents session every Monday to compile last week’s metrics into a spreadsheet, then sends the finished file to the leadership list over Gmail.', modules: ['scheduled', 'files', 'agent', 'workflows'], category: 'operations', tags: ['reporting', 'automation'], @@ -704,7 +704,7 @@ export const ManagedAgentBlockMeta = { icon: ClaudeIcon, title: 'Claude Managed Agents runaway stopper', prompt: - 'Build a scheduled workflow that checks long-running Claude Managed Agents sessions, interrupts any that have been working past a threshold, and archives the ones that already finished.', + 'Build a scheduled workflow that checks long-running Claude Managed Agents sessions, interrupts any that have been working past a threshold, archives the ones that already finished, and posts what it stopped to Slack.', modules: ['scheduled', 'agent', 'workflows'], category: 'operations', tags: ['monitoring', 'automation'], From 7e01b8932ba9d81649696366e209659c58a07d66 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 01:30:34 -0700 Subject: [PATCH 4/4] fix(integrations): point every visible integration at Sim's own docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six blocks had a vendor documentation URL in docsLink — cursor, enrich, enrow, google_groups, qdrant, and similarweb — which was accidental rather than deliberate. Each already has a generated Sim page, and each already carries the vendor's homepage on BlockMeta.url, so the vendor link in docsLink only sent readers away from our own documentation. Point all six at their Sim page and drop the allowlist that had been tolerating them. Every visible integration gets a generated page, so a docsLink outside docs.sim.ai is now always an error. --- apps/sim/blocks/blocks/cursor.ts | 2 +- apps/sim/blocks/blocks/enrich.ts | 2 +- apps/sim/blocks/blocks/enrow.ts | 2 +- apps/sim/blocks/blocks/google_groups.ts | 2 +- apps/sim/blocks/blocks/qdrant.ts | 2 +- apps/sim/blocks/blocks/similarweb.ts | 2 +- apps/sim/lib/integrations/integrations.json | 12 +++--- scripts/check-integration-catalog.ts | 41 +++++---------------- 8 files changed, 22 insertions(+), 43 deletions(-) diff --git a/apps/sim/blocks/blocks/cursor.ts b/apps/sim/blocks/blocks/cursor.ts index b9ff02f7658..fca94300df6 100644 --- a/apps/sim/blocks/blocks/cursor.ts +++ b/apps/sim/blocks/blocks/cursor.ts @@ -10,7 +10,7 @@ export const CursorBlock: BlockConfig = { description: 'Launch and manage Cursor cloud agents to work on GitHub repositories', longDescription: 'Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.', - docsLink: 'https://cursor.com/docs/cloud-agent/api/endpoints', + docsLink: 'https://docs.sim.ai/integrations/cursor', category: 'tools', integrationType: IntegrationType.DevOps, bgColor: '#1E1E1E', diff --git a/apps/sim/blocks/blocks/enrich.ts b/apps/sim/blocks/blocks/enrich.ts index 343344a2bd7..548de939f5c 100644 --- a/apps/sim/blocks/blocks/enrich.ts +++ b/apps/sim/blocks/blocks/enrich.ts @@ -15,7 +15,7 @@ export const EnrichBlock: BlockConfig = { authMode: AuthMode.ApiKey, longDescription: 'Access real-time B2B data intelligence with Enrich.so. Enrich profiles from email addresses, find work emails from LinkedIn, verify email deliverability, search for people and companies, and analyze LinkedIn post engagement.', - docsLink: 'https://docs.enrich.so/', + docsLink: 'https://docs.sim.ai/integrations/enrich', category: 'tools', integrationType: IntegrationType.Sales, bgColor: '#E5E5E6', diff --git a/apps/sim/blocks/blocks/enrow.ts b/apps/sim/blocks/blocks/enrow.ts index 3b47739fc41..d3e50586636 100644 --- a/apps/sim/blocks/blocks/enrow.ts +++ b/apps/sim/blocks/blocks/enrow.ts @@ -9,7 +9,7 @@ export const EnrowBlock: BlockConfig = { authMode: AuthMode.ApiKey, longDescription: 'Integrate Enrow to find verified B2B email addresses from a full name and company, or verify the deliverability of an existing email. Enrow performs deterministic verifications including catch-all emails — no additional verifier needed.', - docsLink: 'https://enrow.readme.io', + docsLink: 'https://docs.sim.ai/integrations/enrow', category: 'tools', integrationType: IntegrationType.Sales, bgColor: '#FFFFFF', diff --git a/apps/sim/blocks/blocks/google_groups.ts b/apps/sim/blocks/blocks/google_groups.ts index 0aae55a5791..14ffa3576a1 100644 --- a/apps/sim/blocks/blocks/google_groups.ts +++ b/apps/sim/blocks/blocks/google_groups.ts @@ -11,7 +11,7 @@ export const GoogleGroupsBlock: BlockConfig = { authMode: AuthMode.OAuth, longDescription: 'Connect to Google Workspace to create, update, and manage groups and their members using the Admin SDK Directory API.', - docsLink: 'https://developers.google.com/admin-sdk/directory/v1/guides/manage-groups', + docsLink: 'https://docs.sim.ai/integrations/google_groups', category: 'tools', integrationType: IntegrationType.Communication, bgColor: '#E8F0FE', diff --git a/apps/sim/blocks/blocks/qdrant.ts b/apps/sim/blocks/blocks/qdrant.ts index b1722a82c7c..45a8a605e0d 100644 --- a/apps/sim/blocks/blocks/qdrant.ts +++ b/apps/sim/blocks/blocks/qdrant.ts @@ -9,7 +9,7 @@ export const QdrantBlock: BlockConfig = { description: 'Use Qdrant vector database', authMode: AuthMode.ApiKey, longDescription: 'Integrate Qdrant into the workflow. Can upsert, search, and fetch points.', - docsLink: 'https://qdrant.tech/documentation/', + docsLink: 'https://docs.sim.ai/integrations/qdrant', category: 'tools', integrationType: IntegrationType.Databases, bgColor: '#1A223F', diff --git a/apps/sim/blocks/blocks/similarweb.ts b/apps/sim/blocks/blocks/similarweb.ts index 254ff7be363..c710f6eab99 100644 --- a/apps/sim/blocks/blocks/similarweb.ts +++ b/apps/sim/blocks/blocks/similarweb.ts @@ -8,7 +8,7 @@ export const SimilarwebBlock: BlockConfig = { description: 'Website traffic and analytics data', longDescription: 'Access comprehensive website analytics including traffic estimates, engagement metrics, rankings, and traffic sources using the Similarweb API.', - docsLink: 'https://developers.similarweb.com/docs/similarweb-web-traffic-api', + docsLink: 'https://docs.sim.ai/integrations/similarweb', category: 'tools', integrationType: IntegrationType.Analytics, bgColor: '#000922', diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index c5cbca6a1a6..fc19c6eab27 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -4501,7 +4501,7 @@ "longDescription": "Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.", "bgColor": "#1E1E1E", "iconName": "CursorIcon", - "docsUrl": "https://cursor.com/docs/cloud-agent/api/endpoints", + "docsUrl": "https://docs.sim.ai/integrations/cursor", "operations": [ { "name": "Launch Agent", @@ -6015,7 +6015,7 @@ "longDescription": "Access real-time B2B data intelligence with Enrich.so. Enrich profiles from email addresses, find work emails from LinkedIn, verify email deliverability, search for people and companies, and analyze LinkedIn post engagement.", "bgColor": "#E5E5E6", "iconName": "EnrichSoIcon", - "docsUrl": "https://docs.enrich.so/", + "docsUrl": "https://docs.sim.ai/integrations/enrich", "operations": [ { "name": "Email to Profile", @@ -6162,7 +6162,7 @@ "longDescription": "Integrate Enrow to find verified B2B email addresses from a full name and company, or verify the deliverability of an existing email. Enrow performs deterministic verifications including catch-all emails — no additional verifier needed.", "bgColor": "#FFFFFF", "iconName": "EnrowIcon", - "docsUrl": "https://enrow.readme.io", + "docsUrl": "https://docs.sim.ai/integrations/enrow", "operations": [ { "name": "Find Email", @@ -8165,7 +8165,7 @@ "longDescription": "Connect to Google Workspace to create, update, and manage groups and their members using the Admin SDK Directory API.", "bgColor": "#E8F0FE", "iconName": "GoogleGroupsIcon", - "docsUrl": "https://developers.google.com/admin-sdk/directory/v1/guides/manage-groups", + "docsUrl": "https://docs.sim.ai/integrations/google_groups", "operations": [ { "name": "List Groups", @@ -14892,7 +14892,7 @@ "longDescription": "Integrate Qdrant into the workflow. Can upsert, search, and fetch points.", "bgColor": "#1A223F", "iconName": "QdrantIcon", - "docsUrl": "https://qdrant.tech/documentation/", + "docsUrl": "https://docs.sim.ai/integrations/qdrant", "operations": [ { "name": "Upsert", @@ -17868,7 +17868,7 @@ "longDescription": "Access comprehensive website analytics including traffic estimates, engagement metrics, rankings, and traffic sources using the Similarweb API.", "bgColor": "#000922", "iconName": "SimilarwebIcon", - "docsUrl": "https://developers.similarweb.com/docs/similarweb-web-traffic-api", + "docsUrl": "https://docs.sim.ai/integrations/similarweb", "operations": [ { "name": "Website Overview", diff --git a/scripts/check-integration-catalog.ts b/scripts/check-integration-catalog.ts index 18f24fff7b1..955f788e1dc 100644 --- a/scripts/check-integration-catalog.ts +++ b/scripts/check-integration-catalog.ts @@ -80,40 +80,24 @@ function expectedEntry(block: BlockConfig): CatalogEntry { const DOCS_LOCALE_ROOT = dirname(DOCS_OUTPUT_PATH) /** - * Integrations that deliberately send readers to the vendor's own documentation - * instead of Sim's generated page. Listed explicitly so a newly pasted vendor - * URL — or a typo in one of these — fails the check rather than passing as - * "probably intentional". - */ -const VENDOR_DOCS_INTEGRATIONS: ReadonlySet = new Set([ - 'cursor_v2', - 'enrich', - 'enrow', - 'google_groups', - 'qdrant', - 'similarweb', -]) - -/** - * Verifies every visible integration's `docsLink` resolves to a real docs page. + * Verifies every visible integration's `docsLink` resolves to a real Sim docs + * page. * * A hand-written `docsLink` overrides {@link defaultIntegrationDocsUrl}, so a - * typo — a hyphen where the page uses an underscore, or a stale `tools/` prefix - * — silently ships a 404 that no other check looks at. The catalog comparison - * below only covers deployment fields. + * typo — a hyphen where the page uses an underscore, a stale `tools/` prefix, + * or a vendor URL pasted in place of ours — silently ships a link that misses + * Sim's own page, and no other check looks at it. Every visible integration + * gets a generated page, so pointing anywhere else is always a mistake; link to + * the vendor from `BlockMeta.url` instead. */ function verifyDocsLinks(blocks: readonly BlockConfig[]): void { const issues: string[] = [] - const vendorLinked = new Set() for (const block of blocks) { const docsLink = block.docsLink ?? defaultIntegrationDocsUrl(block.type) if (!docsLink.startsWith(DOCS_ORIGIN)) { - vendorLinked.add(block.type) - if (!VENDOR_DOCS_INTEGRATIONS.has(block.type)) { - issues.push( - `"${block.type}" docsLink points outside ${DOCS_ORIGIN} (${docsLink}) — add it to VENDOR_DOCS_INTEGRATIONS if that is intentional` - ) - } + issues.push( + `"${block.type}" docsLink points outside ${DOCS_ORIGIN} (${docsLink}) — link to Sim's own page and put the vendor's docs on BlockMeta.url` + ) continue } const page = docsLink.slice(DOCS_ORIGIN.length) @@ -121,11 +105,6 @@ function verifyDocsLinks(blocks: readonly BlockConfig[]): void { issues.push(`"${block.type}" docsLink 404s — no docs page at en/${page}.mdx (${docsLink})`) } } - for (const type of VENDOR_DOCS_INTEGRATIONS) { - if (!vendorLinked.has(type)) { - issues.push(`"${type}" is in VENDOR_DOCS_INTEGRATIONS but no longer links to vendor docs`) - } - } if (issues.length > 0) { throw new Error( `Integration docs links are broken:\n- ${issues.join('\n- ')}\nPoint docsLink at an existing page, or drop it to use the generated default.`