diff --git a/CONTEXT.md b/CONTEXT.md index 4eec7faa8..7048d8e6f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -15,3 +15,29 @@ _Avoid_: Session middleware, web adapter **SDK**: The language client plus any first-party framework helper that ships in that client. _Avoid_: Adapter (for web-framework helpers) + +### Keep building + +**First success**: +The reader has finished the product hello-world: one AgentKit tool call, or one SaaSKit sign-in. +_Avoid_: Go-live, production, first-run + +**Keep building**: +The stage after first success. It holds how-tos and recipes only. The tab opens a hub, not a how-to or a recipe. +_Avoid_: Guides, leftover, Developer Resources, second product journey + +**Keep building hub**: +A short page that names the next how-to, then lists recipes as a pick-list. It is a recommended next plus a shelf, not a 50/50 fork. +_Avoid_: Journey, index, leftover + +**How-to**: +A short page for one Scalekit dashboard or workspace task after first success. How-tos are read in order. The order follows the dashboard after first success, not a docs brainstorm. Account deletion is not a how-to. +_Avoid_: Recipe, product-journey page, leftover + +**Recipe**: +A page for one job in the reader's own app or agent. The reader can land on it, finish the job, and leave. Recipes have no required order. +_Avoid_: Cookbook, how-to, feature tour, quickstart + +**Product journey**: +The main AgentKit or SaaSKit left rail from hello-world through implementation. +_Avoid_: Keep building, how-to sequence diff --git a/astro.config.mjs b/astro.config.mjs index 4514e498b..e5856499c 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -13,7 +13,6 @@ import starlightThemeNova from 'starlight-theme-nova' import starlightVideos from 'starlight-videos' import starlightLinksValidator from 'starlight-links-validator' import starlightLlmsTxt from 'starlight-llms-txt' -import starlightBlog from 'starlight-blog' import { sidebar as sidebarConfig, topics, exclude } from './src/configs/sidebar.config' import { redirects } from './src/configs/redirects.config' import { llmsConfig } from './src/configs/llms.config.ts' @@ -26,6 +25,7 @@ import Icons from 'unplugin-icons/vite' import netlify from '@astrojs/netlify' import openapiToMarkdown from './src/integrations/openapi-markdown' import { injectAgentHeader } from './src/integrations/inject-agent-header.ts' +import assignHowToTopic from './src/integrations/assign-how-to-topic' // https://astro.build/config export default defineConfig({ @@ -104,6 +104,8 @@ export default defineConfig({ starlightImageZoom({ showCaptions: true, }), + // Shared /how-to/** pages have no product folder; assign a topic so Starlight can pick a Cookbooks rail. + assignHowToTopic(), starlightSidebarTopics(sidebarConfig, { topics, exclude }), starlightDocSearch({ appId: '7554BDRAJD', @@ -149,14 +151,6 @@ export default defineConfig({ }, // No baseUrl — prevents llms.txt generation (already handled by starlight-llms-txt) }), - starlightBlog({ - prefix: 'cookbooks', - rss: false, - metrics: { - readingTime: true, - words: 'total', - }, - }), ], head: [ { @@ -447,8 +441,7 @@ export default defineConfig({ }, }, optimizeDeps: { - // starlight-blog uses Astro/Starlight virtual modules that should not be pre-bundled. - exclude: ['starlight-blog'], + include: ['vue'], }, // Provide a safe fallback for libraries that reference the CommonJS // global `__dirname` (e.g. canvaskit-wasm used by astro-og-canvas). diff --git a/package.json b/package.json index 3ace416f9..efd680c8c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "canvaskit-wasm": "0.40.0", "jose": "^6.2.2", "sharp": "^0.35.3", - "starlight-blog": "^0.26.1", "starlight-image-zoom": "^0.14.2", "starlight-links-validator": "^0.24.1", "starlight-llms-txt": "^0.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14268ccc2..d875d215e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,9 +83,6 @@ importers: sharp: specifier: ^0.35.3 version: 0.35.3(@types/node@25.6.0) - starlight-blog: - specifier: ^0.26.1 - version: 0.26.1(@astrojs/starlight@0.41.3(@astrojs/markdown-remark@7.2.1)(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3))(typescript@6.0.3))(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3)) starlight-image-zoom: specifier: ^0.14.2 version: 0.14.2(@astrojs/starlight@0.41.3(@astrojs/markdown-remark@7.2.1)(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3))(typescript@6.0.3)) @@ -289,9 +286,6 @@ packages: resolution: {integrity: sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==} engines: {node: '>=22.12.0'} - '@astrojs/rss@4.0.19': - resolution: {integrity: sha512-e+z5wYeYtffQdHQO8c2tkSd2JEBdAuRXJV4ZEU5IxkYeE6e39woDd7nw1PH1Kk2tEYNCYuKdylnnbhGmt61awA==} - '@astrojs/sitemap@3.7.2': resolution: {integrity: sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==} @@ -1510,9 +1504,6 @@ packages: engines: {node: '>=18.14.0'} hasBin: true - '@nodable/entities@1.1.0': - resolution: {integrity: sha512-bidpxmTBP0pOsxULw6XlxzQpTgrAGLDHGBK/JuWhPDL6ZV0GZ/PmN9CA9do6e+A9lYI6qx6ikJUtJYRxup141g==} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2833,10 +2824,6 @@ packages: peerDependencies: astro: ^5.0.0 || ^6.0.0-alpha - astro-remote@0.3.4: - resolution: {integrity: sha512-jL5skNQLA0YBc1R3bVGXyHew3FqGqsT7AgLzWAVeTLzFkwVMUYvs4/lKJSmS7ygcF1GnHnoKG6++8GL9VtWwGQ==} - engines: {node: '>=18.14.1'} - astro-theme-toggle@0.8.1: resolution: {integrity: sha512-cUQDnMpRrGwWQ0C6dj1x3y3e3ULlikvoc6Tb6FkIQkL1Ropyd6jQ/IZ9GdhSogWEVpNodPckOKeItIh89EDwkg==} @@ -3831,13 +3818,6 @@ packages: fast-wrap-ansi@0.1.6: resolution: {integrity: sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==} - fast-xml-builder@1.1.4: - resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} - - fast-xml-parser@5.6.0: - resolution: {integrity: sha512-5G+uaEBbOm9M4dgMOV3K/rBzfUNGqGqoUTaYJM3hBwM8t71w07gxLQZoTsjkY8FtfjabqgQHEkeIySBDYeBmJw==} - hasBin: true - fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -4909,31 +4889,6 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - marked-footnote@1.4.0: - resolution: {integrity: sha512-fZTxAhI1TcLEs5UOjCfYfTHpyKGaWQevbxaGTEA68B51l7i87SctPFtHETYqPkEN0ka5opvy4Dy1l/yXVC+hmg==} - peerDependencies: - marked: '>=7.0.0' - - marked-plaintify@1.1.1: - resolution: {integrity: sha512-r3kMKArhfo2H3lD4ctFq/OJTzM0uNvXHh7FBTI1hMDpf4Ac1djjtq4g8NfTBWMxWLmaEz3KL1jCkLygik3gExA==} - peerDependencies: - marked: '>=13.0.0' - - marked-smartypants@1.1.12: - resolution: {integrity: sha512-Z0QL2GpihbSeG5aaCrQxMEoqvngMftF/gq1SrdlCnbecUSrX3HYgPtCZzCW+OyNe2ideQqaFdxfGryqQX1MBDA==} - peerDependencies: - marked: '>=4 <19' - - marked@12.0.2: - resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==} - engines: {node: '>= 18'} - hasBin: true - - marked@17.0.6: - resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} - engines: {node: '>= 20'} - hasBin: true - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5556,10 +5511,6 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-expression-matcher@1.5.0: - resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} - engines: {node: '>=14.0.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -6303,10 +6254,6 @@ packages: resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} engines: {node: '>=20'} - smartypants@0.2.2: - resolution: {integrity: sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==} - hasBin: true - smol-toml@1.6.1: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} @@ -6364,12 +6311,6 @@ packages: stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - starlight-blog@0.26.1: - resolution: {integrity: sha512-c2aLtkVTNFKHDJrh1DwlxBj+VyOrLqJeHmGkKKHwL6oowUoaqUiC/vyOZLXNmZL1kcYI0hgHDw1t4G/lkg8mZg==} - engines: {node: '>=22.12.0'} - peerDependencies: - '@astrojs/starlight': '>=0.38.0' - starlight-image-zoom@0.14.2: resolution: {integrity: sha512-YBvE724gFMiVjObGtXmfIDi3zAxVBsvGukeRXOiWLNnESFOBZFfO9DpC/reHohrCxFrO+5ot0XR8EimbE0LpsA==} engines: {node: '>=22.12.0'} @@ -6498,9 +6439,6 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strnum@2.2.3: - resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} - stubborn-fs@2.0.0: resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} @@ -7492,12 +7430,6 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/rss@4.0.19': - dependencies: - fast-xml-parser: 5.6.0 - piccolore: 0.1.3 - zod: 4.3.6 - '@astrojs/sitemap@3.7.2': dependencies: sitemap: 9.0.1 @@ -8859,8 +8791,6 @@ snapshots: - rollup - supports-color - '@nodable/entities@1.1.0': {} - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -10224,14 +10154,6 @@ snapshots: deterministic-object-hash: 2.0.2 entities: 7.0.1 - astro-remote@0.3.4: - dependencies: - entities: 4.5.0 - marked: 12.0.2 - marked-footnote: 1.4.0(marked@12.0.2) - marked-smartypants: 1.1.12(marked@12.0.2) - ultrahtml: 1.6.0 - astro-theme-toggle@0.8.1: {} astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3): @@ -11395,17 +11317,6 @@ snapshots: dependencies: fast-string-width: 1.1.0 - fast-xml-builder@1.1.4: - dependencies: - path-expression-matcher: 1.5.0 - - fast-xml-parser@5.6.0: - dependencies: - '@nodable/entities': 1.1.0 - fast-xml-builder: 1.1.4 - path-expression-matcher: 1.5.0 - strnum: 2.2.3 - fastest-levenshtein@1.0.16: {} fastify-plugin@6.0.0: {} @@ -12623,23 +12534,6 @@ snapshots: markdown-table@3.0.4: {} - marked-footnote@1.4.0(marked@12.0.2): - dependencies: - marked: 12.0.2 - - marked-plaintify@1.1.1(marked@17.0.6): - dependencies: - marked: 17.0.6 - - marked-smartypants@1.1.12(marked@12.0.2): - dependencies: - marked: 12.0.2 - smartypants: 0.2.2 - - marked@12.0.2: {} - - marked@17.0.6: {} - math-intrinsics@1.1.0: {} maxstache-stream@1.0.4: @@ -13687,8 +13581,6 @@ snapshots: path-exists@5.0.0: {} - path-expression-matcher@1.5.0: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -14664,8 +14556,6 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - smartypants@0.2.2: {} - smol-toml@1.6.1: {} sonic-boom@4.2.1: @@ -14715,27 +14605,6 @@ snapshots: stackframe@1.3.4: {} - starlight-blog@0.26.1(@astrojs/starlight@0.41.3(@astrojs/markdown-remark@7.2.1)(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3))(typescript@6.0.3))(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3)): - dependencies: - '@astrojs/markdown-remark': 7.2.1 - '@astrojs/mdx': 5.0.4(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3)) - '@astrojs/rss': 4.0.19 - '@astrojs/starlight': 0.41.3(@astrojs/markdown-remark@7.2.1)(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3))(typescript@6.0.3) - astro-remote: 0.3.4 - github-slugger: 2.0.0 - hast-util-from-html: 2.0.3 - hast-util-to-html: 9.0.5 - hast-util-to-string: 3.0.1 - marked: 17.0.6 - marked-plaintify: 1.1.1(marked@17.0.6) - mdast-util-mdx-expression: 2.0.1 - unist-util-is: 6.0.1 - unist-util-remove: 4.0.0 - unist-util-visit: 5.1.0 - transitivePeerDependencies: - - astro - - supports-color - starlight-image-zoom@0.14.2(@astrojs/starlight@0.41.3(@astrojs/markdown-remark@7.2.1)(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3))(typescript@6.0.3)): dependencies: '@astrojs/starlight': 0.41.3(@astrojs/markdown-remark@7.2.1)(astro@7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@netlify/blobs@10.7.9)(@types/node@25.6.0)(jiti@2.7.0)(rollup@4.60.1)(terser@5.46.1)(yaml@2.8.3))(typescript@6.0.3) @@ -14923,8 +14792,6 @@ snapshots: strip-json-comments@2.0.1: {} - strnum@2.2.3: {} - stubborn-fs@2.0.0: dependencies: stubborn-utils: 1.0.2 diff --git a/project-docs/COOKBOOKS.md b/project-docs/COOKBOOKS.md index f0c7a2fe0..4caf676b2 100644 --- a/project-docs/COOKBOOKS.md +++ b/project-docs/COOKBOOKS.md @@ -1,10 +1,41 @@ # Cookbook authoring guide -Cookbooks live at `src/content/docs/cookbooks/` and are published at `/cookbooks/`. They are powered by the `starlight-blog` plugin (configured with `prefix: 'cookbooks'` in `astro.config.mjs`). +Cookbooks are plain Starlight docs pages under product shelves: -This document covers how to author, review, and validate a cookbook entry. +| Product | Path | URL | +| ------------- | ------------------------------------ | --------------------------- | +| AgentKit | `src/content/docs/agentkit/recipes/` | `/agentkit/recipes//` | +| Auth for SaaS | `src/content/docs/saaskit/recipes/` | `/saaskit/recipes//` | ---- +How-to guides (short dashboard answers) live beside them: + +| Scope | Path | URL | +| --------------------------- | ----------------------------------- | -------------------------- | +| Shared (both product rails) | `src/content/docs/how-to/` | `/how-to//` | +| AgentKit only | `src/content/docs/agentkit/how-to/` | `/agentkit/how-to//` | + +Shared how-tos appear in both Keep building rails from one file. Product chrome on `/how-to/**` uses the same session/cookie as Enterprise Deployment. + +Each product shelf has a Keep building hub at `index.mdx`. The secondary nav lands on that hub. Hide the hub from the Recipes autogenerate list (`sidebar.hidden: true`). + +## How readers find them + +Each product has a **dedicated Keep building sidebar** (not the journey rail): + +- **AgentKit** secondary nav → **Keep building** → left rail shows **How-to** then **Recipes** +- **Auth for SaaS** secondary nav → **Keep building** → same pattern (not under Developer Resources) + +New topic IDs in `src/configs/sidebar.config.ts`: + +- `agentkit-guides` +- `saaskit-guides` + +Each uses `autogenerate` on the product directory. Drop a new `.mdx` file in the folder and it appears in the collapsible. + +A cross-product page remains at `/cookbooks/` (`src/content/docs/cookbooks.mdx`). It belongs to no +single product, so it is listed in `exclude` in `src/configs/sidebar.config.ts` — that keeps it from +inheriting a product journey rail or lighting up a product nav pill. Each product Keep building +tab opens `//recipes/`, not the first recipe. Old `//cookbooks/` URLs redirect there. ## What a cookbook is @@ -12,18 +43,19 @@ A cookbook is a practical, developer-focused guide that solves one specific real The best cookbooks share knowledge, not features. They are useful even for developers who have not yet adopted Scalekit. Each cookbook should be independently useful — a developer should be able to land on one recipe, solve their problem, and continue without reading anything else. -**Cookbooks are not:** - -- "How Scalekit works" explanations (those belong in concept pages) -- Step-by-step quickstarts for first-time setup (those belong in guides) -- API reference documentation - **Cookbooks are:** - Recipes for solving a specific implementation problem - Pattern guides developers can adapt to their own projects - Deep dives into real tradeoffs, gotchas, and working code +**Cookbooks are not:** + +- "How Scalekit works" explanations (concept pages) +- First-time setup quickstarts (journey guides) +- API reference documentation +- Short dashboard answers (those belong in **How-to**) + ### Two layers of content Every cookbook belongs to one of two layers: @@ -34,8 +66,6 @@ Every cookbook belongs to one of two layers: Most cookbooks are Layer 2. A cookbook may contain both layers — a brief orientation section followed by one or more recipes. ---- - ## The P.A.T. framework Every cookbook must be structured around three layers: @@ -46,15 +76,7 @@ Every cookbook must be structured around three layers: Apply P.A.T. to the cookbook as a whole and to each major section. ---- - -## File structure - -Create a new `.mdx` file in `src/content/docs/cookbooks/`: - -``` -src/content/docs/cookbooks/.mdx -``` +## Naming Filename rules: @@ -75,371 +97,74 @@ Name the cookbook like a concrete developer task. Prefer titles that expose the Prefer title patterns: "How to…", "Build…", "Handle…", "Debug…", "Pass…", "Validate…", "Set up…" ---- +## Frontmatter -## Required frontmatter - -The frontmatter schema is enforced by `starlight-blog`. Use this exact structure — field names and types must match. +Use normal Starlight docs frontmatter. Blog-only fields (`date`, `excerpt`, `featured`, `authors`, `tags`, `cover`) are not used. ```yaml --- -title: 'Verb-first title describing what the developer builds (≤60 chars)' -description: 'One sentence: the problem solved and the outcome (≤160 chars)' -date: YYYY-MM-DD -tags: ['tag-one', 'tag-two'] -excerpt: > - 2–3 sentence teaser that states the core problem, the approach, and why it matters. - This appears on the cookbooks index page. -featured: false -# Optional: include a cover image for featured cookbooks -cover: - alt: 'Descriptive alt text for the cover image' - image: ../../../../assets/blog/covers/.jpg -authors: - - name: 'Author Name' - title: 'Role' - url: 'https://linkedin.com/in/...' - picture: '/images/blog/authors/.jpg' +title: 'Build a Mastra agent with Scalekit AgentKit tools' +description: 'Give a Mastra agent access to Gmail and 200+ connectors through Scalekit AgentKit.' +sidebar: + label: 'Build a Mastra agent' + order: 3 +tableOfContents: true --- ``` **Field rules:** -| Field | Required | Notes | -| ------------------- | -------- | ------------------------------------------------------------------------------- | -| `title` | Yes | ≤60 chars, verb-first, sentence case | -| `description` | Yes | ≤160 chars, states problem and outcome | -| `date` | Yes | `YYYY-MM-DD` format | -| `tags` | Yes | Array of strings; use vocabulary below | -| `excerpt` | Yes | 2–3 sentences; appears on `/cookbooks/` index | -| `featured` | Yes | `true` or `false`; featured cookbooks appear prominently | -| `cover` | No | Include only when `featured: true`; image path is relative to the cookbook file | -| `authors` | Yes | Array; at least one author required | -| `authors[].name` | Yes | Full name | -| `authors[].title` | Yes | Role or job title | -| `authors[].url` | Yes | LinkedIn or personal URL | -| `authors[].picture` | Yes | Path under `/images/blog/authors/`; file must exist in `public/` | - -**Cover image path:** relative to the `.mdx` file. For `src/content/docs/cookbooks/.mdx`, the path to `src/assets/blog/covers/.jpg` is `../../../../assets/blog/covers/.jpg`. - -**Real examples from existing cookbooks:** +| Field | Required | Notes | +| ----------------- | -------- | ------------------------------------ | +| `title` | Yes | ≤60 chars, verb-first, sentence case | +| `description` | Yes | ≤160 chars, problem and outcome | +| `sidebar.label` | Yes | Short left-rail label (1–3 words) | +| `sidebar.order` | Yes | Explicit order in the product shelf | +| `tableOfContents` | Optional | Default true for long recipes | -```yaml -# implement-nextjs-auth.mdx -title: 'Implementing Passwordless Auth in Next.js 15' -description: "Add magic link and OTP authentication to your Next.js application using Scalekit's headless API." -date: 2025-02-19 -tags: ['Full stack auth', 'Next.js'] -featured: false -``` +## Create a cookbook -```yaml -# building-custom-org-switcher.mdx (featured, with cover) -title: 'Building a Custom Organization Switcher' -description: 'Learn how to build your own organization switcher UI for complete control over multi-tenant user experiences.' -date: 2025-01-21 -tags: ['Full stack auth'] -featured: true -cover: - alt: 'Modern desk setup with laptop and workspace accessories' - image: ../../../../assets/blog/covers/custom-org-switcher.jpg -``` +1. Choose the product shelf (`agentkit/recipes` or `saaskit/recipes`). +2. Create `src/content/docs//cookbooks/.mdx`. +3. Write the recipe body (problem, steps, working code, failure modes). +4. Put images under `src/assets/docs//cookbooks//` and reference them as + `@/assets/docs//cookbooks//.png`. +5. Run a local build or `pnpm start` and confirm the page appears under the product **Recipes** group. -**Tag vocabulary** (use existing tags for consistency): +**Redirects:** a brand-new cookbook needs none. Only a page whose published URL changes needs an +entry in `src/configs/redirects.config.ts`, and it must be listed one slug at a time — a +`/cookbooks/*` splat cannot work, because the old flat namespace now splits across two products. -- Product area: `Full stack auth`, `SSO`, `SCIM`, `Agent auth`, `M2M` -- Framework/language: `Next.js`, `Node.js`, `Python`, `Go`, `Java`, `Spring Boot` -- Pattern: `JWT`, `OAuth`, `Webhooks`, `API keys` - ---- +**Images:** moving a cookbook means moving its assets in the same commit. A stale `@/assets/…` path +is a hard build failure (`[ImageNotFound]`), and `pnpm start` will not surface it because dev +compiles pages only when you visit them. Run a full `pnpm build` before pushing a move. ## Content structure -Follow this order. Every section is required unless marked optional. - ### Opening (no heading) -2–4 sentences immediately after the frontmatter. State: - -- The concrete developer context (framework, pattern, use case) -- Why this is hard without the recipe -- What the cookbook teaches - -Do not start with "In this guide" or "Welcome to". Start with the problem or the context. +2–4 sentences: concrete context, why it is hard without the recipe, what the reader will build. ### The problem -Use heading `## The problem`. Describe the specific pain points the developer faces. Use a bulleted list with **bolded pain point** — explanation format. Be concrete. Name the exact failure modes, error types, or architectural mismatches. - -**Combined opening**: When the audience and pain points are tightly coupled, you can merge the opening paragraph, problem bullets, and audience callout into a single unnumbered intro block — no separate `## The problem` or `## Who needs this` headings needed. Use this when the cookbook is focused enough that every reader faces the same problem. The combined block should still cover: context, concrete pain points, and a one- or two-line scope statement ("this is for X; if you're doing Y, see Z"). - -### Who needs this (optional but recommended) - -Use heading `## Who needs this`. Two short lists: - -- Checkmark bullets for the intended audience -- X bullets for who should look elsewhere - -This reduces support noise and increases relevance for the right reader. Omit this section when audience and scope are already clear from the combined opening. - -### The solution - -Use heading `## The solution`. Before any code: explain the approach in 2–4 sentences. Name the specific APIs, methods, or patterns you will use and why they fit the problem. - -### Implementation - -Use heading `## Implementation`. Break into numbered H3 subsections (`### 1. Step name`). Each step should: - -- Have a clear, task-oriented title -- Include working code (see code standards below) -- Explain what the code does and why — not just paste it -- Note security implications where relevant - -### Testing and verification (optional but recommended) - -Use heading `## Testing`. Show the developer how to verify the implementation works. Include expected output, curl commands, test assertions, or screenshots where appropriate. - -### Common mistakes - -Use heading `## Common mistakes`. Bulleted list of the 3–5 most frequent errors, each with: - -- The symptom or error message -- The cause -- The fix - -### Production notes (optional but recommended) - -Use heading `## Production notes`. Capture what changes between a working prototype and a production deployment. Include: - -- Failure modes specific to production scale or load -- Security hardening steps not covered in the main implementation -- Monitoring and observability hooks -- When not to use this pattern - -### Next steps - -Use heading `## Next steps`. 3–5 links or actions the developer can take to go deeper or extend the recipe. - ---- - -## Code standards +Heading `## The problem`. Bulleted pain points with **bold** lead-ins. -All code in cookbooks must follow the project-wide standards from `AGENTS.md`. Key rules for cookbooks: +### Who needs this (optional) -- **Multi-language**: Use `` with all four SDK languages (Node.js, Python, Go, Java) for at least 90% of SDK-related code blocks. -- **SDK variable names**: `scalekit` (Node.js), `scalekit_client` (Python), `scalekitClient` (Go/Java) — non-negotiable. -- **Working code**: Examples must be runnable or clearly marked as illustrative pseudocode. -- **No hardcoded secrets**: Always use environment variables. Add a comment explaining why. -- **Error handling**: Show the failure path alongside the happy path. +Two short lists: for you if / not for you if. ---- - -## Authoring checklist - -Use this before submitting a cookbook for review. - -### Content - -- [ ] Title is verb-first, task-focused, ≤60 characters -- [ ] Description states the problem and outcome, ≤160 characters -- [ ] Excerpt is 2–3 sentences, no hype or filler -- [ ] Opening paragraph states context, difficulty, and what reader will learn -- [ ] P.A.T. structure is present: problem → angle → teach -- [ ] "The problem" section uses concrete pain points, not vague descriptions -- [ ] Solution is explained before code, not just introduced by it -- [ ] Each implementation step has a task-oriented heading -- [ ] Common mistakes section exists - -### Code - -- [ ] All code uses correct SDK variable names (`scalekit`, `scalekit_client`, `scalekitClient`) -- [ ] SDK examples cover all four languages (90% rule) -- [ ] No hardcoded secrets -- [ ] Error paths are shown -- [ ] Code compiles or is clearly marked pseudocode - -### Writing - -- [ ] Sentence case for all headings -- [ ] No "just", "simply", "obviously", "we're excited" -- [ ] Active voice throughout -- [ ] Second person ("you") for instructions -- [ ] Present tense ("this method returns" not "will return") -- [ ] Technical terms defined on first use -- [ ] Links use descriptive text (not "click here") - -### Frontmatter - -- [ ] `title`, `description`, `date`, `tags`, `excerpt`, `authors` all present -- [ ] Tags match existing vocabulary -- [ ] Author photo exists at the specified path - ---- - -## Review checklist - -Use this when reviewing a cookbook authored by someone else. - -### P.A.T. pass - -- [ ] Can you identify the exact problem in the first two paragraphs? -- [ ] Is the angle (why this approach) stated, not implied? -- [ ] Does the teach section teach — or just show code? - -### Accuracy pass - -- [ ] Code examples have been tested against the current SDK -- [ ] API method names match current SDK docs -- [ ] Environment variable names are consistent with other cookbooks -- [ ] No steps assume undocumented behavior - -### Audience pass - -- [ ] Would a developer new to Scalekit understand this? -- [ ] Are prerequisites explicitly stated? -- [ ] Does it avoid assuming reader knows internal Scalekit architecture? - -### Quality pass - -- [ ] Does every section earn its place? Remove anything that doesn't add value. -- [ ] Are the "Common mistakes" real mistakes, or invented edge cases? -- [ ] Does the cookbook feel like it was written by someone who solved this problem, or like it was written to explain a feature? - ---- - -## Prompt for AI-assisted authoring - -When using an AI assistant to draft or rewrite a cookbook, use the following prompt. Provide your rough draft after the prompt. - -```text -You are a senior technical content strategist and developer education writer. - -PRIMARY DIRECTIVE - -Transform my rough draft into a task-oriented developer cookbook for src/content/docs/cookbooks/. - -This cookbook must: -- help developers accomplish a specific, concrete implementation task, -- teach through example-backed, implementation-oriented content, -- separate any orientation context from hands-on recipes, -- use task-first naming throughout, -- and be independently useful — a developer should land on this, solve the problem, and move on. +### Procedure -Apply the P.A.T. framework to the whole cookbook and to each major section: -1. Problem — Start from the real developer pain point -2. Angle — Give a clear point of view on why this approach -3. Teach — Teach step by step with code, explanations, outcomes, and failure modes +Use `` for ordered work. Multi-language SDK samples use `` with Node.js, Python, Go, and Java when the 90% rule applies. -STRUCTURE DIRECTIVE +### Verify / next steps -Build the cookbook in up to three layers (use only the layers the content actually needs): - -1. Orientation (if needed) - - What problem this cookbook solves - - Who it is for and who should look elsewhere - - Mental model or architecture overview - - Recommended reading path - -2. Core concept (if the implementation pattern needs explaining first) - - Why this pattern exists - - Tradeoffs vs alternatives - - Security boundaries - -3. Recipes (required) - For each recipe, include: - - Title (task-oriented, sounds like a search query) - - The problem this solves - - When to use it - - Inputs / prerequisites - - Step-by-step implementation - - Code walkthrough (not just code dump) - - Expected outcome - - What could go wrong - - Production notes - - Related recipes - -NAMING RULES - -Name recipes and titles like concrete developer tasks: -- "Set up agent auth locally" -- "Pass user context through an auth layer" -- "Validate identity before tool execution" -- "Handle token refresh for long-running agent sessions" -- "Debug failed OAuth flows in Python" - -Avoid abstract theme titles like "Authentication concepts" or "Token handling." - -REPOSITORY REFERENCE - -For agent auth cookbooks, use as source of truth: -https://github.com/scalekit-developers/agent-auth-examples -(README.md, AGENTS.md, javascript/*, python/*) - -Extract reusable patterns from the repo — don't just summarize files. -Call out where JavaScript and Python implementations meaningfully differ. - -WRITING RULES - -- Share knowledge, not features -- Prefer one language as the primary walkthrough when clarity matters; show the other language only where the implementation differs -- No hype, no vague claims, no marketing copy -- Useful even for someone who has not yet adopted Scalekit -- Optimize for bookmarking: each recipe should stand alone - -SCALEKIT CODE CONVENTIONS - -- SDK variable names (non-negotiable): scalekit (Node.js), scalekit_client (Python), scalekitClient (Go/Java) -- Use for multi-language blocks -- No hardcoded secrets; use environment variables with a comment explaining why -- Show error paths alongside the happy path - -OUTPUT FORMAT - -Return in this order: - -1. Positioning - - 3 title options (verb-first, task-focused, ≤60 chars each) - - Recommended title + one-sentence promise - - Target audience - -2. Proposed structure - - Table of contents - - One-line purpose for each section - -3. Rewritten draft - - Full MDX with correct frontmatter (title, description, date, tags, excerpt, featured, authors — cover only if featured: true) - - P.A.T. structure throughout - - Multi-language code tabs where applicable - - Production notes and failure modes for each recipe - -4. Editorial notes - - What you changed and why - - Weak spots in the original draft - - Missing material to add - - Where real test output or additional examples would strengthen it - -Quality bar: the final result should feel like a practitioner wrote it from experience — something a developer would bookmark and return to. - ---- - -Audience context (fill in before pasting): -- Who is this for: -- Desired outcome: -- Constraints or scope limits: - ---- - -Here is my first draft: - -[PASTE DRAFT HERE] -``` - ---- +How the reader knows it worked; links to related journey docs or how-tos. -## Publishing +## Diagrams (d2) -Cookbooks are automatically listed at `/cookbooks/` once the `.mdx` file is committed and built. No sidebar configuration is needed — `starlight-blog` handles discovery and pagination. +If you add a `d2` code fence, generate and commit the SVG under `public/d2/docs//cookbooks/` after a local build (Netlify does not run `d2`). -To feature a cookbook on the index page, set `featured: true` in frontmatter. +## How-to guides -Authors appear on the cookbook detail page. Author photos should be placed in `public/images/blog/authors/` before the cookbook is published. +How-tos are short, single-task dashboard pages in `*/how-to/`. Prefer imperative titles ("Manage environments"). Keep them out of the product journey sidebars — they belong only in the guides topic. diff --git a/public/_redirects b/public/_redirects index 3adb5ba54..cd255ff7c 100644 --- a/public/_redirects +++ b/public/_redirects @@ -8,6 +8,14 @@ /agent-auth/agentic-quickstart /agentkit/tools/agent-tools-quickstart/ 301 /agent-auth/start-agent-auth-coding-agents /agentkit/build-with-ai/ 301 +# Keep building: cookbooks URLs are now recipes +/agentkit/cookbooks /agentkit/recipes/ 301 +/agentkit/cookbooks/ /agentkit/recipes/ 301 +/agentkit/cookbooks/* /agentkit/recipes/:splat 301 +/saaskit/cookbooks /saaskit/recipes/ 301 +/saaskit/cookbooks/ /saaskit/recipes/ 301 +/saaskit/cookbooks/* /saaskit/recipes/:splat 301 + # Connectors rename redirect /agentkit/providers /agentkit/connectors/ 301 diff --git a/public/d2/docs/cookbooks/litellm-agentkit-inbox-triage-0.svg b/public/d2/docs/agentkit/recipes/litellm-agentkit-inbox-triage-0.svg similarity index 100% rename from public/d2/docs/cookbooks/litellm-agentkit-inbox-triage-0.svg rename to public/d2/docs/agentkit/recipes/litellm-agentkit-inbox-triage-0.svg diff --git a/public/d2/docs/cookbooks/litellm-agentkit-inbox-triage-1.svg b/public/d2/docs/agentkit/recipes/litellm-agentkit-inbox-triage-1.svg similarity index 100% rename from public/d2/docs/cookbooks/litellm-agentkit-inbox-triage-1.svg rename to public/d2/docs/agentkit/recipes/litellm-agentkit-inbox-triage-1.svg diff --git a/scripts/generate-llms-index.js b/scripts/generate-llms-index.js index 05f397e55..7b65d4a2c 100644 --- a/scripts/generate-llms-index.js +++ b/scripts/generate-llms-index.js @@ -204,8 +204,14 @@ const OTHER_SECTIONS = [ match: (p) => p.startsWith('/reference/') || p.startsWith('/dev-kit/sdks/'), }, { - heading: 'Cookbooks & Examples', - match: (p) => p.startsWith('/cookbooks/') || p.startsWith('/resources/'), + heading: 'Recipes & Examples', + match: (p) => + p.startsWith('/agentkit/recipes/') || + p.startsWith('/saaskit/recipes/') || + p.startsWith('/agentkit/how-to/') || + p.startsWith('/how-to/') || + p.startsWith('/saaskit/how-to/') || + p.startsWith('/resources/'), }, { heading: 'Developer Kit & AI-Assisted Development', diff --git a/src/assets/docs/cookbooks/voice-assistant/tool-registration-scalekit.png b/src/assets/docs/agentkit/recipes/voice-assistant/tool-registration-scalekit.png similarity index 100% rename from src/assets/docs/cookbooks/voice-assistant/tool-registration-scalekit.png rename to src/assets/docs/agentkit/recipes/voice-assistant/tool-registration-scalekit.png diff --git a/src/assets/docs/cookbooks/voice-assistant/vmcp-scalekit.png b/src/assets/docs/agentkit/recipes/voice-assistant/vmcp-scalekit.png similarity index 100% rename from src/assets/docs/cookbooks/voice-assistant/vmcp-scalekit.png rename to src/assets/docs/agentkit/recipes/voice-assistant/vmcp-scalekit.png diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/02-dashboard-org-linked.png b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/02-dashboard-org-linked.png similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/02-dashboard-org-linked.png rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/02-dashboard-org-linked.png diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/03-billing-choose-plan.png b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/03-billing-choose-plan.png similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/03-billing-choose-plan.png rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/03-billing-choose-plan.png diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/04-chargebee-cart.png b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/04-chargebee-cart.png similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/04-chargebee-cart.png rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/04-chargebee-cart.png diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/05-chargebee-checkout.png b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/05-chargebee-checkout.png similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/05-chargebee-checkout.png rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/05-chargebee-checkout.png diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/06-billing-subscription-active.png b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/06-billing-subscription-active.png similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/06-billing-subscription-active.png rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/06-billing-subscription-active.png diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/README.md b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/README.md similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/README.md rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/README.md diff --git a/src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/architecture.png b/src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/architecture.png similarity index 100% rename from src/assets/docs/cookbooks/sync-b2b-billing-with-chargebee/architecture.png rename to src/assets/docs/saaskit/recipes/sync-b2b-billing-with-chargebee/architecture.png diff --git a/src/components/SecondaryNav.astro b/src/components/SecondaryNav.astro index 24637dc9b..39e71c8e7 100644 --- a/src/components/SecondaryNav.astro +++ b/src/components/SecondaryNav.astro @@ -3,7 +3,7 @@ import { secondaryNavConfig, IconLucideCheck } from '../configs/secondary-nav.co import { getActiveProduct, isCurrentPage, - isSelfHostedPath, + isSharedProductPath, getDisplayLabel, type SecondaryNavProps, } from '../utils/secondary-nav-utils' @@ -16,7 +16,7 @@ const { entry } = Astro.props satisfies SecondaryNavProps const searchParams = Astro.url.searchParams const activeProduct = getActiveProduct(Astro.url.pathname, entry?.data?.topic, searchParams) -const isSharedPath = isSelfHostedPath(Astro.url.pathname) +const isSharedPath = isSharedProductPath(Astro.url.pathname) // Shared self-hosted pages: render both product rows so the client can swap without // a full rebuild. SSR shows AgentKit (cold default). Unambiguous pages render one row. @@ -94,6 +94,7 @@ function groupNavItems(items: NavItem[]): NavItem[][] { class={itemClass} aria-current={isCurrent ? 'page' : undefined} data-astro-prefetch="viewport" + data-nav-id={item.id} data-product-context={ item.id === 'enterprise-deployment' ? product : undefined } @@ -983,6 +984,16 @@ function groupNavItems(items: NavItem[]): NavItem[][] { if (row.getAttribute('data-product') === product) row.removeAttribute('hidden') else row.setAttribute('hidden', '') }) + if (location.pathname === '/how-to' || location.pathname.startsWith('/how-to/')) { + document.querySelectorAll('.secondary-nav-product .nav-item').forEach((el) => { + const id = el.getAttribute('data-nav-id') + el.classList.toggle( + 'current', + el.closest('[data-product]')?.getAttribute('data-product') === product && + (id === 'agentkit-guides' || id === 'saaskit-guides'), + ) + }) + } } function initSecondaryNav() { @@ -1005,6 +1016,7 @@ function groupNavItems(items: NavItem[]): NavItem[][] { try { sessionStorage.setItem(storageKey, product) } catch {} + document.cookie = `${storageKey}=${product}; path=/; SameSite=Lax` }, { signal, capture: true }, ) diff --git a/src/components/overrides/HeaderProductToggle.astro b/src/components/overrides/HeaderProductToggle.astro index 6f6d75f53..8f12d0541 100644 --- a/src/components/overrides/HeaderProductToggle.astro +++ b/src/components/overrides/HeaderProductToggle.astro @@ -2,7 +2,7 @@ import { getActiveSecondaryNavId, getActiveProduct, - isSelfHostedPath, + isSharedProductPath, type SecondaryNavProps, } from '../../utils/secondary-nav-utils' import { PRODUCT_STORAGE_KEY } from '../../configs/self-hosted' @@ -18,7 +18,7 @@ const activeProduct = getActiveProduct(Astro.url.pathname, entry?.data?.topic, s // Self-hosted routes are shared; product is resolved (cold default AgentKit or // ?product=). Client restores SaaS from sessionStorage when needed. Never show // a third "Self Hosted" product option in the picker. -const isSharedPath = isSelfHostedPath(Astro.url.pathname) +const isSharedPath = isSharedProductPath(Astro.url.pathname) // activeId !== null means the page is in the doc system with a known nav context. // Shared items (rest-apis, sdks, etc.) live in SaasKit nav, so activeProduct covers them. @@ -135,6 +135,16 @@ const productLinks = [ return phrase?.getAttribute('data-storage-key') || 'sk-active-product' } + function persistProduct(product: string) { + const storageKey = getStorageKey() + try { + sessionStorage.setItem(storageKey, product) + } catch { + /* private mode */ + } + document.cookie = `${storageKey}=${product}; path=/; SameSite=Lax` + } + /** * Resolve product for the current page: ?product= wins, then path signals, * then session memory on shared paths, then SSR default baked into the DOM. @@ -165,11 +175,7 @@ const productLinks = [ } if (knownProduct) { - try { - sessionStorage.setItem(storageKey, knownProduct) - } catch { - /* private mode */ - } + persistProduct(knownProduct) } if (isShared) { @@ -231,6 +237,17 @@ const productLinks = [ }) document.dispatchEvent(new CustomEvent('sk:product-context', { detail: { product } })) + tagSharedHowToLinks(product) + } + + /** Autogenerate cannot add ?product=; stamp it so the next /how-to/** render keeps this rail. */ + function tagSharedHowToLinks(product: string) { + document.querySelectorAll('a[href^="/how-to/"]').forEach((anchor) => { + if (!(anchor instanceof HTMLAnchorElement)) return + const url = new URL(anchor.href, location.origin) + url.searchParams.set('product', product) + anchor.setAttribute('href', `${url.pathname}${url.search}${url.hash}`) + }) } function restoreProductContext() { @@ -345,11 +362,7 @@ const productLinks = [ const storageValue = option.getAttribute('data-storage-value') if (storageValue === 'agentkit' || storageValue === 'saaskit') { - try { - sessionStorage.setItem(getStorageKey(), storageValue) - } catch { - /* private mode */ - } + persistProduct(storageValue) applyProductChrome(storageValue) } diff --git a/src/configs/ai-setup-pages.test.js b/src/configs/ai-setup-pages.test.js index 84586a23d..f82faa7da 100644 --- a/src/configs/ai-setup-pages.test.js +++ b/src/configs/ai-setup-pages.test.js @@ -45,7 +45,7 @@ test('AI setup templates do not invent skill tokens as plugin names', () => { test('AgentKit coding-agent cookbook verify uses SCALEKIT_ENVIRONMENT_URL', () => { const text = read( - join(repoRoot, 'src/content/docs/cookbooks/set-up-agentkit-with-your-coding-agent.mdx'), + join(repoRoot, 'src/content/docs/agentkit/recipes/set-up-agentkit-with-your-coding-agent.mdx'), ) assert.equal(text.includes('SCALEKIT_ENVIRONMENT_URL'), true) assert.equal(text.includes('SCALEKIT_ENV_URL'), false) diff --git a/src/configs/llms.config.ts b/src/configs/llms.config.ts index 03bf25bc5..40ff6a7ee 100644 --- a/src/configs/llms.config.ts +++ b/src/configs/llms.config.ts @@ -57,13 +57,15 @@ Start with the Quickstart Collection, then follow the developer's question to th 'directory/scim/**', 'guides/user-auth/**', 'guides/user-management/**', + 'saaskit/recipes/**', + 'how-to/**', ], }, { label: 'AgentKit', description: 'Complete AgentKit documentation with connectors, frameworks, and tool calling for AI agents', - paths: ['agentkit/**', 'dev-kit/ai-assisted-development/**', 'cookbooks/**'], + paths: ['agentkit/**', 'dev-kit/ai-assisted-development/**'], }, { label: 'AgentKit Frameworks', @@ -122,6 +124,7 @@ Start with the Quickstart Collection, then follow the developer's question to th 'guides/integrations/index', 'guides/integrations/*/index', 'guides/dashboard/**', + 'how-to/**', // Workspace/dashboard how-tos (shared by both products) 'dev-kit/api-collections/**', ], }, @@ -139,7 +142,8 @@ Start with the Quickstart Collection, then follow the developer's question to th '**/overview', // All overview pages '**/quickstart', // All quickstart guides 'agentkit/examples/**', // Framework examples (high value for agent queries) - 'cookbooks/**', // Practical cookbooks + 'agentkit/recipes/**', // Practical AgentKit cookbooks + 'saaskit/recipes/**', // Practical SaaSKit cookbooks 'fsa/data-modelling', // Critical data modeling guide 'authenticate/set-up-scalekit', // Initial setup 'authenticate/fsa/complete-login', // Core FSA flow diff --git a/src/configs/product-agent-blocks.test.js b/src/configs/product-agent-blocks.test.js index 89d7cdb1b..e2a1536a3 100644 --- a/src/configs/product-agent-blocks.test.js +++ b/src/configs/product-agent-blocks.test.js @@ -74,7 +74,9 @@ test('product agent block is a copy-only CTA', () => { test('AgentKit pages do not point at a removed FoldCard playbook', () => { const quickstart = read('src/content/docs/agentkit/quickstart.mdx') - const cookbook = read('src/content/docs/cookbooks/set-up-agentkit-with-your-coding-agent.mdx') + const cookbook = read( + 'src/content/docs/agentkit/recipes/set-up-agentkit-with-your-coding-agent.mdx', + ) assert.equal(quickstart.includes('Build with a coding agent'), false) assert.equal(cookbook.includes('playbook below'), false) }) diff --git a/src/configs/redirects.config.ts b/src/configs/redirects.config.ts index 1ccdac129..78ffaabcd 100644 --- a/src/configs/redirects.config.ts +++ b/src/configs/redirects.config.ts @@ -216,9 +216,10 @@ export const redirects = { // Coding agent guides moved from product quickstarts to /dev-kit/build-with-ai/ // Note: With trailingSlash: 'ignore', single redirect without slash handles both /path and /path/ variants - '/agentkit/start-agentkit-coding-agents': '/cookbooks/set-up-agentkit-with-your-coding-agent/', + '/agentkit/start-agentkit-coding-agents': + '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', '/agent-auth/start-agent-auth-coding-agents': - '/cookbooks/set-up-agentkit-with-your-coding-agent/', + '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', '/authenticate/fsa/start-fsa-coding-agents': '/dev-kit/build-with-ai/full-stack-auth/', '/authenticate/mcp/start-mcp-auth-coding-agents': '/dev-kit/build-with-ai/mcp-auth/', '/authenticate/sso/start-sso-coding-agents': '/dev-kit/build-with-ai/sso/', @@ -226,12 +227,12 @@ export const redirects = { // Build with AI moved from /build-with-ai/ to /dev-kit/build-with-ai/ // Agent Auth variant now lives in cookbooks - '/agentkit/build-with-ai': '/cookbooks/set-up-agentkit-with-your-coding-agent/', + '/agentkit/build-with-ai': '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', '/build-with-ai': '/dev-kit/build-with-ai/', '/build-with-ai/full-stack-auth': '/dev-kit/build-with-ai/full-stack-auth/', - '/build-with-ai/agent-auth': '/cookbooks/set-up-agentkit-with-your-coding-agent/', - '/dev-kit/build-with-ai/agentkit': '/cookbooks/set-up-agentkit-with-your-coding-agent/', - '/dev-kit/build-with-ai/agent-auth': '/cookbooks/set-up-agentkit-with-your-coding-agent/', + '/build-with-ai/agent-auth': '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', + '/dev-kit/build-with-ai/agentkit': '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', + '/dev-kit/build-with-ai/agent-auth': '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', '/build-with-ai/mcp-auth': '/dev-kit/build-with-ai/mcp-auth/', '/build-with-ai/sso': '/dev-kit/build-with-ai/sso/', '/build-with-ai/scim': '/dev-kit/build-with-ai/scim/', @@ -471,4 +472,83 @@ export const redirects = { '/sdks/go/reference': '/saaskit/sdks/go/', '/sdks/java': '/saaskit/sdks/java/', '/sdks/java/reference': '/saaskit/sdks/java/', + + // ============================================================================= + // COOKBOOK → RECIPE PREFIX (Keep building hubs) + // ============================================================================= + '/agentkit/cookbooks': '/agentkit/recipes/', + '/agentkit/cookbooks/[...slug]': '/agentkit/recipes/[...slug]', + '/saaskit/cookbooks': '/saaskit/recipes/', + '/saaskit/cookbooks/[...slug]': '/saaskit/recipes/[...slug]', + + // ============================================================================= + // COOKBOOK REDIRECTS (per-product shelves) + // ============================================================================= + '/cookbooks/apify-actor-per-user-oauth': '/agentkit/recipes/apify-actor-per-user-oauth/', + '/cookbooks/build-voice-assistant-1000-tools': + '/agentkit/recipes/build-voice-assistant-1000-tools/', + '/cookbooks/crewai-agentkit-email-triage': '/agentkit/recipes/crewai-agentkit-email-triage/', + '/cookbooks/daily-briefing-agent': '/agentkit/recipes/daily-briefing-agent/', + '/cookbooks/fastrouter-agentkit-tool-calling': + '/agentkit/recipes/fastrouter-agentkit-tool-calling/', + '/cookbooks/langsmith-tracing-agentkit': '/agentkit/recipes/langsmith-tracing-agentkit/', + '/cookbooks/litellm-agentkit-inbox-triage': '/agentkit/recipes/litellm-agentkit-inbox-triage/', + '/cookbooks/livekit-agentkit-voice-tool-calling': + '/agentkit/recipes/livekit-agentkit-voice-tool-calling/', + '/cookbooks/mastra-agentkit': '/agentkit/recipes/mastra-agentkit/', + '/cookbooks/render-github-pr-summarizer': '/agentkit/recipes/render-github-pr-summarizer/', + '/cookbooks/schedule-meeting-and-draft-email': + '/agentkit/recipes/schedule-meeting-and-draft-email/', + '/cookbooks/set-up-agentkit-with-your-coding-agent': + '/agentkit/recipes/set-up-agentkit-with-your-coding-agent/', + '/cookbooks/add-enterprise-sso-nextjs-authjs': + '/saaskit/recipes/add-enterprise-sso-nextjs-authjs/', + '/cookbooks/add-hosted-auth-nextjs-app-router': + '/saaskit/recipes/add-hosted-auth-nextjs-app-router/', + '/cookbooks/building-custom-org-switcher': '/saaskit/recipes/building-custom-org-switcher/', + '/cookbooks/implement-nextjs-auth': '/saaskit/recipes/implement-nextjs-auth/', + '/cookbooks/java-spring-boot-jwt-timeout': '/saaskit/recipes/java-spring-boot-jwt-timeout/', + '/cookbooks/m2m-jwks-and-oauth-scopes': '/saaskit/recipes/m2m-jwks-and-oauth-scopes/', + '/cookbooks/migrate-from-auth0-to-scalekit': '/saaskit/recipes/migrate-from-auth0-to-scalekit/', + '/cookbooks/scim-seat-limit-enforcement': '/saaskit/recipes/scim-seat-limit-enforcement/', + '/cookbooks/search-scalekit-docs-in-your-ide': '/saaskit/recipes/', + '/saaskit/cookbooks/search-scalekit-docs-in-your-ide': '/saaskit/recipes/', + '/saaskit/cookbooks/search-scalekit-docs-in-your-ide/': '/saaskit/recipes/', + '/saaskit/recipes/search-scalekit-docs-in-your-ide': '/saaskit/recipes/', + '/saaskit/recipes/search-scalekit-docs-in-your-ide/': '/saaskit/recipes/', + '/cookbooks/sync-b2b-billing-with-chargebee': '/saaskit/recipes/sync-b2b-billing-with-chargebee/', + + // Routes that `starlight-blog` generated and nothing replaces. It injected + // `/[...prefix]/tags/[tag]`, `/[...prefix]/authors/[author]`, and a paginated + // `/[...prefix]/[...page]` (default 5 posts per page, so pages 2–5 existed for + // 22 cookbooks). Collapse all of them onto the hub instead of serving 404s. + // Listed after the slug redirects above; these patterns cannot shadow them. + '/cookbooks/tags/*': '/cookbooks/', + '/cookbooks/authors/*': '/cookbooks/', + '/cookbooks/2': '/cookbooks/', + '/cookbooks/3': '/cookbooks/', + '/cookbooks/4': '/cookbooks/', + '/cookbooks/5': '/cookbooks/', + + // Product cookbook index URLs are the Keep building hubs. + '/agentkit/how-to': '/agentkit/how-to/inspect-connected-accounts-in-the-dashboard/', + '/agentkit/how-to/': '/agentkit/how-to/inspect-connected-accounts-in-the-dashboard/', + '/how-to': '/how-to/environments/', + '/how-to/': '/how-to/environments/', + + // ============================================================================= + // WORKSPACE HOW-TO REDIRECTS + // ============================================================================= + '/saaskit/how-to': '/how-to/environments/', + '/saaskit/how-to/': '/how-to/environments/', + '/saaskit/how-to/billing': '/how-to/billing/', + '/saaskit/how-to/environments': '/how-to/environments/', + '/saaskit/how-to/manage-team-members': '/how-to/manage-team-members/', + '/dev-kit/guides/dashboard/billing': '/how-to/billing/', + '/dev-kit/guides/dashboard/environments': '/how-to/environments/', + '/dev-kit/guides/dashboard/manage-team-members': '/how-to/manage-team-members/', + + // Moving the dashboard how-tos out left `dev-kit/guides/` holding one orphaned + // page. It now sits with the other testing utilities. + '/dev-kit/guides/testing/scim-simulator': '/dev-kit/tools/scim-simulator/', } diff --git a/src/configs/secondary-nav.config.ts b/src/configs/secondary-nav.config.ts index 1be051f06..2975c453b 100644 --- a/src/configs/secondary-nav.config.ts +++ b/src/configs/secondary-nav.config.ts @@ -75,6 +75,14 @@ const agentKitItems: NavItem[] = [ label: 'Enterprise Deployment', iconComponent: IconSolarServerPathOutline, }, + { + // Keep building hub — isolated from lookup/ops (Connectors, SDKs, APIs, deploy) + id: 'agentkit-guides', + href: '/agentkit/recipes/', + label: 'Keep building', + iconComponent: IconLucideBookOpenText, + dividerBefore: true, + }, ] const saasKitItems: NavItem[] = [ @@ -126,6 +134,7 @@ const saasKitItems: NavItem[] = [ href: '#developer-resources', label: 'Developer Resources', iconComponent: IconHugeiconsResourcesAdd, + dividerBefore: true, children: [ { id: 'build-with-ai', @@ -174,15 +183,6 @@ const saasKitItems: NavItem[] = [ description: 'Automate user lifecycle and auth events with webhooks', columnGroup: 'right', }, - { - id: 'cookbooks', - href: '/cookbooks/', - label: 'Developer Resources', - dropdownLabel: 'Cookbooks', - iconComponent: IconLucideBookOpenText, - description: 'Implement common patterns with step-by-step recipes', - columnGroup: 'right', - }, { id: 'code-samples', href: '/resources/code-samples/', @@ -193,6 +193,13 @@ const saasKitItems: NavItem[] = [ }, ], }, + { + // Keep building hub — same group as Developer Resources (after lookup/ops) + id: 'saaskit-guides', + href: '/saaskit/recipes/', + label: 'Keep building', + iconComponent: IconLucideBookOpenText, + }, ] export const secondaryNavConfig: Record<'agentkit' | 'saaskit', NavItem[]> = { diff --git a/src/configs/self-hosted.ts b/src/configs/self-hosted.ts index 92fe39d8a..f4e50c3f0 100644 --- a/src/configs/self-hosted.ts +++ b/src/configs/self-hosted.ts @@ -1,7 +1,7 @@ /** - * Self-hosted deployment is shared by AgentKit and Auth for SaaS. - * Product chrome is resolved client-side via sessionStorage when the URL - * does not encode a product (see HeaderProductToggle + SecondaryNav). + * Shared product chrome: /self-hosted/** and /how-to/** do not encode a product. + * Header + secondary nav resolve it from ?product=, then sessionStorage / + * the sk-active-product cookie, then a cold default. * * Selection helpers (getActiveProduct, tab current) live in * src/utils/chrome-selection.js. Do not change this storage key or @@ -12,8 +12,31 @@ export const PRODUCT_STORAGE_KEY = 'sk-active-product' /** Cold load default for /self-hosted/** when no query param or session memory exists. */ export const SELF_HOSTED_COLD_DEFAULT_PRODUCT = 'agentkit' as const +/** + * Workspace how-tos (/how-to/**) are shared. Cold default is Auth for SaaS because + * these pages are workspace/dashboard tasks that historically lived under SaaS. + */ +export const SHARED_HOW_TO_COLD_DEFAULT_PRODUCT = 'saaskit' as const + export type DocsProduct = 'agentkit' | 'saaskit' export function isDocsProduct(value: string | null | undefined): value is DocsProduct { return value === 'agentkit' || value === 'saaskit' } + +export function isSelfHostedPath(pathname: string): boolean { + return pathname.startsWith('/self-hosted/') +} + +export function isSharedHowToPath(pathname: string): boolean { + return pathname === '/how-to' || pathname === '/how-to/' || pathname.startsWith('/how-to/') +} + +/** Routes that keep product chrome from session/cookie rather than from the path. */ +export function isSharedProductPath(pathname: string): boolean { + return isSelfHostedPath(pathname) || isSharedHowToPath(pathname) +} + +export function guidesTopicForProduct(product: DocsProduct): 'agentkit-guides' | 'saaskit-guides' { + return product === 'agentkit' ? 'agentkit-guides' : 'saaskit-guides' +} diff --git a/src/configs/sidebar.config.ts b/src/configs/sidebar.config.ts index 6bc487548..c8a0bcf06 100644 --- a/src/configs/sidebar.config.ts +++ b/src/configs/sidebar.config.ts @@ -238,6 +238,163 @@ export const sidebar = [ }, ], }, + // Product cookbook shelves — dedicated sidebars entered from secondary nav, not journey rails + { + label: 'Keep building', + id: 'agentkit-guides', + link: '/agentkit/recipes/', + icon: 'open-book', + items: [ + { + label: 'How-to', + collapsed: false, + items: [ + 'how-to/environments', + { autogenerate: { directory: 'agentkit/how-to' } }, + { autogenerate: { directory: 'how-to' } }, + ], + }, + { + label: 'Recipes', + collapsed: false, + items: [ + { + label: 'Triage email', + collapsed: true, + items: [ + { + label: 'CrewAI email triage', + link: 'agentkit/recipes/crewai-agentkit-email-triage', + }, + { + label: 'LiteLLM inbox triage', + link: 'agentkit/recipes/litellm-agentkit-inbox-triage', + }, + { + label: 'Daily briefing agent', + link: 'agentkit/recipes/daily-briefing-agent', + }, + { + label: 'Book meetings', + link: 'agentkit/recipes/schedule-meeting-and-draft-email', + }, + ], + }, + { + label: 'Add voice', + collapsed: true, + items: [ + { + label: 'Build a Vapi assistant', + link: 'agentkit/recipes/build-voice-assistant-1000-tools', + }, + { + label: 'Build a LiveKit agent', + link: 'agentkit/recipes/livekit-agentkit-voice-tool-calling', + }, + ], + }, + { + label: 'Call tools', + collapsed: true, + items: [ + { label: 'Build a Mastra agent', link: 'agentkit/recipes/mastra-agentkit' }, + { + label: 'Call tools via FastRouter', + link: 'agentkit/recipes/fastrouter-agentkit-tool-calling', + }, + { + label: 'Trace calls in LangSmith', + link: 'agentkit/recipes/langsmith-tracing-agentkit', + }, + ], + }, + { + label: 'Connect a user', + collapsed: true, + items: [ + { + label: 'Set up in a coding agent', + link: 'agentkit/recipes/set-up-agentkit-with-your-coding-agent', + }, + { + label: 'Summarize GitHub PRs', + link: 'agentkit/recipes/render-github-pr-summarizer', + }, + { + label: 'Per-user OAuth on Apify', + link: 'agentkit/recipes/apify-actor-per-user-oauth', + }, + ], + }, + ], + }, + ], + }, + { + label: 'Keep building', + id: 'saaskit-guides', + link: '/saaskit/recipes/', + icon: 'open-book', + items: [ + { + label: 'How-to', + collapsed: false, + items: ['how-to/environments', { autogenerate: { directory: 'how-to' } }], + }, + { + label: 'Recipes', + collapsed: false, + items: [ + { + label: 'Add sign-in', + collapsed: true, + items: [ + { + label: 'Hosted auth in Next.js', + link: 'saaskit/recipes/add-hosted-auth-nextjs-app-router', + }, + { + label: 'Passwordless in Next.js', + link: 'saaskit/recipes/implement-nextjs-auth', + }, + { + label: 'Custom org switcher', + link: 'saaskit/recipes/building-custom-org-switcher', + }, + { + label: 'SSO with Auth.js', + link: 'saaskit/recipes/add-enterprise-sso-nextjs-authjs', + }, + ], + }, + { + label: 'Verify tokens', + collapsed: true, + items: [ + { + label: 'M2M JWT and scopes', + link: 'saaskit/recipes/m2m-jwks-and-oauth-scopes', + }, + { + label: 'Spring Boot JWT timeout', + link: 'saaskit/recipes/java-spring-boot-jwt-timeout', + }, + ], + }, + { + label: 'Enforce seat limits', + link: 'saaskit/recipes/scim-seat-limit-enforcement', + }, + { + label: 'Sync Chargebee billing', + link: 'saaskit/recipes/sync-b2b-billing-with-chargebee', + }, + { label: 'Migrate from Auth0', link: 'saaskit/recipes/migrate-from-auth0-to-scalekit' }, + ], + }, + ], + }, { label: 'Developer Kit', id: 'dev-kit', @@ -269,6 +426,7 @@ export const sidebar = [ items: [ 'dev-kit/tools/scalekit-dryrun', 'dev-kit/tools/sso-simulator', + 'dev-kit/tools/scim-simulator', 'dev-kit/tools/use-scalekit-credentials', ], }, @@ -608,6 +766,9 @@ export const exclude = [ '/blog', '/404', // Error page '/apis/**/*', // REST API reference has Scalar-powered navigation + // Cross-product cookbook hub: belongs to no single product, so it must not + // inherit the Auth for SaaS journey rail or light up a product nav pill. + '/cookbooks', ] /** @@ -632,6 +793,17 @@ export const topics = { // Agent connectors (dedicated connectors sidebar — must come before connect) 'agent-connectors': ['/agentkit/connectors/**/*'], + // Product guide shelves (before connect catch-all and resources) + 'agentkit-guides': [ + '/agentkit/recipes', + '/agentkit/recipes/**/*', + '/agentkit/how-to', + '/agentkit/how-to/**/*', + '/how-to', + '/how-to/**/*', + ], + 'saaskit-guides': ['/saaskit/recipes', '/saaskit/recipes/**/*', '/how-to', '/how-to/**/*'], + // Product SDK sidebars (before connect catch-all) 'agentkit-sdks': ['/agentkit/sdks/**/*'], 'saaskit-sdks': ['/saaskit/sdks/**/*', '/sdks', '/sdks/', '/sdks/expo/**/*', '/sdks/ios/**/*'], @@ -651,8 +823,6 @@ export const topics = { '/guides/**/*', '/browse/**/*', '/reference/**/*', - '/cookbooks', - '/cookbooks/**/*', '/**/*', // Catch-all: anything not matched above defaults here ], @@ -735,6 +905,10 @@ export const sidebarToSecondaryNav: Record = { // Agent connectors sidebar → AgentKit Connectors tab 'agent-connectors': 'agentkit-connectors', + // Product cookbook shelves → top-level Keep building secondary nav (not Developer Resources) + 'agentkit-guides': 'agentkit-guides', + 'saaskit-guides': 'saaskit-guides', + // AgentKit sidebar → AgentKit tabs connect: { default: 'agentkit-quickstart', @@ -772,7 +946,6 @@ export const sidebarToSecondaryNav: Record = { '/authenticate/interceptors': 'workflows', '/reference/interceptors': 'workflows', '/reference/admin-portal': 'workflows', - '/cookbooks': 'cookbooks', }, }, diff --git a/src/content.config.ts b/src/content.config.ts index c2e78caa6..bd796f73c 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -5,15 +5,13 @@ import { topicSchema } from 'starlight-sidebar-topics/schema' import { videosSchema } from 'starlight-videos/schemas' import { githubReleasesLoader } from 'astro-loader-github-releases' import { githubFilesLoader } from './loaders/github-files-loader' -import { blogSchema } from 'starlight-blog/schema' export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema({ extend: (context) => - blogSchema(context) - .merge(topicSchema) + topicSchema .merge(videosSchema) .merge(z.object({ overviewTitle: z.string().optional() })) .merge( diff --git a/src/content/docs/agentkit/examples/crewai.mdx b/src/content/docs/agentkit/examples/crewai.mdx index 67a049735..d7b163d9d 100644 --- a/src/content/docs/agentkit/examples/crewai.mdx +++ b/src/content/docs/agentkit/examples/crewai.mdx @@ -113,7 +113,7 @@ with MCPServerAdapter({ ## Multi-agent crew -CrewAI's real strength is multi-agent orchestration. For a full example that splits email triage across three specialized agents (scanner, prioritizer, drafter), see the [CrewAI email triage cookbook](/cookbooks/crewai-agentkit-email-triage/). +CrewAI's real strength is multi-agent orchestration. For a full example that splits email triage across three specialized agents (scanner, prioritizer, drafter), see the [CrewAI email triage cookbook](/agentkit/recipes/crewai-agentkit-email-triage/). ## Get the MCP server URL diff --git a/src/content/docs/agentkit/how-to/inspect-connected-accounts-in-the-dashboard.mdx b/src/content/docs/agentkit/how-to/inspect-connected-accounts-in-the-dashboard.mdx new file mode 100644 index 000000000..5b59adedc --- /dev/null +++ b/src/content/docs/agentkit/how-to/inspect-connected-accounts-in-the-dashboard.mdx @@ -0,0 +1,55 @@ +--- +title: 'Inspect a user connection in the dashboard' +description: 'Find a specific connected account in the Scalekit dashboard and read its state without adding logging to your agent.' +sidebar: + label: 'Inspect connected accounts' + order: 2 +tableOfContents: true +--- + +import { Aside } from '@astrojs/starlight/components' + +When an agent suddenly cannot reach a user's Gmail, Calendar, or GitHub, the cause is almost always +the state of that user's **connected account** rather than your code. The dashboard shows that state +directly, which is faster than adding logging and redeploying. + +This guide covers only where to look. For what each state means, and the code that fixes it, follow +the links to [Troubleshoot connection errors](/agentkit/authentication/troubleshooting/). + +## Find the connection + +Go to **Dashboard > Connections** and select the connection the agent uses — the name you pass as +`connection_name` in your code, such as `github-connect`. + +Each connection lists the connected accounts created against it. One row exists per user identifier +you have authorized. + + + +## Read the row + +The row's status tells you the next check. Only `ACTIVE` accounts are ready for tool calls. +Other statuses name the step the user or your app still owes. `ACTIVE` does not mean every +tool call will succeed. + +The dashboard reports the same `account.status` value as the SDK. Use the dashboard for a +one-off answer and the SDK when you need the check in code. If status is `ACTIVE` and the +tool still fails, check scopes, logs, or a read-only tool. + +For the full status list and the fix for each one, see +[Start with diagnostics](/agentkit/authentication/troubleshooting/#start-with-diagnostics). For the +webhook that pushes these changes to your app instead, see +[Detect when re-authentication is needed](/agentkit/connected-accounts/#detect-when-re-authentication-is-needed). + +## Narrow one user versus every user + +The row count is the useful signal the SDK will not give you in a single call: + +- **One row is failing** — start with that user's account. Confirm its status, then + re-authorize if it is not `ACTIVE`. +- **Every row is failing** — check the connection settings and credentials before you treat + it as a per-user issue. A rotated or expired OAuth client on the provider side takes down + every account beneath it at once. diff --git a/src/content/docs/agentkit/quickstart.mdx b/src/content/docs/agentkit/quickstart.mdx index cfd4399e0..a452d94d2 100644 --- a/src/content/docs/agentkit/quickstart.mdx +++ b/src/content/docs/agentkit/quickstart.mdx @@ -4,6 +4,9 @@ description: Build a working agent that makes authenticated tool calls on behalf tags: [agent-auth, quickstart, ai-agents, oauth, token-vault, tool-integration, delegated-oauth] sidebar: order: 1 +next: + label: 'Keep building' + link: '/agentkit/recipes/' --- import { Aside, TabItem } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/apify-actor-per-user-oauth.mdx b/src/content/docs/agentkit/recipes/apify-actor-per-user-oauth.mdx similarity index 97% rename from src/content/docs/cookbooks/apify-actor-per-user-oauth.mdx rename to src/content/docs/agentkit/recipes/apify-actor-per-user-oauth.mdx index f46b74098..d67e981f4 100644 --- a/src/content/docs/cookbooks/apify-actor-per-user-oauth.mdx +++ b/src/content/docs/agentkit/recipes/apify-actor-per-user-oauth.mdx @@ -1,17 +1,9 @@ --- title: 'Apify Actor with per-user OAuth via Scalekit' description: 'Build an Apify Actor that uses Scalekit Agent Auth so each user connects their OAuth accounts, keyed by Apify userId.' -date: 2026-04-21 sidebar: - label: 'Apify Actor per-user OAuth' -excerpt: > - Apify Actors run in isolated containers with no persistent session — there is no concept of "who is logged in." This recipe shows how to use Apify's built-in user identity as the key into Scalekit's OAuth token vault, so each user who runs your Actor connects their own third-party accounts, and tokens survive across runs without any user-managed input fields. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' + label: 'Per-user OAuth on Apify' + order: 12 --- import { Aside } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/build-voice-assistant-1000-tools.mdx b/src/content/docs/agentkit/recipes/build-voice-assistant-1000-tools.mdx similarity index 96% rename from src/content/docs/cookbooks/build-voice-assistant-1000-tools.mdx rename to src/content/docs/agentkit/recipes/build-voice-assistant-1000-tools.mdx index 74f293f42..740b1abbf 100644 --- a/src/content/docs/cookbooks/build-voice-assistant-1000-tools.mdx +++ b/src/content/docs/agentkit/recipes/build-voice-assistant-1000-tools.mdx @@ -1,19 +1,10 @@ --- title: 'Build a Vapi voice assistant with Scalekit' description: 'Use Vapi + Scalekit Virtual MCP for voice assistants to securely access any tool from large catalogs.' -date: 2026-07-01 -tags: ['Agent auth', 'Voice', 'MCP', 'Vapi'] sidebar: - label: 'Vapi + Scalekit assistant' + label: 'Build a Vapi assistant' + order: 10 tableOfContents: true -excerpt: > - Voice assistants are powerful for natural interaction but hit walls with auth and tool volume. This cookbook shows how to use Scalekit's Virtual MCP so a Vapi assistant can safely discover and call any tool the user is authorized for — without token bloat or per-tool OAuth code. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; @@ -159,7 +150,7 @@ Key differences from a naive "give the LLM every tool" approach: 4. Save. Copy the **config ID** (e.g. `cfg_...`) and the generated **mcp_server_url**. -![Creating a Virtual MCP in the Scalekit dashboard](@/assets/docs/cookbooks/voice-assistant/vmcp-scalekit.png) +![Creating a Virtual MCP in the Scalekit dashboard](@/assets/docs/agentkit/recipes/voice-assistant/vmcp-scalekit.png) The screenshot above shows the Scalekit dashboard flow for creating the scoped Virtual MCP. @@ -193,7 +184,7 @@ You have two main options in Vapi, depending on whether you want dynamic discove 4. Attach the MCP tool to the assistant. 5. Update the system prompt to tell the model when and how to use tools (example in the demo repo). -![Registering the MCP tool in the Vapi dashboard](@/assets/docs/cookbooks/voice-assistant/tool-registration-scalekit.png) +![Registering the MCP tool in the Vapi dashboard](@/assets/docs/agentkit/recipes/voice-assistant/tool-registration-scalekit.png) The screenshot above illustrates where to configure the server URL and add the Authorization HTTP header in Vapi's MCP tool form. diff --git a/src/content/docs/cookbooks/crewai-agentkit-email-triage.mdx b/src/content/docs/agentkit/recipes/crewai-agentkit-email-triage.mdx similarity index 96% rename from src/content/docs/cookbooks/crewai-agentkit-email-triage.mdx rename to src/content/docs/agentkit/recipes/crewai-agentkit-email-triage.mdx index 4ad4b82bc..f0a1e95d0 100644 --- a/src/content/docs/cookbooks/crewai-agentkit-email-triage.mdx +++ b/src/content/docs/agentkit/recipes/crewai-agentkit-email-triage.mdx @@ -1,22 +1,10 @@ --- title: 'Build a multi-agent email triage crew with CrewAI' description: 'Use CrewAI multi-agent orchestration with Scalekit-authenticated Gmail tools to scan, classify, and draft replies to emails.' -date: 2026-07-12 sidebar: label: 'CrewAI email triage' + order: 5 tableOfContents: true -excerpt: > - CrewAI lets you split complex workflows across specialized agents, but - each agent still needs authenticated access to user tools like Gmail. This - cookbook shows how to wire Scalekit OAuth into a CrewAI crew via MCP, - then build a three-agent pipeline that scans, classifies, and drafts - replies to a user's unread emails. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/daily-briefing-agent.mdx b/src/content/docs/agentkit/recipes/daily-briefing-agent.mdx similarity index 97% rename from src/content/docs/cookbooks/daily-briefing-agent.mdx rename to src/content/docs/agentkit/recipes/daily-briefing-agent.mdx index f4738cff9..b442309ad 100644 --- a/src/content/docs/cookbooks/daily-briefing-agent.mdx +++ b/src/content/docs/agentkit/recipes/daily-briefing-agent.mdx @@ -1,17 +1,9 @@ --- title: 'Build a daily briefing agent with Vercel AI SDK and Scalekit Agent Auth' description: 'Connect a TypeScript or Python agent via Vercel AI SDK and Scalekit AgentKit to Google Calendar and Gmail with authenticated tool calls.' -date: 2026-03-27 sidebar: label: 'Daily briefing agent' -excerpt: > - Connecting an agent to two external APIs means handling two separate OAuth tokens, two authorization flows, and two different error surfaces. This recipe shows how Scalekit manages the OAuth lifecycle for both connectors and how you call Calendar and Gmail through built-in tools — without talking to provider APIs yourself. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' + order: 7 --- import { TabItem, Tabs } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx b/src/content/docs/agentkit/recipes/fastrouter-agentkit-tool-calling.mdx similarity index 95% rename from src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx rename to src/content/docs/agentkit/recipes/fastrouter-agentkit-tool-calling.mdx index 939fbcae4..1b1882a58 100644 --- a/src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx +++ b/src/content/docs/agentkit/recipes/fastrouter-agentkit-tool-calling.mdx @@ -1,18 +1,10 @@ --- title: 'FastRouter + Scalekit tool calling' description: 'Build a Node.js agent that routes LLM calls through FastRouter and uses Scalekit for per-user OAuth tools.' -date: 2026-05-26 sidebar: - label: 'Tool calling with FastRouter' + label: 'Call tools via FastRouter' + order: 4 tableOfContents: true -excerpt: > - Connect FastRouter's OpenAI-compatible API to per-user OAuth tools via Scalekit. The agent discovers available tools, runs an agentic loop through FastRouter, and executes each tool call via Scalekit — no per-integration OAuth code required. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; @@ -350,4 +342,4 @@ const { tools } = await scalekit.tools.listScopedTools('user_123', { - **[Scalekit overview](/agentkit/connections)** — Understand connected accounts, tool discovery, and tool execution in depth. - **[AgentKit connections](/agentkit/connectors)** — Set up Gmail, GitHub, Slack, and other connections. - **[OpenAI example](/agentkit/examples/openai)** — See the same tool-calling pattern with OpenAI directly. -- **[LiteLLM inbox triage cookbook](/cookbooks/litellm-agentkit-inbox-triage)** — A more complex multi-connection agent with a web approval interface. +- **[LiteLLM inbox triage cookbook](/agentkit/recipes/litellm-agentkit-inbox-triage)** — A more complex multi-connection agent with a web approval interface. diff --git a/src/content/docs/agentkit/recipes/index.mdx b/src/content/docs/agentkit/recipes/index.mdx new file mode 100644 index 000000000..954b0e795 --- /dev/null +++ b/src/content/docs/agentkit/recipes/index.mdx @@ -0,0 +1,84 @@ +--- +title: 'Keep building' +description: 'Set up your workspace, then pick an AgentKit recipe for a job in your agent.' +topic: agentkit-guides +sidebar: + label: 'Keep building' + hidden: true +tableOfContents: false +--- + +import { CardGrid, LinkCard } from '@astrojs/starlight/components' + +After first success, set up the workspace you use. Then pick a recipe for a job in your agent. + +## Start with the workspace + +Open [Manage environments](/how-to/environments/) next. Create development, staging, and production. Pick a US or EU region. + +## Pick a recipe + +Recipes are jobs in your agent. Open one, finish it, and leave. + + + + + + + + + + + + + + + diff --git a/src/content/docs/cookbooks/langsmith-tracing-agentkit.mdx b/src/content/docs/agentkit/recipes/langsmith-tracing-agentkit.mdx similarity index 95% rename from src/content/docs/cookbooks/langsmith-tracing-agentkit.mdx rename to src/content/docs/agentkit/recipes/langsmith-tracing-agentkit.mdx index 76631fbbe..3cc665d04 100644 --- a/src/content/docs/cookbooks/langsmith-tracing-agentkit.mdx +++ b/src/content/docs/agentkit/recipes/langsmith-tracing-agentkit.mdx @@ -1,18 +1,10 @@ --- title: 'Trace AgentKit tool calls in LangSmith' description: 'Add LangSmith observability to a LangChain agent that uses Scalekit AgentKit tools for Gmail, Slack, GitHub, and 200+ connectors.' -date: 2026-05-12 sidebar: - label: 'LangSmith tracing' + label: 'Trace calls in LangSmith' + order: 13 tableOfContents: true -excerpt: > - Scalekit AgentKit returns native LangChain StructuredTool objects. Enable LangSmith tracing and every tool call — Gmail fetches, Slack messages, GitHub searches — appears as a traced span automatically. This recipe walks through setup, a working agent, and verifying traces in the LangSmith dashboard. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/litellm-agentkit-inbox-triage.mdx b/src/content/docs/agentkit/recipes/litellm-agentkit-inbox-triage.mdx similarity index 95% rename from src/content/docs/cookbooks/litellm-agentkit-inbox-triage.mdx rename to src/content/docs/agentkit/recipes/litellm-agentkit-inbox-triage.mdx index 5c752cd60..b02aa3392 100644 --- a/src/content/docs/cookbooks/litellm-agentkit-inbox-triage.mdx +++ b/src/content/docs/agentkit/recipes/litellm-agentkit-inbox-triage.mdx @@ -1,18 +1,10 @@ --- title: 'Triage a Gmail inbox with AgentKit and the LiteLLM gateway' description: 'Node.js inbox triage agent: classify Gmail threads, route to GitHub repos, draft issues and replies via LiteLLM, and approve before any side effects.' -date: 2026-05-06 sidebar: - label: 'Inbox triage + LiteLLM' + label: 'LiteLLM inbox triage' + order: 6 tableOfContents: true -excerpt: > - Poll Gmail with AgentKit tools, send each new thread through a multi-stage LiteLLM pipeline with per-stage models, notify Slack, then approve filing a GitHub issue and sending a reply from a localhost dashboard. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/livekit-agentkit-voice-tool-calling.mdx b/src/content/docs/agentkit/recipes/livekit-agentkit-voice-tool-calling.mdx similarity index 96% rename from src/content/docs/cookbooks/livekit-agentkit-voice-tool-calling.mdx rename to src/content/docs/agentkit/recipes/livekit-agentkit-voice-tool-calling.mdx index e59be1060..8c8a31396 100644 --- a/src/content/docs/cookbooks/livekit-agentkit-voice-tool-calling.mdx +++ b/src/content/docs/agentkit/recipes/livekit-agentkit-voice-tool-calling.mdx @@ -1,18 +1,10 @@ --- title: 'Build a LiveKit voice agent with Scalekit AgentKit tools' description: 'Give a LiveKit voice agent secure access to Google Calendar and 200+ AgentKit connectors — no token ever reaches the browser or the LLM.' -date: 2026-07-02 sidebar: - label: 'LiveKit voice agent' + label: 'Build a LiveKit agent' + order: 11 tableOfContents: true -excerpt: > - A voice agent that checks a calendar or sends an email needs an OAuth token for that API — and LiveKit's Agents framework has no opinion on where that token comes from. This cookbook wires Scalekit AgentKit into a LiveKit Node agent using direct tool calls, carrying the user's identity through LiveKit's own dispatch metadata so the token never reaches the browser or the LLM. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/mastra-agentkit.mdx b/src/content/docs/agentkit/recipes/mastra-agentkit.mdx similarity index 96% rename from src/content/docs/cookbooks/mastra-agentkit.mdx rename to src/content/docs/agentkit/recipes/mastra-agentkit.mdx index 2f4eea223..1d8018d19 100644 --- a/src/content/docs/cookbooks/mastra-agentkit.mdx +++ b/src/content/docs/agentkit/recipes/mastra-agentkit.mdx @@ -1,18 +1,10 @@ --- title: 'Build a Mastra agent with Scalekit AgentKit tools' description: 'Give a Mastra agent access to Gmail and 200+ connectors through Scalekit AgentKit — zero manual OAuth handling.' -date: 2026-05-19 sidebar: - label: 'Mastra AgentKit' + label: 'Build a Mastra agent' + order: 3 tableOfContents: true -excerpt: > - Mastra agents need tools. Each third-party API — Gmail, Slack, Calendar — means another OAuth flow, another token store, another refresh cycle. This recipe connects a Mastra agent to Scalekit AgentKit tools using the Node SDK, with automatic authorization and token refresh. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/render-github-pr-summarizer.mdx b/src/content/docs/agentkit/recipes/render-github-pr-summarizer.mdx similarity index 98% rename from src/content/docs/cookbooks/render-github-pr-summarizer.mdx rename to src/content/docs/agentkit/recipes/render-github-pr-summarizer.mdx index 6a986d660..9dd562e3a 100644 --- a/src/content/docs/cookbooks/render-github-pr-summarizer.mdx +++ b/src/content/docs/agentkit/recipes/render-github-pr-summarizer.mdx @@ -1,18 +1,10 @@ --- title: 'Build a multi-user GitHub PR summarizer agent' description: 'Build a GitHub PR summarizer that binds each connected GitHub account to a secure browser session instead of trusting a client-supplied user ID.' -date: 2026-04-11 sidebar: - label: 'GitHub PR summarizer' + label: 'Summarize GitHub PRs' + order: 9 tableOfContents: true -excerpt: > - Build a GitHub PR summarizer that ranks the most-discussed pull requests in a repository and writes plain-language summaries. The secure version of this recipe binds each GitHub connection to a server-side session and never accepts a user ID from the browser. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' --- import { Aside, Steps } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/schedule-meeting-and-draft-email.mdx b/src/content/docs/agentkit/recipes/schedule-meeting-and-draft-email.mdx similarity index 96% rename from src/content/docs/cookbooks/schedule-meeting-and-draft-email.mdx rename to src/content/docs/agentkit/recipes/schedule-meeting-and-draft-email.mdx index f57e1399e..dfe05c5d3 100644 --- a/src/content/docs/cookbooks/schedule-meeting-and-draft-email.mdx +++ b/src/content/docs/agentkit/recipes/schedule-meeting-and-draft-email.mdx @@ -1,15 +1,9 @@ --- title: 'Build an agent that books meetings and drafts emails' description: 'Connect a Python agent to Google Calendar and Gmail via Scalekit to find free slots, book meetings, and draft follow-up emails.' -date: 2026-03-06 -excerpt: > - Building a scheduling agent means coordinating authentication to two separate tools — Google Calendar and Gmail — then chaining their outputs in one workflow. Without managed OAuth, each connector requires its own token lifecycle and error-handling logic. This recipe shows how Scalekit handles auth per connector so your agent can focus on finding a free slot, creating the event, and drafting the confirmation. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' +sidebar: + label: 'Book meetings' + order: 8 --- import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; diff --git a/src/content/docs/cookbooks/set-up-agentkit-with-your-coding-agent.mdx b/src/content/docs/agentkit/recipes/set-up-agentkit-with-your-coding-agent.mdx similarity index 90% rename from src/content/docs/cookbooks/set-up-agentkit-with-your-coding-agent.mdx rename to src/content/docs/agentkit/recipes/set-up-agentkit-with-your-coding-agent.mdx index 51daa7d91..da0e814c3 100644 --- a/src/content/docs/cookbooks/set-up-agentkit-with-your-coding-agent.mdx +++ b/src/content/docs/agentkit/recipes/set-up-agentkit-with-your-coding-agent.mdx @@ -1,19 +1,9 @@ --- title: 'Set up AgentKit with your coding agent' description: 'Add Scalekit Agent Auth to your codebase using Claude Code, Codex, GitHub Copilot CLI, Cursor, or any of 40+ coding agents.' -date: 2026-04-15 sidebar: - label: 'Set up AgentKit with coding agents' -excerpt: > - Install the authstack plugin into your coding agent and paste one - prompt. The agent scaffolds OAuth handling, token management, and connected - account logic so you can start writing agent logic immediately. -featured: false -authors: - - name: 'Saif' - title: 'Developer Advocate' - url: 'https://www.linkedin.com/in/saif-shines/' - picture: '/images/blog/authors/saif.png' + label: 'Set up in a coding agent' + order: 2 --- import { TabItem, Aside } from '@astrojs/starlight/components' diff --git a/src/content/docs/authenticate/fsa/quickstart.mdx b/src/content/docs/authenticate/fsa/quickstart.mdx index 83a86b20c..1107354d4 100644 --- a/src/content/docs/authenticate/fsa/quickstart.mdx +++ b/src/content/docs/authenticate/fsa/quickstart.mdx @@ -4,8 +4,8 @@ description: "SaaSKit — Hosted auth pages, managed sessions, secure logout. Pu tags: [full-stack-auth, quickstart, hosted-login, authentication, users, organizations, sessions] tableOfContents: true next: - label: "Manage users" - link: "/fsa/data-modelling/" + label: "Keep building" + link: "/saaskit/recipes/" head: - tag: style content: | diff --git a/src/content/docs/authenticate/m2m/api-auth-quickstart.mdx b/src/content/docs/authenticate/m2m/api-auth-quickstart.mdx index 2a86ff306..fcc4734a9 100644 --- a/src/content/docs/authenticate/m2m/api-auth-quickstart.mdx +++ b/src/content/docs/authenticate/m2m/api-auth-quickstart.mdx @@ -28,7 +28,7 @@ import InstallSDK from '@components/templates/_installsdk.mdx'; APIs let your customers, partners, and external systems interact with your application and its data. You need authentication to ensure only authorized clients can consume your APIs. Scalekit helps you add OAuth 2.0-based client-credentials authentication to your API endpoints. -If you are new to JWT-based API authentication, read the cookbook **[M2M JWT verification with JWKS and OAuth scopes](/cookbooks/m2m-jwks-and-oauth-scopes/)** for foundational context before following the steps below. +If you are new to JWT-based API authentication, read the cookbook **[M2M JWT verification with JWKS and OAuth scopes](/saaskit/recipes/m2m-jwks-and-oauth-scopes/)** for foundational context before following the steps below. Here's how it works: @@ -294,7 +294,7 @@ Your App -> API Client: 7. Returns the protected resource - 1. **Retrieve the public key:** Fetch the appropriate public key from your Scalekit environment's [JSON Web Key Set (JWKS)](/cookbooks/m2m-jwks-and-oauth-scopes/#jwks-and-scalekit-keys) at `https:///keys`. Use the `kid` (Key ID) from the JWT header to identify the correct key. Cache the key according to standard JWKS practices. + 1. **Retrieve the public key:** Fetch the appropriate public key from your Scalekit environment's [JSON Web Key Set (JWKS)](/saaskit/recipes/m2m-jwks-and-oauth-scopes/#jwks-and-scalekit-keys) at `https:///keys`. Use the `kid` (Key ID) from the JWT header to identify the correct key. Cache the key according to standard JWKS practices. @@ -369,7 +369,7 @@ Your App -> API Client: 7. Returns the protected resource 5. ## Register API client's scopes - [OAuth scopes](/cookbooks/m2m-jwks-and-oauth-scopes/#oauth-scopes-for-machine-clients) are embedded in the access token and validated server-side using the Scalekit SDK. This ensures that API clients only access resources they're authorized for, adding an extra layer of security. + [OAuth scopes](/saaskit/recipes/m2m-jwks-and-oauth-scopes/#oauth-scopes-for-machine-clients) are embedded in the access token and validated server-side using the Scalekit SDK. This ensures that API clients only access resources they're authorized for, adding an extra layer of security. For example, you might create an API client for a customer's deployment service with scopes like `deploy:applications` and `read:deployments`. diff --git a/src/content/docs/cookbooks.mdx b/src/content/docs/cookbooks.mdx new file mode 100644 index 000000000..770562e89 --- /dev/null +++ b/src/content/docs/cookbooks.mdx @@ -0,0 +1,25 @@ +--- +title: 'Recipes' +description: 'Step-by-step recipes for building with Scalekit, split by product.' +template: splash +sidebar: + label: 'Recipes' +tableOfContents: false +--- + +import { CardGrid, LinkCard } from '@astrojs/starlight/components' + +Recipes are jobs in your app or agent. Each product keeps its own shelf, reached from that product's secondary nav **Keep building** entry. + + + + + diff --git a/src/content/docs/cookbooks/search-scalekit-docs-in-your-ide.mdx b/src/content/docs/cookbooks/search-scalekit-docs-in-your-ide.mdx deleted file mode 100644 index fc650982b..000000000 --- a/src/content/docs/cookbooks/search-scalekit-docs-in-your-ide.mdx +++ /dev/null @@ -1,193 +0,0 @@ ---- -title: 'Search Scalekit docs with ref.tools' -description: 'Configure ref.tools MCP to search Scalekit documentation directly from Cursor, Claude Code, or Windsurf without leaving your IDE.' -sidebar: - label: 'Search Scalekit docs in IDE' -date: 2026-03-26 -excerpt: > - Switching to a browser to look up Scalekit docs breaks your coding flow. - This cookbook shows you how to configure the ref.tools MCP server so your - AI coding assistant can search Scalekit documentation inline — in Cursor, - Claude Code, Windsurf, or any MCP-compatible client. -featured: false -authors: - - name: 'Saif' - title: 'Developer' ---- - -import { Aside, Steps } from '@astrojs/starlight/components'; - -Every time you need to look up a Scalekit API, scope name, or configuration option, you break your flow: open a new tab, search the docs, copy the answer, switch back. With ref.tools configured as an MCP server, your AI coding assistant can search Scalekit documentation inline and return accurate, up-to-date answers without you leaving the editor. Setup takes about two minutes. - -## The problem - -AI coding assistants are good at generating code, but they have two failure modes when it comes to third-party docs: - -- **Hallucination** — The model invents an API that doesn't exist or gets parameter names wrong because its training data is incomplete -- **Stale knowledge** — Even accurate training data goes out of date as SDKs and APIs evolve - -Both problems get worse when you're working with a narrowly scoped platform like Scalekit. The model may have seen very little training data about it, and what it did see may be outdated. - -The standard workaround is to paste docs into the chat manually — which means constant context-switching between your editor and a browser. ref.tools solves both problems by connecting your AI assistant directly to live Scalekit documentation through an MCP tool call. - -## Who needs this - -This cookbook is for you if: - -- ✅ You use Cursor, Claude Code, Windsurf, or another MCP-compatible AI assistant -- ✅ You're building with Scalekit (auth, SSO, MCP servers, M2M, SCIM) -- ✅ You want accurate, up-to-date answers without context-switching to a browser - -You **don't** need this if: - -- ❌ You prefer pasting docs into your chat manually -- ❌ Your AI assistant doesn't support MCP - -## The solution - -[ref.tools](https://ref.tools) is a documentation search platform that indexes third-party docs — including Scalekit — and exposes them as an MCP tool called `ref_search_documentation`. Once you add the ref.tools MCP server to your AI assistant, you can prompt it to search Scalekit docs and it will call the tool and return current results directly in chat. - -The server supports two transports: - -- **Streamable HTTP** (recommended) — Direct HTTP connection using your API key; lower latency, no local process required -- **stdio** (legacy) — Runs a local `npx` process; works with any MCP client that supports stdio - -## Set up ref.tools - - -1. ### Get your API key - 1. Go to [ref.tools](https://ref.tools) and sign in - 2. Search for **Scalekit** to confirm the documentation source is indexed - 3. Open the **Quick Install** panel for Scalekit — your API key is pre-filled in the install commands - 4. Copy your API key; you'll use it in the next step - -2. ### Add the MCP server to your AI assistant - - Pick your tool and apply the matching configuration. - - #### Claude Code - - Run this command in your terminal to add the MCP server globally across all projects: - - ```bash - claude mcp add --transport http ref-context https://api.ref.tools/mcp \ - --header "x-ref-api-key: YOUR_API_KEY" - ``` - - To scope it to a single project instead, add `--scope project` to the command. - - #### Cursor - - Add the following to `.cursor/mcp.json` in your project root (or via **Settings → MCP**): - - ```json title=".cursor/mcp.json" - { - "ref-context": { - "type": "http", - "url": "https://api.ref.tools/mcp?apiKey=YOUR_API_KEY" - } - } - ``` - - #### Windsurf - - Add the following to `~/.codeium/windsurf/mcp_config.json`: - - ```json title="~/.codeium/windsurf/mcp_config.json" - { - "ref-context": { - "serverUrl": "https://api.ref.tools/mcp?apiKey=YOUR_API_KEY" - } - } - ``` - - #### Other (stdio) - - For any MCP client that supports stdio, add to your MCP config: - - ```json title="mcp.json" - { - "ref-context": { - "command": "npx", - "args": ["ref-tools-mcp@latest"], - "env": { - "REF_API_KEY": "YOUR_API_KEY" - } - } - } - ``` - - This requires Node.js installed locally. The `npx` command fetches and runs the server on first use. - -3. ### Verify it's working - 1. Restart your AI assistant (or use its MCP reload command if available) - 2. Open a new chat and send this prompt: - ``` - Use ref to look up how to add OAuth 2.1 authorization to an MCP server with Scalekit - ``` - 3. Your assistant should call the `ref_search_documentation` tool and return results from `docs.scalekit.com` - - If the tool doesn't appear, check that you restarted the assistant after saving the config, and that the API key is correct. - - - - -## Example searches to try - -Once ref.tools is connected, use phrases like "use ref to..." or "look up in ref..." to trigger the tool explicitly: - -- `Use ref to find the Scalekit MCP auth quickstart` -- `Look up how to configure SSO with Scalekit` -- `Use ref to find Scalekit M2M token documentation` -- `Search Scalekit docs for SCIM provisioning setup` -- `Use ref to look up Scalekit SDK environment variables` - -You can also just ask naturally — most assistants will call the tool automatically when the question is about Scalekit. - -## Common mistakes - -
-API key committed to git - -- **Symptom**: Your key appears in git history or a public repository -- **Cause**: Config file with the key inline was committed -- **Fix**: Use an environment variable (`$REF_API_KEY`) and add the config file to `.gitignore` if it contains real credentials - -
- -
-Wrong transport for your client - -- **Symptom**: MCP server fails to connect or appears as disconnected -- **Cause**: Some clients only support stdio; others support both HTTP and stdio -- **Fix**: Check your client's MCP documentation. Cursor and Claude Code support streamable HTTP. Older or less common clients may require stdio. - -
- -
-Server name not matching what the client expects - -- **Symptom**: Tool calls fail with "unknown tool" or the server doesn't appear in the tool list -- **Cause**: The config key (e.g., `ref-context`) doesn't match what you reference in prompts, or the client uses a different config field name -- **Fix**: Confirm the key in your config file matches the server name shown in your client's MCP settings panel - -
- -
-Tool not appearing after config change - -- **Symptom**: You updated the config but the `ref_search_documentation` tool isn't available -- **Cause**: The MCP connection wasn't refreshed -- **Fix**: Fully restart your AI assistant, or use its MCP reload command (Claude Code: `claude mcp list` to verify; Cursor: reload the window) - -
- -## Next steps - -For further setup, authentication options, and available documentation sources, see the links below. - -- [Add OAuth 2.1 authorization to MCP servers](/authenticate/mcp/quickstart) — the most common thing developers look up using ref -- [ref.tools](https://ref.tools) — browse all available documentation sources you can add alongside Scalekit -- [M2M authentication overview](/guides/m2m/overview) — machine-to-machine auth patterns frequently searched via ref diff --git a/src/content/docs/dev-kit/build-with-ai/index.mdx b/src/content/docs/dev-kit/build-with-ai/index.mdx index 6d108565b..46933b0f7 100644 --- a/src/content/docs/dev-kit/build-with-ai/index.mdx +++ b/src/content/docs/dev-kit/build-with-ai/index.mdx @@ -46,7 +46,7 @@ Done: the command exits 0 and the skill you need is available. + +1. ## Open Edit role + + In the header, click the **workspace name** and choose **Team Members**. + + Open the member's row menu and choose **Edit role**. + +2. ## Confirm the workspace default + + **Workspace role** is the default dashboard role. It applies across every environment unless you override it below. + + Leave this set to the role they should have on most environments. + +3. ## Override one environment + + In **Environment access**, each row shows **Environment**, **Effective role**, and **Source**. + + | Source | Meaning | + | ------ | ------- | + | Workspace Default | Uses the workspace role above | + | Overridden | This environment uses a different dashboard role | + + Choose **Set override** on the environment. Select the dashboard role for that environment, or **No access** to hide it. + + Repeat for each environment that should differ from the default. + + + +4. ## Save + + Click **Save**. + + Tell the teammate which environment to open. They switch with the environment name in the header (right of the workspace name). + +
+ +## Reset an override + +In **Environment access**, choose **Reset to Default** on that environment. + +The row source returns to **Workspace Default**. The member uses the workspace role again on that environment. + +## Verify + +1. Sign in as the member +2. Switch to the granted environment and confirm settings load +3. Switch to an environment set to **No access** and confirm it is blocked or hidden +4. Re-open **Edit role** and confirm **Source** is **Overridden** on the environments you changed + +## Common questions + +
+Do application roles follow the same per-environment switch? + +No. This page is about Scalekit dashboard access. Application roles for your product's users are not scoped here. + +
+ +
+Why is Staging not in the list? + +Scalekit environment types are **Development** and **Production**. Create extra environments of either type from **Workspace > Environments** if you need a separate test stack. + +
diff --git a/src/content/docs/how-to/custom-domain-on-development.mdx b/src/content/docs/how-to/custom-domain-on-development.mdx new file mode 100644 index 000000000..a8c8638d2 --- /dev/null +++ b/src/content/docs/how-to/custom-domain-on-development.mdx @@ -0,0 +1,95 @@ +--- +title: 'Add a custom domain on development' +description: 'Development environments use the assigned Scalekit URL. Custom domains require Production and Customization Pro.' +tableOfContents: true +sidebar: + label: 'Add a dev domain' + order: 5 +head: + - tag: style + content: | + .sl-markdown-content h2 { font-size: var(--sl-text-xl); } + .sl-markdown-content h3 { font-size: var(--sl-text-lg); } +--- + +import { Steps, Aside } from '@astrojs/starlight/components' + +A Development environment cannot use a branded custom domain. **Configure Custom Domain** stays disabled, with the hint: custom domains are available on **Production** after you upgrade to the **Customization Pro** add-on. + +Use the assigned Development environment URL for local work. When you need `auth.yourapp.com`, switch to Production and follow [Branded custom domains](/guides/custom-domain/). + +## Before you start + +- You can open the environment (**Development** or **Production**) from the header switcher +- For a branded hostname, you can edit DNS for a domain you own +- Customization Pro is billed at **$99/month** and includes custom domains, watermark removal, custom email provider, and custom email templates + + + + + +1. ## Open the Development environment + + In the header, click the **environment name** (right of the workspace name). + + Choose the Development environment you use for local work. + +2. ## Open Custom Domain + + In the left nav, under **Customize**, click **Custom Domain**. + + The page shows **Custom Domain Add-on**, the **Environment URL**, and a **Live** badge. + +3. ## Copy the Development environment URL + + Copy the **Environment URL** (for example `scalekit-xxxxx-dev.scalekit.cloud`). + + Use that host in Development redirect URLs, SDK `environmentUrl`, and local tests. See [Manage environments](/how-to/environments/). + + **Configure Custom Domain** is disabled on Development. Hover it to read the Production + Customization Pro requirement. + +4. ## Configure a branded domain on Production + + Switch the header to a **Production** environment. + + Open **Customize > Custom Domain** and click **Configure Custom Domain**. + + If Customization Pro is not on this environment, Scalekit opens the **Customization Pro Add-on $99/month** dialog. Click **Upgrade**, then continue with the CNAME steps in [Branded custom domains](/guides/custom-domain/). + + + +## Verify + +**Development** + +1. Open the **Environment URL** in a browser +2. Confirm the hosted login or admin page loads over HTTPS +3. Confirm Development redirect URLs use that host, not a production hostname + +**Production (after upgrade)** + +1. **Configure Custom Domain** is enabled +2. DNS verifies and the branded host serves HTTPS +3. Production redirect URLs use the branded host + + + +## Common questions + +
+Can I attach auth.dev.yourapp.com to Development? + +Not through **Custom Domain**. That page only enables CNAME on Production with Customization Pro. Keep Development on the assigned `*-dev.scalekit.cloud` URL. + +
+ +
+Can I reuse the production hostname in Development? + +No. Use the Development environment URL so cookies and redirect URLs never collide with Production. + +
diff --git a/src/content/docs/how-to/define-custom-dashboard-roles.mdx b/src/content/docs/how-to/define-custom-dashboard-roles.mdx new file mode 100644 index 000000000..985105c67 --- /dev/null +++ b/src/content/docs/how-to/define-custom-dashboard-roles.mdx @@ -0,0 +1,125 @@ +--- +title: 'Define custom dashboard roles' +description: 'Create a custom dashboard role and pick which Scalekit dashboard permissions it grants.' +tableOfContents: true +sidebar: + label: 'Define dashboard roles' + order: 2 +head: + - tag: style + content: | + .sl-markdown-content h2 { font-size: var(--sl-text-xl); } + .sl-markdown-content h3 { font-size: var(--sl-text-lg); } +--- + +import { Steps, Aside } from '@astrojs/starlight/components' + +Custom dashboard roles grant access to specific Scalekit dashboard areas. Use one when **Admin** is too broad and a fixed role such as **Member** or **Developer** is too narrow. + +These permissions apply to the dashboard, not to users in your application. Application roles stay in [Create roles and permissions](/authenticate/authz/create-roles-permissions/). + +## Before you start + +- **Roles** appears under **Workspace** in workspace settings, between **Team Members** and **Billing** +- You hold `dashboard_roles:write` (and you can only grant permissions you already have) +- You know which dashboard areas the job must open + + + +## Permission groups + +The role drawer groups dashboard permissions. Typical groups: + +| Area | What it gates | +| ---- | ------------- | +| Workspace | Workspace name and settings | +| Members | **Team Members** invites and removals | +| Dashboard roles | Creating and editing dashboard roles | +| Environment access | Per-environment role overrides | +| Billing | Plan, invoices, and payment method | +| Environments | Creating and renaming environments | +| Environment settings | Auth methods, session policy, and related env settings | +| Branding, emails, custom domain | **Customize** pages | +| Applications and API credentials | **Applications** and client secrets | +| Organizations, users, SSO, SCIM | Customer-tenant configuration | +| Webhooks, interceptors, logs | Developer tooling | + +Permissions marked **Sensitive** cover irreversible actions, live credentials, or the ability to grant access to others. + + + +1. ## Open Roles + + In the header, click the **workspace name** and open workspace settings. + + In the workspace left nav, click **Roles**. + + The table lists **Name**, **Permissions**, **Description**, and **Type**. **Fixed** roles ship with Scalekit. **Custom** roles are ones you created. + +2. ## Create a role + + Click **Create role**. + + Enter a **Role name** (50 characters or fewer). Name it after the job, not the person — for example **Billing Owner** or **Support**. + + Add a **Description** so the next Admin knows when to assign it. + +3. ## Start from a preset (optional) + + In **Start from**, pick a preset if one matches the job: + + | Preset | Use when | + | ------ | -------- | + | Member floor | Baseline read access | + | Developer | Apps, credentials, and env configuration | + | Auditor | Read-only review | + | Support | Helping customers without billing or role admin | + | Billing Owner | Plan and invoices | + | Designer | Branding and emails | + + A preset is a starting point. You can change any permission after you pick one. + +4. ## Choose permissions + + Enable only the permissions this job needs. Use **Filter permissions**, **Select all** on a group, or **Clear all**. + + + + + +5. ## Save and assign + + Click **Save changes**. + + Open **Team Members**, choose **Edit role** on the member, select the new role under **Workspace role**, and click **Save**. See [Set up workspace roles](/how-to/set-up-workspace-roles/). + + + +## Verify + +1. Open **Roles** and confirm the new row shows **Custom** and the permission count +2. Assign the role to a test member +3. Sign in as that member and confirm allowed pages load +4. Confirm a denied page shows an access error instead of the setting + +## Common questions + +
+Can I delete a custom role? + +Open the role's **Role actions** menu and choose **Delete**. You cannot delete a role that is still assigned. Reassign those members first. Fixed roles cannot be deleted. + +
+ +
+Does this role apply inside my application? + +No. Dashboard roles only gate `app.scalekit.com`. Application roles for your users live in left-nav **Roles & Permissions** and in [Create roles and permissions](/authenticate/authz/create-roles-permissions/). + +
diff --git a/src/content/docs/how-to/delete-your-account.mdx b/src/content/docs/how-to/delete-your-account.mdx new file mode 100644 index 000000000..343ab99d3 --- /dev/null +++ b/src/content/docs/how-to/delete-your-account.mdx @@ -0,0 +1,88 @@ +--- +title: 'Delete your Scalekit account' +description: 'Remove your Scalekit dashboard login. The dashboard has no self-serve delete-account control.' +tableOfContents: true +sidebar: + label: 'Delete your account' + order: 4 + hidden: true +head: + - tag: style + content: | + .sl-markdown-content h2 { font-size: var(--sl-text-xl); } + .sl-markdown-content h3 { font-size: var(--sl-text-lg); } +--- + +import { Steps, Aside } from '@astrojs/starlight/components' + +The Scalekit dashboard does not include a **Delete account** action. Profile settings only edit your name and passkeys. Avatar menu options are **Profile Settings** and **Logout**. + +To remove your login and workspace data, email Scalekit. To leave one workspace and keep the login, ask an Admin to remove you. + +This page is about **your** Scalekit dashboard login. To delete users inside your application, use [Delete users and organizations](/authenticate/manage-users-orgs/delete-users-and-organizations/). + +## Before you start + +- You are signed in to the account you want removed +- You have exported anything you still need: client IDs, redirect URLs, connection configs +- Another Admin can keep the workspace if you should not remain a member + + + + + +1. ## Decide what you need removed + + | Goal | What to do | + | ---- | ---------- | + | Leave one workspace | Ask an Admin to **Remove Member** on **Team Members** | + | Sign out only | Open your avatar menu (initials in the header) and choose **Logout** | + | Delete the Scalekit login | Email support (next step) | + +2. ## Export workspace data you still need + + Record environment URLs, client IDs, and redirect URLs from each environment you use. Do not export client secrets. After the login is deleted you cannot open the dashboard. + +3. ## Email Scalekit support + + Write to [support@scalekit.com](mailto:support@scalekit.com) from the same address as the account. + + Include: + + - The email on the account + - The workspace name + - Whether you also want the workspace deleted (only if you are the last Admin) + + Support confirms when the login is gone. + + + +## Verify + +- Signing in with the same email fails or starts a new signup +- Teammates no longer see you on **Workspace > Team Members** +- If you asked to delete the workspace, the workspace URL no longer loads + +## Common questions + +
+I only want to leave one workspace + +Ask an Admin to open **Team Members**, open your row menu, and choose **Remove Member**. That removes this workspace only. Your Scalekit login can still join other workspaces. + +
+ +
+Can I delete the account from Profile? + +No. **Profile** (avatar menu → **Profile Settings**, or **My account > Profile**) lets you edit first name, last name, and passkeys. It does not delete the account. + +
+ +
+I need a workspace deleted but I am not the last Admin + +Ask an Admin to remove the workspace, or include that request when you write to [support@scalekit.com](mailto:support@scalekit.com). +
diff --git a/src/content/docs/dev-kit/guides/dashboard/environments.mdx b/src/content/docs/how-to/environments.mdx similarity index 98% rename from src/content/docs/dev-kit/guides/dashboard/environments.mdx rename to src/content/docs/how-to/environments.mdx index eeb30a5d5..cb884fe1a 100644 --- a/src/content/docs/dev-kit/guides/dashboard/environments.mdx +++ b/src/content/docs/how-to/environments.mdx @@ -4,6 +4,8 @@ description: Configure development, staging, and production environments, and ch tableOfContents: true sidebar: label: Manage environments + order: 6 + hidden: true head: - tag: style content: | @@ -69,7 +71,7 @@ EU data residency is a **$99/month add-on per production environment**, billed s Other per-environment add-ons, such as **Customization Pro** (custom domain, branding removal, custom email templates), are also billed per region. Running Customization Pro on both a US and an EU production environment bills the add-on twice. -Add a payment method under **Workspace Settings → Billing** in the EU workspace so the add-on and usage-based charges succeed. See [Billing and usage](/dev-kit/guides/dashboard/billing/) for payment methods and plan details. +Add a payment method under **Workspace Settings → Billing** in the EU workspace so the add-on and usage-based charges succeed. See [Billing and usage](/how-to/billing/) for payment methods and plan details. ## Access environment settings diff --git a/src/content/docs/dev-kit/guides/dashboard/manage-team-members.mdx b/src/content/docs/how-to/manage-team-members.mdx similarity index 86% rename from src/content/docs/dev-kit/guides/dashboard/manage-team-members.mdx rename to src/content/docs/how-to/manage-team-members.mdx index 3a27881bc..9f90faad6 100644 --- a/src/content/docs/dev-kit/guides/dashboard/manage-team-members.mdx +++ b/src/content/docs/how-to/manage-team-members.mdx @@ -4,6 +4,7 @@ description: Invite team members to your Scalekit organization and manage their tableOfContents: true sidebar: label: Manage team members + order: 7 head: - tag: style content: | @@ -29,13 +30,16 @@ Navigate to **Dashboard > Settings > Team** to view and manage team members. ## Team member roles -Scalekit supports two roles with different permission levels: +Workspace roles control who can open the Scalekit dashboard. They do not control users inside your application. | Role | Permissions | | ---- | ----------- | +| **Admin** | Full dashboard access for the workspace, including billing and team management. | | **Owner** | Full access to all settings, billing, and team management. Can invite and remove members. | | **Member** | View and manage authentication configurations, but cannot access billing or remove other members. | +A workspace can also use a role you define. See [Set up workspace roles](/how-to/set-up-workspace-roles/) and [Define custom dashboard roles](/how-to/define-custom-dashboard-roles/). +