Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added adk/assets/cursor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified adk/assets/intro-new-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified adk/assets/intro-new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions adk/quickstart-ai-assistant.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
title: Quickstart with Cursor
description: Use Cursor to build your first ADK agent.
---

Install the ADK CLI, give Cursor ADK knowledge with skills, then let it build your agent from a simple prompt.

<Frame>
<img alt="Cursor building an ADK agent" src="./assets/cursor.png" />
</Frame>

## Install the CLI

<CodeGroup>
```bash macOS & Linux
curl -fsSL https://github.com/botpress/adk/releases/latest/download/install.sh | bash
```

```powershell Windows (PowerShell)
powershell -c "irm https://github.com/botpress/adk/releases/latest/download/install.ps1 | iex"
```
</CodeGroup>

## Install ADK skills

Skills give Cursor deep knowledge of ADK conventions, primitives, and CLI commands:

```bash
bunx skills add botpress/skills -s '*' -a cursor -y
```

## Build your agent

Open your project in Cursor, use the chat panel (<kbd>Cmd</kbd> + <kbd>L</kbd>), and give it a prompt:

```
Initialize a simple ADK hello world bot for me.
```

Cursor will scaffold the project and write the agent code. Once it's done, run these commands from the project folder:

```bash
adk link # connects your agent to Botpress (opens a browser to log in)
adk dev # starts the dev console
```

Open the dev console and start chatting with your agent:

<Frame>
<img alt="ADK dev console chat" className="block dark:hidden" src="./assets/quickstart.png" />
<img alt="ADK dev console chat" className="hidden dark:block" src="./assets/quickstart-dark.png" />
</Frame>

## Add more features

Keep prompting Cursor to extend your bot. For example:

```
Add a tool that looks up the weather for a given city.
```

Chat with your bot in the dev console to try out your changes.

## Deploy your agent

When you're ready to share your bot, deploy it to Botpress Cloud:

```bash
adk deploy
```

---

<Card title="Agent configuration" icon="settings" href="/adk/setup/configuration">
Learn about project structure, models, state, and dependencies.
</Card>
142 changes: 114 additions & 28 deletions assistant.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,84 @@
return /\/adk\/.+/.test(window.location.pathname)
}

function botUrlForCurrentRoute() {
return isAdkRoute() ? ADK_BOT_URL : DEFAULT_BOT_URL
}

const iframe = document.createElement('iframe')
iframe.title = 'Botpress'
iframe.style.width = '100%'
iframe.style.height = '100%'
iframe.src = botUrlForCurrentRoute()
iframe.style.position = 'absolute'
iframe.style.top = '0'
iframe.style.left = '0'
iframe.allow = 'clipboard-write'
iframe.src = DEFAULT_BOT_URL

const adkIframe = document.createElement('iframe')
adkIframe.title = 'Botpress ADK'
adkIframe.style.width = '100%'
adkIframe.style.height = '100%'
adkIframe.style.position = 'absolute'
adkIframe.style.top = '0'
adkIframe.style.left = '0'
adkIframe.allow = 'clipboard-write'
adkIframe.src = ADK_BOT_URL

// "ready" means the React app inside the iframe has mounted and rendered its
// first frame. We detect this via the `requestTheme` postMessage the frontend
// sends from a useEffect on mount — that fires after the first paint, not just
// after the HTML document loads. The `load` event fires too early (HTML loaded
// but React not yet mounted), so using it directly causes a blank-white flash.
let defaultReady = false
let adkReady = false

function markReady(isAdk) {
if (isAdk) adkReady = true
else defaultReady = true
showActiveIframe()
}

// Fallback: if the iframe never sends requestTheme (e.g. default docs bot
// doesn't have the hook), mark ready 600ms after the HTML load event so the
// panel isn't stuck hidden forever.
iframe.addEventListener('load', () => {
setTimeout(() => { if (!defaultReady) markReady(false) }, 600)
})
adkIframe.addEventListener('load', () => {
setTimeout(() => { if (!adkReady) markReady(true) }, 600)
})

function showActiveIframe() {
const adk = isAdkRoute()
const target = adk ? adkIframe : iframe
const other = adk ? iframe : adkIframe
const ready = adk ? adkReady : defaultReady

if (ready) {
// Bring the active bot to the front (z-index swap, no repaint = no flash).
target.style.zIndex = '2'
target.style.pointerEvents = 'auto'
other.style.zIndex = '0'
other.style.pointerEvents = 'none'
}
// If target isn't ready yet, leave both layers as-is — 'other' keeps its
// current z-index so the panel stays populated. markReady() fires again
// once the target's React app has mounted.
}

function getActiveIframe() {
return isAdkRoute() ? adkIframe : iframe
}

// Default starts above ADK so non-ADK pages show the correct bot before
// either React app sends its first readiness signal (requestTheme).
iframe.style.zIndex = '1'
iframe.style.pointerEvents = 'none'
adkIframe.style.zIndex = '0'
adkIframe.style.pointerEvents = 'none'

