From 8b4dbc384ca14b967d9b5e8ae68ebd7f4de2f5ec Mon Sep 17 00:00:00 2001 From: Sam Kujovich Date: Mon, 19 Oct 2026 11:43:49 -0700 Subject: [PATCH 1/6] fix(integrations): list the toolkits the public catalog publishes The integrations index built its list from the design-system TOOLKITS array, so it drifted from production whenever the pinned package fell behind: Twilio showed as a coming-soon Verified server after it launched, and Workday needed a hand-written override. Build the index from the nightly toolkit-docs-generator output instead. That data comes from the same experience-api public catalog that arcade.dev/tools snapshots, so both sites list the same toolkits with the same badges. Coming-soon placeholders that exist only in the design system are no longer listed, and Math and ArcadeEngineApi are hidden to match arcade.dev/tools. --- app/_lib/integration-catalog.ts | 99 ++++++++++++++----------------- tests/integration-catalog.test.ts | 97 ++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 53 deletions(-) create mode 100644 tests/integration-catalog.test.ts diff --git a/app/_lib/integration-catalog.ts b/app/_lib/integration-catalog.ts index fc518ba7f..b1f0f1e47 100644 --- a/app/_lib/integration-catalog.ts +++ b/app/_lib/integration-catalog.ts @@ -1,65 +1,58 @@ -import type { Toolkit } from "@arcadeai/design-system"; -import { TOOLKITS } from "@arcadeai/design-system/metadata/toolkits"; +import type { ToolkitData } from "@/app/_components/toolkit-docs/types"; import { PARTNER_TOOLKITS } from "@/app/_data/partner-toolkits"; -import { normalizeToolkitId } from "@/toolkit-docs-generator/src/shared/toolkit-primitives"; -import { readToolkitData } from "./toolkit-data"; +import { resolveToolkitDataDir } from "@/toolkit-docs-generator/src/shared/toolkit-data-dir"; +import { loadAllToolkitData } from "./toolkit-data"; import type { ToolkitWithDocsLink } from "./toolkit-slug"; -const getToolkitDocsLink = (toolkit: Toolkit): string | undefined => { - if ("docsLink" in toolkit) { - const value = (toolkit as ToolkitWithDocsLink).docsLink; - return value ?? undefined; - } - return; -}; - /** - * Toolkits that have launched but are still flagged `isComingSoon` in the - * design-system catalog. Remove an entry once DS ships the flag as `false`. + * Engine plumbing the public catalog lists like any other toolkit. Nothing in + * the catalog separates these from a product, so they are named here. Keep in + * step with arcade.dev/tools, which hides the same set. */ -const LAUNCHED_TOOLKIT_IDS = new Set(["workday"]); - -const clearLaunchedComingSoon = (toolkit: Toolkit): Toolkit => - LAUNCHED_TOOLKIT_IDS.has(normalizeToolkitId(toolkit.id)) - ? { ...toolkit, isComingSoon: false } - : toolkit; +const INTERNAL_TOOLKIT_IDS: ReadonlySet = new Set([ + "Math", + "ArcadeEngineApi", +]); + +const toCatalogToolkit = ({ + id, + label, + metadata, +}: ToolkitData): ToolkitWithDocsLink => ({ + id, + label, + isBYOC: metadata.isBYOC, + isPro: metadata.isPro, + publicIconUrl: metadata.iconUrl, + docsLink: metadata.docsLink, + relativeDocsLink: new URL(metadata.docsLink).pathname, + type: metadata.type, + category: metadata.category, + isComingSoon: metadata.isComingSoon, + isHidden: metadata.isHidden, +}); /** - * The full integrations catalog the index renders: design-system toolkits - * (enriched with a `docsLink` from their data file when the catalog entry - * lacks one, so the card's slug matches the generated page) plus docs-local - * partner toolkits. + * The integrations index: every toolkit the public catalog publishes, plus + * docs-local partner toolkits. + * + * `generated` is the nightly output of toolkit-docs-generator, which reads the + * same public catalog arcade.dev/tools is built from, so both list the same + * toolkits with the same badges. */ +export const toIntegrationCatalog = ( + generated: Iterable, + partners: readonly ToolkitWithDocsLink[] +): ToolkitWithDocsLink[] => [ + ...[...generated] + .filter((toolkit) => !INTERNAL_TOOLKIT_IDS.has(toolkit.id)) + .map(toCatalogToolkit), + ...partners, +]; + export const getToolkitsWithDocsLinks = async (): Promise< ToolkitWithDocsLink[] > => { - const docsLinkById = new Map(); - - await Promise.all( - TOOLKITS.map(async (toolkit) => { - const existing = getToolkitDocsLink(toolkit); - if (existing) { - return; - } - - const data = await readToolkitData(toolkit.id); - if (data?.metadata?.docsLink) { - docsLinkById.set( - normalizeToolkitId(toolkit.id), - data.metadata.docsLink - ); - } - }) - ); - - const dsToolkits: ToolkitWithDocsLink[] = TOOLKITS.map((dsToolkit) => { - const toolkit = clearLaunchedComingSoon(dsToolkit); - const existing = getToolkitDocsLink(toolkit); - const docsLink = - existing ?? docsLinkById.get(normalizeToolkitId(toolkit.id)); - - return docsLink ? { ...toolkit, docsLink } : toolkit; - }); - - return [...dsToolkits, ...PARTNER_TOOLKITS]; + const { byNormalizedId } = await loadAllToolkitData(resolveToolkitDataDir()); + return toIntegrationCatalog(byNormalizedId.values(), PARTNER_TOOLKITS); }; diff --git a/tests/integration-catalog.test.ts b/tests/integration-catalog.test.ts new file mode 100644 index 000000000..b19ee24b5 --- /dev/null +++ b/tests/integration-catalog.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, test } from "vitest"; +import type { ToolkitData } from "@/app/_components/toolkit-docs/types"; +import type { PartnerToolkit } from "@/app/_data/partner-toolkits"; +import { toIntegrationCatalog } from "@/app/_lib/integration-catalog"; + +const INTEGRATIONS = "https://docs.arcade.dev/en/resources/integrations"; + +// Minimal generated toolkit file. toIntegrationCatalog only reads `id`, +// `label`, and `metadata`. +const makeGenerated = ( + id: string, + metadata: Partial = {} +): ToolkitData => + ({ + id, + label: `${id} Label`, + metadata: { + category: "social", + iconUrl: `https://design-system.arcade.dev/icons/${id.toLowerCase()}.svg`, + isBYOC: false, + isPro: false, + type: "arcade", + docsLink: `${INTEGRATIONS}/social/${id.toLowerCase()}`, + isComingSoon: false, + isHidden: false, + ...metadata, + }, + }) as unknown as ToolkitData; + +const partner = { + id: "Partner", + label: "Partner", + category: "search", + publicIconUrl: "/images/partners/partner.svg", + isBYOC: true, + isPro: false, + isPartner: true, + type: "verified", + mcpUrl: "https://mcp.example.com/mcp", + docsLink: `${INTEGRATIONS}/search/partner`, + relativeDocsLink: "/en/resources/integrations/search/partner", + isComingSoon: false, + isHidden: false, +} satisfies PartnerToolkit; + +describe("toIntegrationCatalog", () => { + test("lists a published toolkit with the card fields from its generated file", () => { + const [toolkit] = toIntegrationCatalog( + [makeGenerated("Twilio", { isBYOC: true, type: "community" })], + [] + ); + + expect(toolkit).toEqual({ + id: "Twilio", + label: "Twilio Label", + isBYOC: true, + isPro: false, + publicIconUrl: "https://design-system.arcade.dev/icons/twilio.svg", + docsLink: `${INTEGRATIONS}/social/twilio`, + relativeDocsLink: "/en/resources/integrations/social/twilio", + type: "community", + category: "social", + isComingSoon: false, + isHidden: false, + }); + }); + + test("lists only toolkits that have a generated file", () => { + const ids = toIntegrationCatalog( + [makeGenerated("Slack"), makeGenerated("SlackApi")], + [] + ).map((toolkit) => toolkit.id); + + expect(ids).toEqual(["Slack", "SlackApi"]); + }); + + test("leaves out the platform's own toolkits", () => { + const ids = toIntegrationCatalog( + [ + makeGenerated("Math"), + makeGenerated("ArcadeEngineApi"), + makeGenerated("Slack"), + ], + [] + ).map((toolkit) => toolkit.id); + + expect(ids).toEqual(["Slack"]); + }); + + test("appends partner toolkits after the published ones", () => { + const ids = toIntegrationCatalog([makeGenerated("Slack")], [partner]).map( + (toolkit) => toolkit.id + ); + + expect(ids).toEqual(["Slack", "Partner"]); + }); +}); From 62a75a5d736ccb871c7adfa032d4ed50330a9f36 Mon Sep 17 00:00:00 2001 From: Sam Kujovich Date: Mon, 19 Oct 2026 13:44:32 -0700 Subject: [PATCH 2/6] test(integrations): use a verified fixture in the catalog test --- tests/integration-catalog.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration-catalog.test.ts b/tests/integration-catalog.test.ts index b19ee24b5..62c8a0142 100644 --- a/tests/integration-catalog.test.ts +++ b/tests/integration-catalog.test.ts @@ -46,7 +46,7 @@ const partner = { describe("toIntegrationCatalog", () => { test("lists a published toolkit with the card fields from its generated file", () => { const [toolkit] = toIntegrationCatalog( - [makeGenerated("Twilio", { isBYOC: true, type: "community" })], + [makeGenerated("Twilio", { isBYOC: true, type: "verified" })], [] ); @@ -58,7 +58,7 @@ describe("toIntegrationCatalog", () => { publicIconUrl: "https://design-system.arcade.dev/icons/twilio.svg", docsLink: `${INTEGRATIONS}/social/twilio`, relativeDocsLink: "/en/resources/integrations/social/twilio", - type: "community", + type: "verified", category: "social", isComingSoon: false, isHidden: false, From acbb4adf463b4946fd4033b753c499392f606b17 Mon Sep 17 00:00:00 2001 From: Sam Kujovich Date: Tue, 20 Oct 2026 14:31:31 -0700 Subject: [PATCH 3/6] fix(integrations): drive the sidebar from the catalog data and hide Math there The sidebar took toolkit labels, the hidden flag and a fallback category from design-system. It now reads them from the generated toolkit files, the same source as the index, so the two cannot disagree. Math is hidden in the toolkit metadata (ArcadeAI/monorepo#5848), so the index no longer needs its own list of toolkits to leave out. math.json carries the flag now, which the nightly writes once the catalog serves it. Part of GRO-615 --- .../integrations/development/meta.json | 1 - lib/integration-catalog.ts | 14 +-- tests/integration-catalog.test.ts | 13 --- .../data/toolkits/math.json | 2 +- .../scripts/sync-toolkit-sidebar.ts | 90 +++---------------- .../scripts/sync-toolkit-sidebar.test.ts | 84 +++++------------ 6 files changed, 39 insertions(+), 165 deletions(-) diff --git a/content/docs/resources/integrations/development/meta.json b/content/docs/resources/integrations/development/meta.json index 72efb9772..8f02bf2fc 100644 --- a/content/docs/resources/integrations/development/meta.json +++ b/content/docs/resources/integrations/development/meta.json @@ -9,7 +9,6 @@ "[Firecrawl](/en/resources/integrations/development/firecrawl)", "[Fly.io](/en/resources/integrations/development/fly-io)", "[GitHub](/en/resources/integrations/development/github)", - "[Math](/en/resources/integrations/development/math)", "[Mixpanel](/en/resources/integrations/development/mixpanel)", "[PagerDuty](/en/resources/integrations/development/pagerduty)", "[PostHog](/en/resources/integrations/development/posthog)", diff --git a/lib/integration-catalog.ts b/lib/integration-catalog.ts index 18ca3b56b..233f8698d 100644 --- a/lib/integration-catalog.ts +++ b/lib/integration-catalog.ts @@ -4,16 +4,6 @@ import { resolveToolkitDataDir } from "@/toolkit-docs-generator/src/shared/toolk import { loadAllToolkitData } from "./toolkit-data"; import type { ToolkitWithDocsLink } from "./toolkit-slug"; -/** - * Engine plumbing the public catalog lists like any other toolkit. Nothing in - * the catalog separates these from a product, so they are named here. Keep in - * step with arcade.dev/tools, which hides the same set. - */ -const INTERNAL_TOOLKIT_IDS: ReadonlySet = new Set([ - "Math", - "ArcadeEngineApi", -]); - const toCatalogToolkit = ({ id, label, @@ -44,9 +34,7 @@ export const toIntegrationCatalog = ( generated: Iterable, partners: readonly ToolkitWithDocsLink[] ): ToolkitWithDocsLink[] => [ - ...[...generated] - .filter((toolkit) => !INTERNAL_TOOLKIT_IDS.has(toolkit.id)) - .map(toCatalogToolkit), + ...[...generated].map(toCatalogToolkit), ...partners, ]; diff --git a/tests/integration-catalog.test.ts b/tests/integration-catalog.test.ts index 20cd2ca02..a5ffd35ef 100644 --- a/tests/integration-catalog.test.ts +++ b/tests/integration-catalog.test.ts @@ -74,19 +74,6 @@ describe("toIntegrationCatalog", () => { expect(ids).toEqual(["Slack", "SlackApi"]); }); - test("leaves out the platform's own toolkits", () => { - const ids = toIntegrationCatalog( - [ - makeGenerated("Math"), - makeGenerated("ArcadeEngineApi"), - makeGenerated("Slack"), - ], - [] - ).map((toolkit) => toolkit.id); - - expect(ids).toEqual(["Slack"]); - }); - test("appends partner toolkits after the published ones", () => { const ids = toIntegrationCatalog([makeGenerated("Slack")], [partner]).map( (toolkit) => toolkit.id diff --git a/toolkit-docs-generator/data/toolkits/math.json b/toolkit-docs-generator/data/toolkits/math.json index 8187d4ace..2badbc44d 100644 --- a/toolkit-docs-generator/data/toolkits/math.json +++ b/toolkit-docs-generator/data/toolkits/math.json @@ -11,7 +11,7 @@ "type": "arcade", "docsLink": "https://docs.arcade.dev/en/resources/integrations/development/math", "isComingSoon": false, - "isHidden": false + "isHidden": true }, "auth": null, "tools": [ diff --git a/toolkit-docs-generator/scripts/sync-toolkit-sidebar.ts b/toolkit-docs-generator/scripts/sync-toolkit-sidebar.ts index 079396069..0f93d0a23 100644 --- a/toolkit-docs-generator/scripts/sync-toolkit-sidebar.ts +++ b/toolkit-docs-generator/scripts/sync-toolkit-sidebar.ts @@ -31,7 +31,6 @@ import { } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { TOOLKITS as DESIGN_SYSTEM_TOOLKITS } from "@arcadeai/design-system/metadata/toolkits"; import { PARTNER_TOOLKITS, type PartnerToolkit, @@ -50,20 +49,6 @@ import type { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -// Import design system toolkits -type DesignSystemToolkit = { - id: string; - label: string; - category?: string; - isHidden?: boolean; -}; -let TOOLKITS: DesignSystemToolkit[] = - DESIGN_SYSTEM_TOOLKITS as DesignSystemToolkit[]; - -export function setToolkitsForTesting(toolkits: DesignSystemToolkit[]): void { - TOOLKITS = toolkits; -} - // Get project root (two levels up from toolkit-docs-generator/scripts) const PROJECT_ROOT = resolve(__dirname, "..", ".."); @@ -187,33 +172,9 @@ export function getToolkitFiles(dataDir: string): string[] { } /** - * Get category for a toolkit ID from design system - */ -export function getToolkitCategory(toolkitId: string): string | null { - const toolkit = TOOLKITS.find( - (t) => t.id.toLowerCase() === toolkitId.toLowerCase() - ); - - if (toolkit && !toolkit.isHidden) { - return toolkit.category || null; - } - - return null; -} - -/** - * Get label for a toolkit ID from design system + * A readable label from a toolkit ID, for a toolkit file with no label. */ export function getToolkitLabel(toolkitId: string): string { - const toolkit = TOOLKITS.find( - (t) => t.id.toLowerCase() === toolkitId.toLowerCase() - ); - - if (toolkit) { - return toolkit.label; - } - - // Fallback: Convert ID to readable label return toolkitId .replace(CAPITAL_LETTER_REGEX, " $1") .replace(FIRST_CHARACTER_REGEX, (str) => str.toUpperCase()) @@ -269,35 +230,21 @@ export function inferNavGroup( } /** - * Build toolkit info from available JSON files + * Build a sidebar entry from a toolkit's generated JSON file, or `null` when + * the toolkit is hidden or has no category. */ -type ToolkitInfoEntry = { - info: ToolkitInfo; - priority: number; -}; - -function resolveToolkitInfo( - dataDir: string, - slug: string -): ToolkitInfoEntry | null { +function resolveToolkitInfo(dataDir: string, slug: string): ToolkitInfo | null { const jsonData = readToolkitJson(dataDir, slug); const toolkitId = jsonData?.id ?? slug; const docsSlug = getToolkitSlug({ docsLink: jsonData?.metadata?.docsLink ?? null, id: toolkitId, }); - const designSystemToolkit = TOOLKITS.find( - (t) => t.id.toLowerCase() === toolkitId.toLowerCase() - ); - const isHidden = jsonData?.metadata?.isHidden ?? false; - if (designSystemToolkit?.isHidden || isHidden) { + if (jsonData?.metadata?.isHidden) { return null; } - // Keep sidebar routes aligned with static params: toolkit JSON is source of - // truth for category, with design system as fallback when JSON is missing. - const category = - jsonData?.metadata?.category ?? designSystemToolkit?.category; + const category = jsonData?.metadata?.category; if (!category) { return null; } @@ -306,41 +253,30 @@ function resolveToolkitInfo( `Unrecognized integration category "${category}" for toolkit "${toolkitId}".` ); } - const labelFromDesignSystem = designSystemToolkit?.label ?? null; const labelFromJson = jsonData?.label ?? jsonData?.name ?? null; const typeFromJson = jsonData?.metadata?.type ?? null; - const info: ToolkitInfo = { + return { category, id: toolkitId, - // Prefer the Design System label (has correct spacing/casing), but fall back - // to JSON label for toolkits not present in the design system. - label: labelFromDesignSystem ?? labelFromJson ?? getToolkitLabel(toolkitId), + label: labelFromJson ?? getToolkitLabel(toolkitId), navGroup: inferNavGroup(toolkitId, typeFromJson), slug: docsSlug, }; - - const priority = designSystemToolkit ? 2 : 0; - return { info, priority }; } export function buildToolkitInfoList(dataDir: string): ToolkitInfo[] { const files = getToolkitFiles(dataDir); - const toolkitsBySlug = new Map(); + const toolkitsBySlug = new Map(); for (const slug of files) { - const entry = resolveToolkitInfo(dataDir, slug); - if (!entry) { - continue; - } - - const existing = toolkitsBySlug.get(entry.info.slug); - if (!existing || entry.priority > existing.priority) { - toolkitsBySlug.set(entry.info.slug, entry); + const info = resolveToolkitInfo(dataDir, slug); + if (info && !toolkitsBySlug.has(info.slug)) { + toolkitsBySlug.set(info.slug, info); } } - return Array.from(toolkitsBySlug.values()).map((entry) => entry.info); + return Array.from(toolkitsBySlug.values()); } /** diff --git a/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts b/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts index e28bc3847..ea48d0e44 100644 --- a/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts +++ b/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts @@ -16,7 +16,6 @@ import { type CategoryMeta, findMissingMainMetaCategories, generateCategoryMeta, - getToolkitCategory, getToolkitFiles, getToolkitLabel, getToolkitLabelFromJson, @@ -25,31 +24,11 @@ import { type PartnerNavSource, parseBooleanCliFlag, resolveRemoveEmptySections, - setToolkitsForTesting, syncToolkitSidebar, type ToolkitInfo, } from "../../scripts/sync-toolkit-sidebar"; import { INTEGRATION_CATEGORIES } from "../../src/shared/toolkit-primitives"; -setToolkitsForTesting([ - { id: "Gmail", label: "Gmail", category: "productivity" }, - { id: "Slack", label: "Slack", category: "social" }, - { id: "Github", label: "GitHub", category: "development" }, - { id: "Stripe", label: "Stripe", category: "payments" }, - { id: "Zendesk", label: "Zendesk", category: "customer-support" }, - { id: "GoogleSearch", label: "Google Search", category: "search" }, - { id: "Hubspot", label: "HubSpot", category: "sales" }, - { id: "Spotify", label: "Spotify", category: "entertainment" }, - { id: "Postgres", label: "Postgres", category: "databases" }, - { id: "WeaviateApi", label: "Weaviate API", category: "development" }, - { - id: "HiddenToolkit", - label: "Hidden", - category: "productivity", - isHidden: true, - }, -]); - // Test directory setup const TEST_DIR = join(process.cwd(), ".test-sync-sidebar"); const TEST_DATA_DIR = join(TEST_DIR, "data/toolkits"); @@ -114,41 +93,11 @@ describe("getToolkitFiles", () => { }); }); -// ============================================================================ -// Unit Tests: getToolkitCategory -// ============================================================================ - -describe("getToolkitCategory", () => { - it("should return category for known toolkit", () => { - expect(getToolkitCategory("Gmail")).toBe("productivity"); - expect(getToolkitCategory("Slack")).toBe("social"); - }); - - it("should return null for unknown toolkit", () => { - expect(getToolkitCategory("Unknown")).toBeNull(); - }); - - it("should return null for hidden toolkit", () => { - expect(getToolkitCategory("HiddenToolkit")).toBeNull(); - }); -}); - // ============================================================================ // Unit Tests: getToolkitLabel // ============================================================================ describe("getToolkitLabel", () => { - it("should return label for known toolkit", () => { - expect(getToolkitLabel("Gmail")).toBe("Gmail"); - expect(getToolkitLabel("Github")).toBe("GitHub"); - expect(getToolkitLabel("GoogleSearch")).toBe("Google Search"); - }); - - it("should return label for known toolkit regardless of case", () => { - expect(getToolkitLabel("gmail")).toBe("Gmail"); - expect(getToolkitLabel("GITHUB")).toBe("GitHub"); - }); - it("should generate fallback label for unknown toolkit", () => { expect(getToolkitLabel("MyCustomToolkit")).toBe("My Custom Toolkit"); expect(getToolkitLabel("APIHelper")).toBe("A P I Helper"); @@ -234,8 +183,14 @@ describe("buildToolkitInfoList", () => { }); it("should build list of toolkits with correct labels and categories", () => { - createToolkitJson("gmail", { label: "Gmail" }); - createToolkitJson("slack", { label: "Slack" }); + createToolkitJson("gmail", { + label: "Gmail", + metadata: { category: "productivity" }, + }); + createToolkitJson("slack", { + label: "Slack", + metadata: { category: "social" }, + }); createToolkitJson("unknowntoolkit", { label: "Unknown Toolkit" }); const result = buildToolkitInfoList(TEST_DATA_DIR); @@ -258,27 +213,36 @@ describe("buildToolkitInfoList", () => { }); it("should skip hidden toolkits", () => { - createToolkitJson("HiddenToolkit", { label: "Hidden" }); + createToolkitJson("HiddenToolkit", { + label: "Hidden", + metadata: { category: "productivity", isHidden: true }, + }); const result = buildToolkitInfoList(TEST_DATA_DIR); expect(result).toHaveLength(0); }); - it("should prefer design system label over JSON", () => { - createToolkitJson("gmail", { label: "Custom Gmail Label" }); + it("uses the label from the toolkit file", () => { + createToolkitJson("gmail", { + label: "Custom Gmail Label", + metadata: { category: "productivity" }, + }); const result = buildToolkitInfoList(TEST_DATA_DIR); - expect(result[0].label).toBe("Gmail"); + expect(result[0].label).toBe("Custom Gmail Label"); }); - it("should use design system label as fallback", () => { - createToolkitJson("gmail", { label: null }); + it("falls back to a readable name when the file has no label", () => { + createToolkitJson("GoogleSearch", { + label: null, + metadata: { category: "search" }, + }); const result = buildToolkitInfoList(TEST_DATA_DIR); - expect(result[0].label).toBe("Gmail"); + expect(result[0].label).toBe("Google Search"); }); it("should use docsLink slug when available", () => { From 5f2d7476518590f74f3034640c9b82d27ebd2701 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:32:15 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=A4=96=20Regenerate=20LLMs.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/llms.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index 39a2302d1..929cf7844 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -278,7 +278,6 @@ Arcade docs serve two audiences. Start with the path that matches your goal: - [LinkedIn](https://docs.arcade.dev/en/resources/integrations/social/linkedin): Arcade.dev provides a toolkit for integrating with LinkedIn, enabling developers to streamline interactions with the platform's API. This toolkit allows for the creation of content directly on LinkedIn, enhancing user engagement and social sharing capabilities. Capabilities -… - [Luma API](https://docs.arcade.dev/en/resources/integrations/productivity/luma-api): LumaApi provides tools that facilitate LLMs in interacting seamlessly with the Luma API to manage events and user memberships. This toolkit allows developers to efficiently handle event creation, guest management, and membership features. Capabilities - Create, update, and… - [Mailchimp API](https://docs.arcade.dev/en/resources/integrations/productivity/mailchimp-marketing-api): The MailchimpMarketingApi toolkit enables seamless integration with the Mailchimp Marketing API, allowing developers to manage marketing efforts effectively. It empowers users to execute various operations related to audience management, email campaigns, and e-commerce… -- [Math](https://docs.arcade.dev/en/resources/integrations/development/math): The Arcade Math toolkit provides LLM-callable tools for performing mathematical operations, from basic arithmetic to statistical and number-theory functions. Capabilities - Arithmetic & rounding: Addition, subtraction, multiplication, division, modulus, absolute value, ceiling,… - [Microsoft Excel](https://docs.arcade.dev/en/resources/integrations/productivity/microsoft-excel): Microsoft Excel Toolkit Arcade's Microsoft Excel toolkit connects LLMs to Excel workbooks stored in OneDrive for Business, enabling agents to read, write, analyze, and manage spreadsheets through the Microsoft Graph API. Capabilities - Workbook discovery & metadata: Search… - [Microsoft OneDrive](https://docs.arcade.dev/en/resources/integrations/productivity/microsoft-onedrive): Microsoft OneDrive Toolkit Arcade toolkit for Microsoft OneDrive that enables LLM agents to manage files, folders, permissions, and sharing via the Microsoft Graph API. Capabilities - Drive & item inspection: Retrieve drive metadata, resolve item metadata by ID, list folder… - [Microsoft Outlook Calendar](https://docs.arcade.dev/en/resources/integrations/productivity/microsoft-outlook-calendar): Microsoft Outlook Calendar Toolkit The Microsoft Outlook Calendar toolkit connects Arcade to Outlook Calendar via Microsoft Graph, enabling LLMs to read, create, update, and manage calendar events and scheduling on behalf of a user. Capabilities - Event read & search: Fetch a… From fd685ae74e9083b1f52cc63c7b72dce883eb7655 Mon Sep 17 00:00:00 2001 From: Sam Kujovich Date: Tue, 20 Oct 2026 15:42:38 -0700 Subject: [PATCH 5/6] docs(crewai): suggest real MCP servers instead of Math in next steps The CrewAI guide pointed readers at a "Math" toolkit, which the catalog now hides, and at "Search", which is not a current toolkit name. Part of GRO-615 --- .../get-started/agent-frameworks/crewai/use-arcade-tools.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/get-started/agent-frameworks/crewai/use-arcade-tools.mdx b/content/docs/get-started/agent-frameworks/crewai/use-arcade-tools.mdx index d79ec0d05..9106bc1ba 100644 --- a/content/docs/get-started/agent-frameworks/crewai/use-arcade-tools.mdx +++ b/content/docs/get-started/agent-frameworks/crewai/use-arcade-tools.mdx @@ -356,7 +356,7 @@ You should see the agent responding to your prompts like any model, as well as h Now that you have integrated Arcade tools into your CrewAI agent team, you can: -- Experiment with different toolkits, such as "Math" or "Search." +- Add other MCP servers to `MCP_SERVERS`, such as `"GoogleCalendar"` or `"Linear"`. - Customize the agent's prompts for specific tasks. - Customize the tool authorization and execution flow to meet your application's requirements. From 7b1d264395c93f131da94f92b879798daf9e8ad2 Mon Sep 17 00:00:00 2001 From: Sam Kujovich Date: Tue, 20 Oct 2026 15:50:09 -0700 Subject: [PATCH 6/6] test(sidebar): name fixture files the way the generator does getToolkitFiles lowercases file names before reading them, so a fixture saved as GoogleSearch.json was never found on a case-sensitive file system: the fallback-label test failed on Linux CI, and the hidden test passed only because its file went missing. Fixtures now use lowercase file names with the toolkit ID inside, and the hidden test keeps a visible sibling so it fails if the flag stops working. Part of GRO-615 --- .../tests/scripts/sync-toolkit-sidebar.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts b/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts index ea48d0e44..de273632f 100644 --- a/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts +++ b/toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts @@ -213,14 +213,19 @@ describe("buildToolkitInfoList", () => { }); it("should skip hidden toolkits", () => { - createToolkitJson("HiddenToolkit", { + createToolkitJson("hiddentoolkit", { + id: "HiddenToolkit", label: "Hidden", metadata: { category: "productivity", isHidden: true }, }); + createToolkitJson("gmail", { + label: "Gmail", + metadata: { category: "productivity" }, + }); const result = buildToolkitInfoList(TEST_DATA_DIR); - expect(result).toHaveLength(0); + expect(result.map((item) => item.label)).toEqual(["Gmail"]); }); it("uses the label from the toolkit file", () => { @@ -235,7 +240,8 @@ describe("buildToolkitInfoList", () => { }); it("falls back to a readable name when the file has no label", () => { - createToolkitJson("GoogleSearch", { + createToolkitJson("googlesearch", { + id: "GoogleSearch", label: null, metadata: { category: "search" }, });