Files
supabase/apps/docs/data/monitoring-agents.utils.ts
Saxon Fletcher 36d2982af4 docs: add reusable monitoring agent setup components (#49506)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Stack

Draft stack extracted from `docs/monitoring`. Merge bottom-up.
Troubleshooting / debugging-guide rewrite is out of scope.

1. #49503 move inspect and advisors
2. #49501 split Studio logs from ClickHouse queries
3. #49500 treat reports as signal dashboards
4. #49502 add Observe the data hub
5. **#49506** add agent setup components ← **this PR**
6. #49504 add hire-an-agent templates
7. #49505 restructure observability nav and overview

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs app feature (MDX components + markdown export). Fifth layer in the
observability stack.

## What is the current behavior?

There is no shared way to render a monitoring agent prompt, schedule,
and Claude/Codex/Cursor setup instructions in both HTML and generated
markdown.

## What is the new behavior?

- `AgentSetup` and `AgentWatchSchedule` MDX components, registered for
HTML and markdown export
- Shared `monitoring-agents` data (cadence, prompt ids, harness steps)
- Opt-in `AiPrompt` markdown export (`includeInMarkdown`) so quickstarts
stay HTML-only
- Optional content-listing `subtitle` for schedule labels on cards

No agent guide pages yet — those land in #49504 so this PR stays a
reviewable code change.

## Additional context

Markdown schema handlers share the same data module as the React
components.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-a3cb5ece-925b-4046-b58a-5d69e9a9d794?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-a3cb5ece-925b-4046-b58a-5d69e9a9d794&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saxon Fletcher <SaxonF@users.noreply.github.com>
2026-09-04 13:38:37 +10:00

140 lines
4.7 KiB
TypeScript

import { aiPrompts } from './ai-prompts.data'
import {
monitoringAgents,
type MonitoringAgent,
type MonitoringAgentId,
} from './monitoring-agents.data'
export type MonitoringAgentHarnessKey = 'claude' | 'codex' | 'cursor'
export type ScheduleMark = {
key: string
label?: string
}
export type MonitoringAgentHarnessSetup = {
key: MonitoringAgentHarnessKey
label: string
icon: 'claude' | 'openai' | 'cursor'
hasDistinctDarkIcon?: boolean
docsUrl: string
intro: string
steps: string[]
note?: string
}
const WEEKDAY_LABELS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const
const DAY_LABELS = ['12am', '6am', '12pm', '6pm'] as const
const MCP_STEP =
'Connect the [Supabase MCP server](/docs/guides/ai-tools/mcp) with `project_ref` and `read_only=true`.'
export function getMonitoringAgent(id: string): MonitoringAgent {
const agent = monitoringAgents[id as MonitoringAgentId]
if (!agent) {
throw new Error(`Unknown monitoring agent id: ${id}`)
}
return agent
}
export function getMonitoringAgentPrompt(agent: MonitoringAgent): string {
const prompt = aiPrompts[agent.promptId]
if (!prompt) {
throw new Error(`Unknown AiPrompt id: ${agent.promptId}`)
}
return prompt
}
export function getCronExpression(intervalMinutes: number): string {
if (intervalMinutes === 15) return '*/15 * * * *'
if (intervalMinutes === 60) return '0 * * * *'
if (intervalMinutes === 1440) return '0 9 * * *'
throw new Error(`Unsupported monitoring agent interval: ${intervalMinutes}`)
}
export function getScheduleMarks(intervalMinutes: number): {
window: 'day' | 'week'
marks: ScheduleMark[]
} {
if (intervalMinutes >= 24 * 60) {
return {
window: 'week',
marks: WEEKDAY_LABELS.map((label) => ({ key: label, label })),
}
}
const marksPerDay = (24 * 60) / intervalMinutes
if (!Number.isInteger(marksPerDay)) {
throw new Error(`intervalMinutes must divide 1440 evenly. Received: ${intervalMinutes}`)
}
const labelEvery = marksPerDay / 4
const marks = Array.from({ length: marksPerDay }, (_, index) => ({
key: String(index),
label: index % labelEvery === 0 ? DAY_LABELS[index / labelEvery] : undefined,
}))
return { window: 'day', marks }
}
export function getScheduleLabel(agent: MonitoringAgent): string {
const cadence = agent.schedule.cadence
return cadence.charAt(0).toUpperCase() + cadence.slice(1)
}
export function getMonitoringAgentHarnesses(agent: MonitoringAgent): MonitoringAgentHarnessSetup[] {
const cron = getCronExpression(agent.schedule.intervalMinutes)
const cadence = agent.schedule.cadence
const isSubHourly = agent.schedule.intervalMinutes < 60
return [
{
key: 'claude',
label: 'Claude',
icon: 'claude',
docsUrl: isSubHourly
? 'https://code.claude.com/docs/en/desktop-scheduled-tasks'
: 'https://code.claude.com/docs/en/routines',
intro: isSubHourly
? `Create a Claude Desktop scheduled task that runs ${agent.name} ${cadence}.`
: `Create a Claude routine that runs ${agent.name} ${cadence}.`,
steps: [
MCP_STEP,
isSubHourly
? 'In the Claude Code Desktop app, open **Routines**, click **New routine**, and choose **Local**.'
: 'Open [Claude routines](https://claude.ai/code/routines) or run `/schedule` in Claude Code.',
`Name it ${agent.name}. Paste the prompt. Set the schedule to ${cadence}.`,
],
note: isSubHourly
? 'Cloud routines have a 1-hour minimum. Use a [Desktop scheduled task](https://code.claude.com/docs/en/desktop-scheduled-tasks) for this cadence.'
: undefined,
},
{
key: 'codex',
label: 'Codex',
icon: 'openai',
hasDistinctDarkIcon: true,
docsUrl: 'https://developers.openai.com/codex/app/automations',
intro: `Create a Codex scheduled task that runs ${agent.name} ${cadence}.`,
steps: [
MCP_STEP,
'Open **Scheduled** in the ChatGPT desktop app, or ask Codex to create a standalone scheduled task.',
`Name it ${agent.name}. Paste the prompt. Set the schedule to ${cadence}. Each run should start a new chat.`,
],
},
{
key: 'cursor',
label: 'Cursor',
icon: 'cursor',
hasDistinctDarkIcon: true,
docsUrl: 'https://cursor.com/docs/cloud-agent/automations',
intro: `Create a Cursor automation that runs ${agent.name} ${cadence}.`,
steps: [
MCP_STEP,
'Create an automation in the Agents Window, at [cursor.com/automations](https://cursor.com/automations), or with the `/automate` skill.',
`Name it ${agent.name}. Use a scheduled trigger (${cadence}, cron \`${cron}\`). Paste the prompt. Keep the agent read-only, with no repository.`,
],
},
]
}