botContainer.style.position = 'relative'
botContainer.appendChild(iframe)
botContainer.appendChild(adkIframe)
showActiveIframe()

panel.appendChild(mobileDismiss)
resizeHandle.appendChild(toggleCloseButton)
Expand Down Expand Up @@ -116,7 +182,7 @@
}

function focusComposerInput() {
const iframe = document.querySelector('iframe[title="Botpress"]')
const iframe = getActiveIframe()
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
Expand All @@ -128,7 +194,7 @@
}

function sendPanelOpenedMessage() {
const iframe = document.querySelector('iframe[title="Botpress"]')
const iframe = getActiveIframe()
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
Expand Down Expand Up @@ -384,20 +450,27 @@
// light/dark. Mintlify sets `class="dark"` on <html> in dark mode.
if (event.data.type === 'requestTheme') {
const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
const iframe = document.querySelector('iframe[title="Botpress"]')
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({ type: 'themeChanged', theme }, '*')
// The iframe's React app just mounted — mark it as visually ready so we
// can show it without a blank-white flash.
if (event.source === adkIframe.contentWindow) {
if (!adkReady) markReady(true)
} else if (event.source === iframe.contentWindow) {
if (!defaultReady) markReady(false)
}
// Respond with the current theme to whichever iframe asked.
if (event.source && typeof event.source.postMessage === 'function') {
try { event.source.postMessage({ type: 'themeChanged', theme }, '*') } catch (_e) {}
}
}
})

// Watch for docs theme toggles and forward to the iframe.
// Watch for docs theme toggles and forward to both iframes so they stay in
// sync regardless of which one is currently visible.
const themeObserver = new MutationObserver(() => {
const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
const iframe = document.querySelector('iframe[title="Botpress"]')
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({ type: 'themeChanged', theme }, '*')
}
;[iframe, adkIframe].forEach(f => {
if (f && f.contentWindow) f.contentWindow.postMessage({ type: 'themeChanged', theme }, '*')
})
})
themeObserver.observe(document.documentElement, {
attributes: true,
Expand All @@ -421,6 +494,26 @@
window.addEventListener('hashchange', handleHashChange)
updateOverlay()

// Card/button link clicks trigger Mintlify's page transition animation which
// causes the iframe to flash. Sidebar links don't trigger this. Hide the
// panel for the duration of the transition to prevent the flash.
let hideTimer = null
document.addEventListener('click', (e) => {
const link = e.target.closest('a')
if (!link) return
const href = link.getAttribute('href')
if (!href || href.startsWith('#') || href.startsWith('http') || href.startsWith('mailto')) return
if (panel.classList.contains('bot-panel-expanded')) return
if (hideTimer) clearTimeout(hideTimer)
panel.style.visibility = 'hidden'
toggleButton.style.visibility = 'hidden'
hideTimer = setTimeout(() => {
panel.style.visibility = ''
toggleButton.style.visibility = ''
hideTimer = null
}, 400)
}, true)

let resizeTimeout
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout)
Expand Down Expand Up @@ -448,20 +541,13 @@
if (window.location.pathname !== lastPath) {
lastPath = window.location.pathname

// Swap the bot iframe when we cross the ADK boundary. Stripping the
// src triggers a full reload, so the agent's conversation resets —
// intentional, since we're switching to a different agent entirely.
const targetBotUrl = botUrlForCurrentRoute()
const iframeEl = document.querySelector('iframe[title="Botpress"]')
if (iframeEl && iframeEl.src !== targetBotUrl) {
iframeEl.src = targetBotUrl
}
showActiveIframe()

const isExpanded = panel.classList.contains('bot-panel-expanded')
if (isExpanded) {
const iframe = document.querySelector('iframe[title="Botpress"]')
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
const activeIframe = isAdkRoute() ? adkIframe : iframe
if (activeIframe && activeIframe.contentWindow) {
activeIframe.contentWindow.postMessage(
{
type: 'pageChanged',
data: {
Expand Down Expand Up @@ -561,7 +647,7 @@
if (toggleButton) {
toggleButton.classList.add('bot-toggle-expanded')
}
const iframe = document.querySelector('iframe[title="Botpress"]')
const iframe = getActiveIframe()
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
Expand Down Expand Up @@ -699,7 +785,7 @@
}
}

const iframe = document.querySelector('iframe[title="Botpress"]')
const iframe = getActiveIframe()
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
Expand Down Expand Up @@ -769,7 +855,7 @@
}
}

const iframe = document.querySelector('iframe[title="Botpress"]')
const iframe = getActiveIframe()
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
Expand Down
3 changes: 2 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@
"group": "Get started",
"pages": [
"/adk/introduction",
"/adk/quickstart"
"/adk/quickstart",
"/adk/quickstart-ai-assistant"
]
},
{
Expand Down
Loading