This commit is contained in:
ray zhou
2026-05-29 11:21:40 +08:00
parent 5d6d482efe
commit f71a5c59af
447 changed files with 32245 additions and 116 deletions

View File

@@ -0,0 +1,48 @@
{
"version": 1,
"skills": {
"babysit": {
"lastSyncedAt": 1780024096159
},
"canvas": {
"lastSyncedAt": 1780024096159
},
"create-hook": {
"lastSyncedAt": 1780024096159
},
"create-rule": {
"lastSyncedAt": 1780024096159
},
"create-skill": {
"lastSyncedAt": 1780024096159
},
"create-subagent": {
"lastSyncedAt": 1780024096159
},
"sdk": {
"lastSyncedAt": 1780024096159
},
"migrate-to-skills": {
"lastSyncedAt": 1780024096159
},
"shell": {
"lastSyncedAt": 1780024096159
},
"split-to-prs": {
"lastSyncedAt": 1780024096159
},
"statusline": {
"lastSyncedAt": 1780024096159
},
"update-cli-config": {
"lastSyncedAt": 1780024096159
},
"update-cursor-settings": {
"lastSyncedAt": 1780024096159
},
"loop": {
"lastSyncedAt": 1780024096159
}
},
"lastInventoryAt": 1780014510207
}

View File

@@ -0,0 +1,14 @@
---
name: babysit
description: >-
Keep a PR merge-ready by triaging comments, resolving clear conflicts, and
fixing CI in a loop.
---
# Babysit PR
Your job is to get this PR to a merge-ready state.
Check PR status, comments, and latest CI and resolve any issues until the PR is ready to merge.
1. Merge conflicts: Intelligently resolve any merge conflicts, preserving the intent and correctness of changes on your branch and the base branch. If intents conflict, abort the merge and ask for clarification.
2. Comments: Review active unresolved comments (including Bugbot) and resolve change requests / bug reports where valid. When fetching GitHub comments, filter out resolved threads first. Read only each comment body and the minimum location/URL needed to act on it; do not read the entire JSON output or other unnecessary payload data. Carefully validate issues reported by Bugbot and only take action on those that are valid; explain when you disagree or are unsure.
3. CI: Fix CI issues caused by changes within this PR's scope. Never change CI checks/workflows just to make failures pass, or make unrelated code changes; if that would be required, report back instead. For merge-blocking failures that seem unrelated to this PR, check whether the branch is behind the base branch and merge latest changes, since another PR may have fixed them. Push scoped fixes and re-watch CI until mergeable + green + comments triaged.

View File

@@ -0,0 +1,106 @@
---
name: canvas
description: >-
A Cursor Canvas is a live React app that the user can open beside the chat.
You MUST use a canvas when the agent produces a standalone analytical artifact
— quantitative analyses, billing investigations, security audits, architecture
reviews, data-heavy content, timelines, charts, tables, interactive
explorations, repeatable tools, or any response that benefits from visual
layout. Especially prefer a canvas when presenting results from MCP tools
(Datadog, Databricks, Linear, Sentry, Slack, etc.) where the data is the
deliverable — render it in a rich canvas rather than dumping it into a
markdown table or code block. If you catch yourself about to write a markdown
table, stop and use a canvas instead. You MUST also read this skill whenever
you create, edit, or debug any .canvas.tsx file.
metadata:
surfaces:
- ide
---
A canvas is a single `.canvas.tsx` file the IDE compiles so the user can open it beside the chat. Follow the workflow below in order.
## Workflow
### 1. Decide whether to use a canvas
The trigger is **user intent**, not response shape. Ask: would the user benefit from viewing this output as its **own standalone artifact**, separate from the chat? If the output is a means to an end (a drafted message, a code fix, a dashboard in another tool), skip the canvas.
**Use a canvas when the agent produces new standalone analytical output:**
- Quantitative analyses and metrics breakdowns (e.g. "send 500 requests and tell me how many fail")
- Billing or account investigations that surface structured findings from database queries
- Security audits or architecture reviews with categorized findings
- Cross-system data analyses and overlap reports
- Structured data from MCP tools (Databricks, Datadog, etc.) where the data IS the deliverable
- Financial analyses, margin decompositions, usage trend reports
- Tables with more than a handful of rows that the user asked to see
**Do NOT use a canvas when:**
- The user asks for work in a **specific tool** — "create a Datadog dashboard" means give them a Datadog dashboard, not a canvas
- The user has a **specific deliverable** — "draft a support response", "fix this code", "make this PR"
- The user is **working within an existing artifact** — improving an HTML dashboard, editing an existing file
- The user is doing **targeted debugging** or active development, even if structured findings emerge along the way
- Short factual answers, one-off file edits, or quick clarifying questions
- MCP tools are queried as an **intermediate step** for a different deliverable (e.g. querying Stripe to draft a support reply)
### 2. Write the canvas
**Location.** Canvases live at `/Users/<user>/.cursor/projects/<workspace>/canvases/<name>.canvas.tsx`. The IDE only detects canvases written directly inside that exact directory — subfolders, alternate extensions, and other locations are not picked up. For a new canvas, always use the write file tool to create the `.canvas.tsx` file at that exact path; do not stop after telling the user the path or showing code in chat. Treat that managed `canvases/` directory as pre-provisioned by Cursor itself: write the canvas file directly there and do **not** spend turns creating the directory with `mkdir` or checking whether it exists before writing. Listing its contents for other purposes (e.g. checking for existing canvases) is fine. If you can't determine the workspace directory from absolute paths already in your environment (terminals, transcripts, recently-viewed files), list `~/.cursor/projects/` rather than guessing. Use a descriptive kebab-case filename ending in `.canvas.tsx`; preserve acronym capitalization and lowercase the rest.
**File rules:**
- Exactly one `.canvas.tsx` file per canvas. Never create helper files, style files, or supporting modules.
- Import **only** from `cursor/canvas`. No relative imports, no npm packages, no Node built-ins.
- Default-export the top-level component.
- Embed all data inline. **No `fetch()`, no network calls.**
**Never render empty states.** A canvas exists to show real content. If a section, chart, table, or component has no data to display, **omit it** — do not render it with placeholder text ("Add header here", "TODO", "Example"), a "No data" message, an empty array, zeroed rows, or an empty chart frame. If the entire canvas would be empty because you don't have the underlying data, do not produce a canvas — tell the user what's missing and ask for it instead.
**Label every plot.** Charts and tables must be self-describing — a reader looking at the canvas alone should know exactly what they're seeing. For every plot include:
- A title naming the **specific metric** (not "Metrics" — "API error rate by service").
- **Axis labels with units** on both axes (e.g. "Date", "Latency (ms)").
- A **legend** when more than one series is shown, with the exact series names from the source data.
- The **source and time range** in a small caption (e.g. "Source: Datadog · last 7 days"). If a value is a transformation (mean, p95, normalized, smoothed), say so in the label.
**Component discovery:** prefer built-in `cursor/canvas` components over hand-rolled markup. The full public surface (components, hooks, prop types, tokens) is declared in `~/.cursor/skills-cursor/canvas/sdk/index.d.ts` and its sibling `.d.ts` files — read them when you need exact exports, prop shapes, or hook signatures rather than guessing. Referencing an export that does not exist is the most common runtime error.
Apply the Canvas generation policy below as you write, and complete its pre-delivery self-check (section 6) before returning the canvas.
## Design guidance
Be creative. The SDK gives you expressive building blocks — use them in whatever combination best serves the content. But avoid slop: no gradients, no emojis, no box-shadows, no rainbow coloring. Cursor canvases are flat, minimal, and purposeful.
### Visual hierarchy
Not everything deserves equal treatment. Primary content gets more space, larger headings, and accent color. Supporting content stays compact. Squint test: blur your eyes — can you tell what matters?
**Color.** All colors from `useHostTheme()` tokens — read its JSDoc in the SDK declarations for the return shape and usage pattern. No hardcoded hex. Use accent color deliberately, not on everything.
### Slop patterns — forbidden
These specific patterns produce low-quality output. If 2+ are present, redesign.
- **Gradients** — no `linear-gradient`, `radial-gradient`, `background-clip: text`.
- **Emojis** — no emoji as icons, status indicators, bullets, or section markers.
- **Box shadows** — no `box-shadow`. Flat surfaces only.
- **Wall of identical cards** — every section wrapped in the same card style with no variation. Mix open sections with cards.
- **Rainbow coloring** — a different color on every element. Most elements are neutral; color is used sparingly with purpose.
- **Giant text** — font sizes above H1 (24px), or bold text stuffed in CardHeader.
- **Decorative borders** — colored borders on every element. Borders are structural (subtle stroke tokens), not decorative.
### Pre-delivery self-check
Before returning canvas code, verify:
1. Does the layout have visual hierarchy? One thing should stand out.
2. Is there variety in the composition? Not just a single column of uniform blocks.
3. Slop check: scan for the forbidden patterns above.
## Introducing the canvas
When you create a canvas, add a short note in your chat response telling the user you created a canvas they can open beside the chat:
- **First canvas** — if no other `.canvas.tsx` files exist in the workspace's `canvases/` directory, include one sentence explaining what a canvas is.
- **Unsolicited canvas** — if the user didn't ask for a canvas, include one sentence explaining why you chose it over plain text.
Both can apply at once; one or two sentences total is enough. Skip the intro for subsequent canvases.
## Troubleshooting
If a canvas appears blank or missing, the most common cause is that it was not written under `/Users/<user>/.cursor/projects/<workspace>/canvases/` exactly — re-save it to that path. Do not debug this by trying to create the managed directory manually; focus on correcting the file path instead. Users can click the canvas file path in the response to open it, just like any other file path in Cursor. When present, the canvas server writes a `<name>.canvas.status.json` sidecar after each build with `status`, `diagnostics`, or `error` fields you can read; the file is best-effort and may not exist, so don't block on it.

View File

@@ -0,0 +1,264 @@
/**
* Design tokens for `cursor/canvas` (standalone; no UI framework dependency).
*
* Color values are aligned with the Cursor app dark theme (`packages/ui` `cursor-dark` sources).
*/
export declare const canvasPaletteDark: {
readonly foreground: "#E4E4E4EB";
readonly foregroundSecondary: "#E4E4E48D";
readonly foregroundTertiary: "#E4E4E45E";
readonly foregroundQuaternary: "#E4E4E442";
readonly editor: "#181818";
readonly chrome: "#141414";
readonly sidebar: "#141414";
readonly elevated: "#181818";
readonly fillPrimary: "#E4E4E430";
readonly fillSecondary: "#E4E4E41E";
readonly fillTertiary: "#E4E4E411";
readonly fillQuaternary: "#E4E4E40A";
readonly strokePrimary: "#E4E4E433";
readonly strokeSecondary: "#E4E4E41F";
readonly strokeTertiary: "#E4E4E414";
readonly accent: "#599CE7";
readonly buttonBackground: "#599CE7";
readonly buttonForeground: "#191c22";
readonly buttonHoverBackground: "#6AABE9";
readonly link: "#87c3ff";
readonly diffInsertedLine: "#3FA26633";
readonly diffRemovedLine: "#B8004933";
readonly diffStripAdded: "#3FA2668F";
readonly diffStripRemoved: "#FC6B838F";
};
/**
* Light-mode palette derived from `packages/ui/src/tokens/themes/cursor-core/light.ts`.
* Base color: #141414. Same percentages as dark (regular light has no overrides
* in CURSOR_SEMANTIC_OVERRIDES — only high-contrast does).
*/
export declare const canvasPaletteLight: {
readonly foreground: "#141414F0";
readonly foregroundSecondary: "#141414BD";
readonly foregroundTertiary: "#1414148A";
readonly foregroundQuaternary: "#1414145C";
readonly editor: "#FCFCFC";
readonly chrome: "#F8F8F8";
readonly sidebar: "#F3F3F3";
readonly elevated: "#FCFCFC";
readonly fillPrimary: "#14141433";
readonly fillSecondary: "#14141424";
readonly fillTertiary: "#14141414";
readonly fillQuaternary: "#1414140F";
readonly strokePrimary: "#14141433";
readonly strokeSecondary: "#1414141F";
readonly strokeTertiary: "#14141414";
readonly accent: "#3685BF";
readonly buttonBackground: "#3685BF";
readonly buttonForeground: "#FCFCFC";
readonly buttonHoverBackground: "#2E76AB";
readonly link: "#3685BF";
readonly diffInsertedLine: "#1F8A651F";
readonly diffRemovedLine: "#CF2D5614";
readonly diffStripAdded: "#1F8A65CC";
readonly diffStripRemoved: "#CF2D56CC";
};
export interface CanvasPalette {
readonly foreground: string;
readonly foregroundSecondary: string;
readonly foregroundTertiary: string;
readonly foregroundQuaternary: string;
readonly editor: string;
readonly chrome: string;
readonly sidebar: string;
readonly elevated: string;
readonly fillPrimary: string;
readonly fillSecondary: string;
readonly fillTertiary: string;
readonly fillQuaternary: string;
readonly strokePrimary: string;
readonly strokeSecondary: string;
readonly strokeTertiary: string;
readonly accent: string;
readonly buttonBackground: string;
readonly buttonForeground: string;
readonly buttonHoverBackground: string;
readonly link: string;
readonly diffInsertedLine: string;
readonly diffRemovedLine: string;
readonly diffStripAdded: string;
readonly diffStripRemoved: string;
}
/**
* Chart color palette — distilled from portal-website analytics charts.
* 88% opacity (E0) softens vibrancy without dulling; palette maximizes
* hue + luminosity spread for distinguishable multi-series charts.
*/
export declare const chartPalette: {
readonly green: "#1F8A65E8";
readonly darkGreen: "#0D855AE0";
readonly lightGreen: "#52B896E0";
readonly mintGreen: "#7DCAB0E0";
readonly blue: "#2E79B5E0";
readonly lightBlue: "#70B0D8E0";
readonly indigo: "#5A6CC0F0";
readonly lightIndigo: "#9AAADCE0";
readonly purple: "#7B64B8F0";
readonly lightPurple: "#AA98D8E0";
readonly warmPink: "#C85898E0";
readonly lightPink: "#E8A0C4E0";
readonly brightOrange: "#F0A040E0";
readonly deepOrange: "#C06028E0";
readonly goldenYellow: "#E8C030E0";
readonly darkAmber: "#C04848E0";
readonly warmPeach: "#F0A088E0";
readonly vibrantTeal: "#2A9A8AE0";
readonly muted: "#8888A8E0";
readonly neutralLine: "#888899D0";
};
/**
* Shared category palette for canvas primitives that show categorical tints
* (`Swatch`, `UsageBar` segments, etc.). Uses a coherent subset of
* `chartPalette` so a category's color reads consistently across any
* primitive that consumes it.
*
* The insertion order here is the canonical category order — primitives
* that auto-assign colors (e.g. `UsageBar` segments without an explicit
* `color`) cycle through these keys in order.
*/
export declare const colorPalette: {
readonly gray: "#8888A8E0";
readonly purple: "#7B64B8F0";
readonly green: "#1F8A65E8";
readonly yellow: "#E8C030E0";
readonly pink: "#C85898E0";
readonly blue: "#2E79B5E0";
readonly orange: "#F0A040E0";
};
export type Color = keyof typeof colorPalette;
/**
* Auto-color rotation for `UsageBar` segments without an explicit `color`.
* Decoupled from `colorPalette`'s declaration order so the palette can grow
* or be reordered without changing how unspecified segments cycle.
*
* Matches the original `packages/ui` `ContextUsageTray` order so the same
* segment index lands on the same hue as the source.
*/
export declare const usageColorSequence: readonly Color[];
/**
* Ordered array for automatic series coloring — alternates dark/light across
* distinct hue families for maximum perceptual separation.
*/
export declare const chartColorSequence: readonly ["#1F8A65E8", "#70B0D8E0", "#5A6CC0F0", "#F0A040E0", "#C06028E0", "#E8C030E0", "#C85898E0", "#F0A088E0", "#7B64B8F0", "#7DCAB0E0", "#8888A8E0", "#2A9A8AE0"];
declare function buildTokens(palette: CanvasPalette): {
bg: {
editor: string;
chrome: string;
elevated: string;
};
text: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
link: string;
onAccent: string;
};
stroke: {
primary: string;
secondary: string;
tertiary: string;
};
fill: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
};
accent: {
primary: string;
control: string;
controlHover: string;
};
diff: {
insertedLine: string;
removedLine: string;
stripAdded: string;
stripRemoved: string;
};
};
/** Semantic colors for components (spacing and radius live in `theme.ts`). */
export declare const canvasTokens: {
bg: {
editor: string;
chrome: string;
elevated: string;
};
text: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
link: string;
onAccent: string;
};
stroke: {
primary: string;
secondary: string;
tertiary: string;
};
fill: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
};
accent: {
primary: string;
control: string;
controlHover: string;
};
diff: {
insertedLine: string;
removedLine: string;
stripAdded: string;
stripRemoved: string;
};
};
export declare const canvasTokensLight: {
bg: {
editor: string;
chrome: string;
elevated: string;
};
text: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
link: string;
onAccent: string;
};
stroke: {
primary: string;
secondary: string;
tertiary: string;
};
fill: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
};
accent: {
primary: string;
control: string;
controlHover: string;
};
diff: {
insertedLine: string;
removedLine: string;
stripAdded: string;
stripRemoved: string;
};
};
export type CanvasTokens = ReturnType<typeof buildTokens>;
export {};
//# sourceMappingURL=canvas-tokens.d.ts.map

View File

@@ -0,0 +1,200 @@
/**
* Chart primitives for `cursor/canvas` — multi-series, stacked, and pie charts
* rendered as pure inline SVG with zero external dependencies.
*
* Distilled from the portal-website Highcharts analytics charting layer.
*/
import type { CSSProperties, JSX } from "react";
/**
* Semantic tone for a chart series or slice. Mirrors the tone vocabulary
* used by `Stat`, `Pill`, `Table`, and other SDK primitives so colors
* match across a canvas — e.g. a `Stat tone="success"` and a
* `ChartSeries tone="success"` render in the same green.
*
* Omit `tone` to let the chart auto-assign a distinct color from the
* chart palette; supply `tone` only when the value carries semantic
* meaning that should match other tonal elements on the page.
*/
export type ChartTone = "success" | "danger" | "warning" | "info" | "neutral";
/** A single labeled value, used by `PieChart`. */
export type ChartDataPoint = {
label: string;
/** Non-negative numeric value. */
value: number;
};
/**
* A named data series for `BarChart` and `LineChart`.
* The `data` array aligns by index with the parent component's `categories`.
* If `tone` is omitted, a color is auto-assigned from the chart palette.
*/
export type ChartSeries = {
name: string;
data: number[];
tone?: ChartTone;
};
export type BarChartProps = {
/** Category labels along the independent axis. */
categories: string[];
/** One or more data series. Values align by index with `categories`. */
series: ChartSeries[];
height?: number;
/** Stack series on top of each other instead of grouping side-by-side. */
stacked?: boolean;
/** Render horizontal bars instead of vertical columns. */
horizontal?: boolean;
/** Show as 100% stacked (implies `stacked`). */
normalized?: boolean;
/** Suffix for y-axis tick labels (e.g. "%"). */
valueSuffix?: string;
style?: CSSProperties;
};
export type LineChartProps = {
categories: string[];
series: ChartSeries[];
height?: number;
/** Fill the area under each line with a soft tint. */
fill?: boolean;
valueSuffix?: string;
style?: CSSProperties;
};
export type PieChartProps = {
data: Array<ChartDataPoint & {
tone?: ChartTone;
}>;
size?: number;
donut?: boolean;
style?: CSSProperties;
};
/**
* Multi-series bar/column chart with optional stacking and normalization.
* Distilled from the portal-website Highcharts analytics charts.
*
* Pass `categories` for x-axis labels and one or more `series` whose `data`
* arrays align by index. With a single series you get simple bars; with
* multiple series the default is grouped (side-by-side) — set `stacked` for
* stacked columns or `normalized` for 100%-stacked share-mode.
*
* Colors are auto-assigned from the chart palette. With a **single series**,
* each bar gets a different color by category (so a chart of 5 categories
* shows 5 colors out of the box). With **multiple series**, each series gets
* its own color. A legend appears when there are 2+ series.
*
* For semantic coloring, pass `tone` on a series — it maps to the same
* palette entries used by `Stat`, `Pill`, and `Table` so your chart matches
* tonal elements elsewhere on the page.
*
* @example
* ```tsx
* // Simple single-series
* <BarChart
* categories={["Mon", "Tue", "Wed"]}
* series={[{ name: "Requests", data: [120, 90, 150] }]}
* />
*
* // Stacked multi-series (like portal AI commit chart)
* <BarChart
* categories={["Mon", "Tue", "Wed"]}
* series={[
* { name: "IDE", data: [120, 90, 150] },
* { name: "CLI", data: [30, 40, 25] },
* { name: "Cloud", data: [50, 60, 70] },
* ]}
* stacked
* />
*
* // Semantic tones — "accepted" renders in the same green as
* // <Stat tone="success"> elsewhere on the page.
* <BarChart
* categories={["Mon", "Tue", "Wed"]}
* series={[
* { name: "Accepted", data: [70, 80, 60], tone: "success" },
* { name: "Rejected", data: [30, 20, 40], tone: "danger" },
* ]}
* stacked
* />
* ```
*/
export declare function BarChart({ categories, series, height, stacked, horizontal, normalized, valueSuffix, style }: BarChartProps): JSX.Element;
/**
* Multi-series line chart with optional area fill. Distilled from the
* portal-website Highcharts analytics charts.
*
* Each series draws a polyline with dot markers at each data point.
* Set `fill` to shade the area under every line. Hover over any category
* column to see a tooltip with all series values at that point.
*
* This is **not** a time-series component — it does not parse dates.
* Pass pre-formatted date strings as `categories` if plotting over time.
*
* Colors are auto-assigned from the chart palette. For semantic coloring,
* pass `tone` on a series — it maps to the same palette entries used by
* `Stat`, `Pill`, and `Table`.
*
* @example
* ```tsx
* // Single line
* <LineChart
* categories={["Jan", "Feb", "Mar", "Apr"]}
* series={[{ name: "Revenue", data: [100, 140, 120, 180] }]}
* />
*
* // Multi-series with area fill
* <LineChart
* categories={["Jan", "Feb", "Mar", "Apr"]}
* series={[
* { name: "Accepted", data: [50, 70, 60, 90] },
* { name: "Suggested", data: [120, 140, 130, 160] },
* ]}
* fill
* />
*
* // Semantic tones — "errors" renders in the same red as a
* // <Pill tone="danger"> elsewhere on the page.
* <LineChart
* categories={["00:00", "06:00", "12:00", "18:00"]}
* series={[
* { name: "p95 latency", data: [80, 95, 110, 90], tone: "info" },
* { name: "errors", data: [2, 4, 9, 3], tone: "danger" },
* ]}
* />
* ```
*/
export declare function LineChart({ categories, series, height, fill, valueSuffix, style }: LineChartProps): JSX.Element;
/**
* Pie (or donut) chart with hover highlighting. Distilled from the
* portal-website Highcharts analytics charts.
*
* Unlike `BarChart` and `LineChart`, `PieChart` takes a flat `data` array of
* `{ label, value }` points — each slice is its own category. Colors are
* auto-assigned from the chart palette; pass `tone` on a point to give a
* slice a semantic color that matches other tonal elements on the page.
*
* Hovering a slice expands it outward and dims the others; hovering a legend
* item does the same. A tooltip with value and percentage appears below the
* chart. Set `donut` for a hollow center.
*
* **Do not** use for bar-style comparisons — use `BarChart` instead.
*
* @example
* ```tsx
* // Basic pie
* <PieChart
* data={[
* { label: "IDE", value: 120 },
* { label: "CLI", value: 30 },
* { label: "Cloud", value: 50 },
* ]}
* />
*
* // Donut with semantic tones
* <PieChart
* data={[
* { label: "Passing", value: 70, tone: "success" },
* { label: "Failing", value: 30, tone: "danger" },
* ]}
* donut
* />
* ```
*/
export declare function PieChart({ data, size, donut, style }: PieChartProps): JSX.Element;
//# sourceMappingURL=chart-primitives.d.ts.map

View File

@@ -0,0 +1,65 @@
/**
* Borderless disclosure row — chevron + structured header (title, optional
* leading slot, count, trailing slot) with a body that toggles open. Distilled
* from baby-glass `ContextTreeRow`'s lightweight list-row chrome (no card
* border, no background fill).
*
* For a bordered surface that collapses, use `<Card collapsible>` instead.
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
export type CollapsibleSectionProps = {
/** Plain-text title rendered next to the disclosure chevron. */
title: string;
/**
* Optional leading visual (e.g. `<Swatch>`) shown between the chevron and
* the title.
*/
leading?: ReactNode;
/**
* Optional small count rendered after the title (e.g. number of children).
*/
count?: number;
/**
* Optional trailing node, right-aligned (e.g. token readout, badge, button).
* Rendered with `t.text.tertiary` color hint via the wrapper; the slot can
* override.
*/
trailing?: ReactNode;
/** Body shown when expanded. */
children?: ReactNode;
style?: CSSProperties;
};
/**
* Borderless collapsible row with a structured header. Always starts closed
* (uncontrolled, no `defaultOpen`).
*
* Compose with `<Swatch>` in the `leading` slot for a colored category icon,
* and put a token readout / pill / button in `trailing`. Body content is
* indented under the row so nested `CollapsibleSection`s read as a tree.
*
* For a bordered, card-shaped collapsible surface, use `<Card collapsible>`
* instead — `CollapsibleSection` has no border or background and is meant to
* sit in a list of similar rows.
*
* @example
* ```tsx
* // Basic
* <CollapsibleSection title="Conversation">
* <Text>Messages go here.</Text>
* </CollapsibleSection>
*
* // With a colored category swatch + count + trailing token readout
* <CollapsibleSection
* title="Tools"
* count={4}
* leading={<Swatch color="purple" />}
* trailing={<Text size="small" tone="tertiary">12.3k</Text>}
* >
* <CollapsibleSection title="Grep">
* <Text>Search results.</Text>
* </CollapsibleSection>
* </CollapsibleSection>
* ```
*/
export declare function CollapsibleSection({ title, leading, count, trailing, children, style }: CollapsibleSectionProps): JSX.Element;
//# sourceMappingURL=collapsible-section.d.ts.map

102
skills-cursor/canvas/sdk/dag-layout.d.ts vendored Normal file
View File

@@ -0,0 +1,102 @@
/**
* Pure layout math for directed acyclic graphs. Returns positioned node
* coordinates, edge anchor points, rank bounding boxes, and back-edge flags.
* Rendering is the caller's responsibility.
*
* Handles cycles gracefully: back-edges are detected via DFS, excluded from
* ranking, and flagged in the output so the caller can render them differently
* (e.g. dashed arcs).
*/
export type DAGLayoutOptions = {
/** Nodes to lay out. Only `id` is required. */
nodes: Array<{
id: string;
}>;
/** Directed edges. */
edges: Array<{
from: string;
to: string;
}>;
/** Flow direction. Default `"vertical"` (top-to-bottom). */
direction?: "vertical" | "horizontal";
/** Node box width in px. Default 160. */
nodeWidth?: number;
/** Node box height in px. Default 40. */
nodeHeight?: number;
/** Gap between ranks (layers) in px. Default 64. */
rankGap?: number;
/** Gap between sibling nodes in the same rank in px. Default 48. */
nodeGap?: number;
/** Padding around the bounding box in px. Default 24. */
padding?: number;
};
export type DAGLayoutNode = {
id: string;
/** Left edge of the node box. */
x: number;
/** Top edge of the node box. */
y: number;
/** Layer index (0 = root). */
rank: number;
/** Position within the rank (0-indexed). */
order: number;
};
export type DAGLayoutEdge = {
from: string;
to: string;
/** Suggested source anchor point (center of the outgoing side). */
sourceX: number;
sourceY: number;
/** Suggested target anchor point (center of the incoming side). */
targetX: number;
targetY: number;
/** True when this edge was identified as a back-edge (part of a cycle). */
isBackEdge: boolean;
};
export type DAGLayoutRank = {
/** Rank index (0 = root). */
rank: number;
/** Left edge of the rank bounding box. */
x: number;
/** Top edge of the rank bounding box. */
y: number;
/** Width of the rank bounding box. */
width: number;
/** Height of the rank bounding box. */
height: number;
/** Node ids in this rank, in order. */
nodeIds: string[];
};
export type DAGLayoutResult = {
nodes: DAGLayoutNode[];
edges: DAGLayoutEdge[];
/** Bounding box per rank — useful for drawing layer bands. */
ranks: DAGLayoutRank[];
/** The direction used for this layout. */
direction: "vertical" | "horizontal";
/** Total width of the bounding box. */
width: number;
/** Total height of the bounding box. */
height: number;
};
/**
* Compute a hierarchical layout for a directed graph.
*
* Returns node positions, edge anchor points, rank bounding boxes, and
* back-edge flags. The caller handles all rendering.
*
* @example
* ```ts
* const layout = computeDAGLayout({
* nodes: [{ id: "a" }, { id: "b" }, { id: "c" }],
* edges: [{ from: "a", to: "b" }, { from: "b", to: "c" }],
* });
*
* // layout.nodes[i].x / .y → position your own SVG/HTML elements
* // layout.edges[i].sourceX/Y, targetX/Y → draw lines between them
* // layout.edges[i].isBackEdge → style cycle edges differently
* // layout.ranks[i] → draw layer bands behind each rank
* ```
*/
export declare function computeDAGLayout(options: DAGLayoutOptions): DAGLayoutResult;
//# sourceMappingURL=dag-layout.d.ts.map

130
skills-cursor/canvas/sdk/diff-view.d.ts vendored Normal file
View File

@@ -0,0 +1,130 @@
/**
* Low-level diff primitives for the canvas SDK.
*
* The canvas diff surface is intentionally minimal — two components that
* compose with the generic `Card` / `CardHeader` / `CardBody` / `Pill` /
* `Text` primitives to build any diff layout an agent can imagine:
*
* - `DiffView` — a monospaced, syntax-highlighted unified diff renderer.
* Drops into any container (a `Card`, a table cell, a bare layout,
* nothing at all). No card chrome, no header, no path display. Pass
* `path` to auto-detect the language for highlighting, or `language`
* to override.
*
* - `DiffStats` — the canonical `+N` green / `-N` red glyph pair. Use
* it anywhere a small "added/deleted" summary makes sense — in a
* `CardHeader`'s `trailing` slot, next to a filename in a file tree,
* inside a status row, etc.
*
* For file-level metadata the preferred composition is to use the
* generic `Card` family:
*
* ```tsx
* <Card collapsible>
* <CardHeader trailing={<DiffStats additions={5} deletions={2} />}>
* src/utils.ts
* </CardHeader>
* <CardBody style={{ padding: 0 }}>
* <DiffView path="src/utils.ts" lines={lines} />
* </CardBody>
* </Card>
* ```
*/
import type { CSSProperties, JSX } from "react";
export type DiffStatsProps = {
additions?: number;
deletions?: number;
style?: CSSProperties;
};
/**
* Inline `+N` / `-N` glyph pair. Green additions, red deletions, with
* tabular numerals so columns of stats line up. Renders nothing when
* both counts are zero.
*
* @example
* ```tsx
* <CardHeader trailing={<DiffStats additions={12} deletions={3} />}>
* src/utils.ts
* </CardHeader>
*
* <Row gap={8}>
* <Text>Refactor pass</Text>
* <DiffStats additions={42} deletions={17} />
* </Row>
* ```
*/
export declare function DiffStats({ additions, deletions, style }: DiffStatsProps): JSX.Element | null;
export type DiffLineType = "added" | "removed" | "unchanged";
export type DiffLineData = {
type: DiffLineType;
content: string;
lineNumber?: number;
};
export type DiffViewProps = {
lines: DiffLineData[];
/**
* File path used to infer the syntax-highlighting language from the
* extension (e.g. `"src/utils.ts"` → `typescript`). The most ergonomic
* way to enable highlighting — pass the same path you show in the
* enclosing card header. Unknown extensions silently render as plain
* text.
*
* If both `path` and `language` are provided, `language` wins.
*/
path?: string;
/**
* Explicit language override for syntax highlighting (e.g.
* `"typescript"`, `"python"`, `"tsx"`). Use this when no file path is
* available, when the path's extension is misleading, or when the
* content is a snippet rather than a real file. Accepts common
* aliases (`ts`, `py`, `rs`, `md`, etc.). Unknown languages silently
* fall back to plain text.
*
* Highlighting is applied per line, so multi-line constructs (block
* comments, template literals) may not colorize perfectly across line
* boundaries. For typical diff-sized inputs this is fine.
*/
language?: string;
/** Show line numbers in the gutter. Default `true`. */
showLineNumbers?: boolean;
/** Color line numbers green/red for added/removed lines. Default `true`. */
coloredLineNumbers?: boolean;
/** Show a 3px accent strip on the left edge for changed lines. Default `true`. */
showAccentStrip?: boolean;
style?: CSSProperties;
};
/**
* Unified diff body renderer with monospaced type, colored line
* backgrounds, line-number gutter, accent strip, and optional Shiki
* syntax highlighting.
*
* `DiffView` does not provide any surrounding chrome — place it inside
* a `Card` + `CardBody` (with `padding: 0`) when you want the standard
* bordered "file diff" look, or drop it anywhere else if you want the
* bare renderer.
*
* Pass `path` to enable syntax highlighting from the file extension.
*
* @example
* ```tsx
* <Card>
* <CardHeader trailing={<DiffStats additions={2} deletions={1} />}>
* src/utils.ts
* </CardHeader>
* <CardBody style={{ padding: 0 }}>
* <DiffView
* path="src/utils.ts"
* lines={[
* { type: "unchanged", content: "export function add(a: number, b: number): number {", lineNumber: 1 },
* { type: "removed", content: " return a + b;", lineNumber: 2 },
* { type: "added", content: " const result = a + b;", lineNumber: 2 },
* { type: "added", content: " return result;", lineNumber: 3 },
* { type: "unchanged", content: "}", lineNumber: 4 },
* ]}
* />
* </CardBody>
* </Card>
* ```
*/
export declare function DiffView({ lines, path, language, showLineNumbers, coloredLineNumbers, showAccentStrip, style }: DiffViewProps): JSX.Element;
//# sourceMappingURL=diff-view.d.ts.map

View File

@@ -0,0 +1,194 @@
/**
* Form primitives for `cursor/canvas`. Provides themed, controlled form controls
* for interactive canvas apps with persistent state. All `onChange` callbacks
* receive the **value directly** (not a DOM event), so they pair naturally
* with `useCanvasState`:
*
* ```tsx
* const [name, setName] = useCanvasState("name", "");
* <TextInput value={name} onChange={setName} placeholder="Enter name…" />
* ```
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
export type TextInputProps = {
value?: string;
/** Called with the new string value on every keystroke. */
onChange?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
type?: "text" | "email" | "password" | "number" | "url" | "search";
style?: CSSProperties;
};
/**
* Single-line text input (28px height). Use for names, titles, search
* queries, and short text fields.
*
* `onChange` receives the **string value**, not a DOM event — this pairs
* directly with `useCanvasState` setters.
*
* @example
* ```tsx
* const [name, setName] = useCanvasState("name", "");
*
* <TextInput value={name} onChange={setName} placeholder="Task title…" />
* ```
*/
export declare function TextInput({ value, onChange, placeholder, disabled, type, style }: TextInputProps): JSX.Element;
export type TextAreaProps = {
value?: string;
/** Called with the new string value on every keystroke. */
onChange?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
/** Minimum visible rows. Defaults to 3. */
rows?: number;
style?: CSSProperties;
};
/**
* Multi-line text input that auto-resizes to fit its content.
* Use for notes, descriptions, comments, and multi-line text fields.
*
* The textarea grows as the user types. Set `rows` for the minimum visible
* height (defaults to 3). Override with `style={{ height: "100px" }}` for a
* fixed size.
*
* @example
* ```tsx
* const [notes, setNotes] = useCanvasState("notes", "");
*
* <TextArea value={notes} onChange={setNotes} placeholder="Add notes…" rows={4} />
* ```
*/
export declare function TextArea({ value, onChange, placeholder, disabled, rows, style }: TextAreaProps): JSX.Element;
export type CheckboxProps = {
checked?: boolean;
/** Called with the new boolean value when toggled. */
onChange?: (checked: boolean) => void;
disabled?: boolean;
/** Optional label rendered beside the checkbox. Clicking the label toggles the checkbox. */
label?: ReactNode;
style?: CSSProperties;
};
/**
* Checkbox with optional label (accent blue when checked).
*
* Pass `label` to render a clickable text label beside the checkbox. Without a
* label, provide `title` or wrap in your own `<label>` for accessibility.
*
* @example
* ```tsx
* const [agreed, setAgreed] = useCanvasState("agreed", false);
*
* <Checkbox checked={agreed} onChange={setAgreed} label="I agree to the terms" />
* ```
*
* @example
* ```tsx
* // Checkbox in a list — no label, parent handles layout
* <Checkbox checked={item.done} onChange={(v) => toggleItem(item.id, v)} />
* ```
*/
export declare function Checkbox({ checked, onChange, disabled, label, style }: CheckboxProps): JSX.Element;
export type ToggleProps = {
checked?: boolean;
/** Called with the new boolean value when toggled. */
onChange?: (checked: boolean) => void;
disabled?: boolean;
/** `sm` = 16px track, `md` = 20px track (default `sm`). */
size?: "sm" | "md";
style?: CSSProperties;
};
/**
* Boolean toggle switch. Uses the accent color for the "on" state and a
* neutral fill for "off".
*
* @example
* ```tsx
* const [enabled, setEnabled] = useCanvasState("enabled", false);
*
* <Row gap={8} align="center">
* <Text>Notifications</Text>
* <Spacer />
* <Toggle checked={enabled} onChange={setEnabled} />
* </Row>
* ```
*/
export declare function Toggle({ checked, onChange, disabled, size, style }: ToggleProps): JSX.Element;
export type SelectOption = {
value: string;
label: string;
disabled?: boolean;
};
export type SelectProps = {
value?: string;
/** Called with the new selected value. */
onChange?: (value: string) => void;
/** List of options. Each must have a unique `value`. */
options: SelectOption[];
/** Placeholder shown when no value is selected. */
placeholder?: string;
disabled?: boolean;
style?: CSSProperties;
};
/**
* Dropdown select (native `<select>` with themed styling).
*
* Uses a native `<select>` under the hood for reliable keyboard, screen-reader,
* and mobile support. The dropdown list uses OS-native styling.
*
* @example
* ```tsx
* const [priority, setPriority] = useCanvasState("priority", "medium");
*
* <Select
* value={priority}
* onChange={setPriority}
* options={[
* { value: "low", label: "Low" },
* { value: "medium", label: "Medium" },
* { value: "high", label: "High" },
* ]}
* />
* ```
*/
export declare function Select({ value, onChange, options, placeholder, disabled, style }: SelectProps): JSX.Element;
export type IconButtonProps = {
/** Icon content: an SVG element, emoji, or unicode character. */
children: ReactNode;
onClick?: () => void;
disabled?: boolean;
/** Tooltip text. Always provide for accessibility since there is no text label. */
title?: string;
/**
* `"default"` is transparent until hovered; `"circle"` has a permanent
* background fill.
*/
variant?: "default" | "circle";
/** `sm` = 16px, `md` = 20px (default `md`). */
size?: "sm" | "md";
style?: CSSProperties;
};
/**
* Compact icon-only button for inline actions on list items (delete, edit,
* expand, etc.). Accepts **any** `children` as the icon — use an inline SVG,
* an emoji, or a unicode character.
*
* Always provide `title` for accessibility (screen-reader label + tooltip).
*
* Canvas has no icon font, so pass icon content directly.
*
* @example
* ```tsx
* // Delete button on a card
* <IconButton title="Delete" onClick={() => remove(id)}>✕</IconButton>
*
* // Edit button with an SVG icon
* <IconButton title="Edit" variant="circle" size="sm" onClick={edit}>
* <svg width={12} height={12} viewBox="0 0 12 12" fill="none">
* <path d="M8.5 1.5l2 2L4 10H2V8z" stroke="currentColor" strokeWidth={1.2} />
* </svg>
* </IconButton>
* ```
*/
export declare function IconButton({ children, onClick, disabled, title, variant, size, style }: IconButtonProps): JSX.Element;
//# sourceMappingURL=form-primitives.d.ts.map

141
skills-cursor/canvas/sdk/hooks.d.ts vendored Normal file
View File

@@ -0,0 +1,141 @@
import type { CanvasPalette, CanvasTokens } from "./canvas-tokens.js";
import { type CanvasAction } from "./internal/canvas-action-dispatch.js";
/**
* Host theme for the current canvas. Semantic color groups (`text`, `bg`,
* `fill`, `stroke`, `accent`, `diff`) live at the top level for ergonomic
* inline-style access; `tokens` is also present as a self-reference for
* callers that prefer a namespaced form.
*/
export interface CanvasHostTheme extends CanvasTokens {
readonly kind: string;
readonly tokens: CanvasTokens;
readonly palette: CanvasPalette;
}
/**
* Returns the current host theme. Falls back to dark mode when no host
* state is available.
*
* Semantic color groups are available directly on the returned object —
* `accent`, `text`, `bg`, `fill`, `stroke`, `diff` — as well as `kind`
* (`"dark"` | `"light"` | …) and `palette` (the flat color palette).
*
* Call `useHostTheme()` inside each component that needs theme access —
* the returned object is scoped to that component, not shared across
* function boundaries.
*
* Stable paths for `style={{ ... }}` usage:
* - `text.primary / secondary / tertiary / quaternary` — text hierarchy
* - `bg.editor / chrome / elevated` — surface backgrounds
* - `fill.primary / secondary / tertiary / quaternary` — tinted fills
* - `stroke.primary / secondary / tertiary` — borders and dividers
* - `accent.primary / control` — accent blue and button background
* - `text.link` — link color
* - `text.onAccent` — text on accent-colored surfaces
*
* Prefer built-in components (`Card`, `Button`, `Text`, etc.) over raw
* token usage. Reach for tokens only when no component covers the case,
* and stick to flat solid colors — no gradients, no box-shadows.
*
* @example
* ```tsx
* function Overview() {
* const theme = useHostTheme();
* return (
* <div style={{ background: theme.fill.tertiary, color: theme.text.secondary, padding: 8 }}>
* <span style={{ color: theme.accent.primary }}>Accent text</span>
* </div>
* );
* }
* ```
*/
export declare function useHostTheme(): CanvasHostTheme;
/**
* Setter for `useCanvasState`. Accepts either a new value or an updater
* function that receives the previous value.
*/
export type SetCanvasState<T> = (action: T | ((prev: T) => T)) => void;
/**
* Persistent state hook for canvas applications. Works like `React.useState`
* but the value survives rebuilds, reloads, and IDE restarts — it is stored
* in a `.canvas.data.json` sidecar file next to the canvas source.
*
* Each key is a unique string chosen by the canvas author. Keys are stable
* regardless of hook call order or component tree structure.
*
* @param key - Unique string identifier for this piece of state.
* @param defaultValue - Value returned when no persisted value exists for `key`.
* @returns A `[value, setValue]` tuple, same shape as `React.useState`.
*
* @example
* ```tsx
* function Counter() {
* const [count, setCount] = useCanvasState("count", 0);
* return <Button onClick={() => setCount(c => c + 1)}>{count}</Button>;
* }
* ```
*
* @example
* ```tsx
* interface Column { id: string; title: string; cardIds: string[] }
* function KanbanBoard() {
* const [columns, setColumns] = useCanvasState<Column[]>("columns", [
* { id: "todo", title: "To Do", cardIds: [] },
* { id: "doing", title: "In Progress", cardIds: [] },
* { id: "done", title: "Done", cardIds: [] },
* ]);
* // ...
* }
* ```
*/
export declare function useCanvasState<T>(key: string, defaultValue: T): [T, SetCanvasState<T>];
export type { CanvasAction };
/**
* Returns a stable `dispatch` function for triggering IDE actions from
* canvas buttons. Actions are fire-and-forget — the canvas does not
* receive a response.
*
* ## Available actions
*
* **`openAgent`** — Navigate the IDE to an agent conversation.
* `agentId` is the conversation UUID (the filename stem from the
* `agent-transcripts/` directory). Works for both local and
* cloud/background agents in Glass and classic IDE modes.
*
* **`newComposerChat`** — Open a new local agent chat with the
* dispatching canvas file as an @mention. Optional `userPrompt` is
* appended after the mention. The canvas file is resolved from the
* action route's `canvasId`; do not pass the path from canvas UI.
*
* @example
* ```tsx
* function AgentLink({ agentId, title }: { agentId: string; title: string }) {
* const dispatch = useCanvasAction();
* return (
* <Button onClick={() => dispatch({ type: "openAgent", agentId })}>
* {title}
* </Button>
* );
* }
* ```
*
* @example
* ```tsx
* function AskAboutCanvas() {
* const dispatch = useCanvasAction();
* return (
* <Button
* onClick={() =>
* dispatch({
* type: "newComposerChat",
* userPrompt: "please explain what is using the most context",
* })
* }
* >
* Ask in chat
* </Button>
* );
* }
* ```
*/
export declare function useCanvasAction(): (action: CanvasAction) => void;
//# sourceMappingURL=hooks.d.ts.map

59
skills-cursor/canvas/sdk/index.d.ts vendored Normal file
View File

@@ -0,0 +1,59 @@
/**
* Public API for authoring `.canvas.tsx` files via `cursor/canvas`.
*
* Be creative with layout — use Grid, Row, cards, charts, tables, and raw SVG
* in whatever combination serves the content. Read the canvas skill for full
* design guidance. Key constraints:
*
* - Colors from `useHostTheme()` tokens. No hardcoded hex.
* - No gradients, no box-shadows, no emojis as decoration.
* - Don't wrap every section in Card — mix open sections with cards.
* - Run the pre-delivery self-check before returning code.
*/
/** Shared category color palette used by `Swatch`, `UsageBar`, etc. */
export type { Color } from "./canvas-tokens.js";
export { colorPalette, usageColorSequence } from "./canvas-tokens.js";
/** Charts. */
export type { BarChartProps, ChartDataPoint, ChartSeries, ChartTone, LineChartProps, PieChartProps, } from "./chart-primitives.js";
export { BarChart, LineChart, PieChart } from "./chart-primitives.js";
/** Borderless collapsible disclosure row with a structured header. */
export type { CollapsibleSectionProps } from "./collapsible-section.js";
export { CollapsibleSection } from "./collapsible-section.js";
/** DAG layout. */
export type { DAGLayoutEdge, DAGLayoutNode, DAGLayoutOptions, DAGLayoutRank, DAGLayoutResult, } from "./dag-layout.js";
export { computeDAGLayout } from "./dag-layout.js";
/**
* Diff rendering. Compose with the generic `Card` family for file-level
* chrome: use `DiffView` inside a `CardBody` (with `padding: 0`) and put
* `DiffStats` in the enclosing `CardHeader`'s `trailing` slot.
*/
export type { DiffLineData, DiffLineType, DiffStatsProps, DiffViewProps, } from "./diff-view.js";
export { DiffStats, DiffView } from "./diff-view.js";
/** Form controls. */
export type { CheckboxProps, IconButtonProps, SelectOption, SelectProps, TextAreaProps, TextInputProps, ToggleProps, } from "./form-primitives.js";
export { Checkbox, IconButton, Select, TextArea, TextInput, Toggle, } from "./form-primitives.js";
/** Host state hooks. */
export type { CanvasAction, CanvasHostTheme, SetCanvasState } from "./hooks.js";
export { useCanvasAction, useCanvasState, useHostTheme } from "./hooks.js";
/** Colored category swatch (uses the shared `Color` palette). */
export type { SwatchProps } from "./swatch.js";
export { Swatch } from "./swatch.js";
/** Semantic design tokens for custom styling. */
export type { CanvasPalette, CanvasTokens } from "./theme.js";
export { canvasPaletteDark, canvasPaletteLight, canvasTokens, canvasTokensLight, } from "./theme.js";
export type { TodoItem, TodoListCardProps, TodoListProps, TodoStatus, } from "./todo-list.js";
export { TodoList, TodoListCard } from "./todo-list.js";
/** Component props types. */
export type { ButtonProps, CalloutProps, CalloutTone, CardBodyProps, CardHeaderProps, CardProps, CardSize, CardVariant, CodeProps, DividerProps, GridProps, H1Props, H2Props, H3Props, LinkProps, PillProps, PillSize, PillTone, RowProps, StackProps, StatProps, StatTone, TableColumnAlign, TableProps, TableRowTone, TextProps, TextWeight, } from "./ui-primitives.js";
/** Layout. */
/** Typography. */
/** Surfaces. */
/** Actions. */
/** Feedback. */
export { Button, Callout, Card, CardBody, CardHeader, Code, Divider, Grid, H1, H2, H3, Link,
/** Shallow-merge two style objects. Useful for combining tokens with overrides. */
mergeStyle, Pill, Row, Spacer, Stack, Stat, Table, Text, } from "./ui-primitives.js";
/** Usage bar — segmented progress meter with optional labels above. */
export type { UsageBarProps, UsageBarSegment } from "./usage-bar.js";
export { UsageBar } from "./usage-bar.js";
//# sourceMappingURL=index.d.ts.map

40
skills-cursor/canvas/sdk/swatch.d.ts vendored Normal file
View File

@@ -0,0 +1,40 @@
/**
* Colored category swatch — a small filled rounded box. Intended for inline
* list/row decoration (category badges, the leading slot of
* `CollapsibleSection`, etc.).
*
* Pulls colors from the shared `Color` palette so a category's swatch and
* its `UsageBar` segment for the same `color` stay visually coherent.
*/
import type { CSSProperties, JSX } from "react";
import { type Color } from "./canvas-tokens.js";
export type SwatchProps = {
/** One of the 7 shared category hues. Matches `UsageBar` segment colors. */
color: Color;
style?: CSSProperties;
};
/**
* Filled, rounded category swatch (24px). Use as the leading visual on
* category rows, list items, or as the `leading` slot of a
* `CollapsibleSection`.
*
* Colors come from the shared 7-hue `Color` palette, so a category's swatch
* matches its `UsageBar` segment for the same `color`.
*
* @example
* ```tsx
* // Standalone — purple "tools" swatch
* <Swatch color="purple" />
*
* // As the `leading` slot on a CollapsibleSection
* <CollapsibleSection
* title="Tools"
* count={4}
* leading={<Swatch color="purple" />}
* >
* <Text>Tool calls go here.</Text>
* </CollapsibleSection>
* ```
*/
export declare function Swatch({ color, style }: SwatchProps): JSX.Element;
//# sourceMappingURL=swatch.d.ts.map

61
skills-cursor/canvas/sdk/theme.d.ts vendored Normal file
View File

@@ -0,0 +1,61 @@
export type { CanvasPalette, CanvasTokens } from "./canvas-tokens.js";
export { canvasPaletteDark, canvasPaletteLight, canvasTokens, canvasTokensLight, } from "./canvas-tokens.js";
/** Typography presets used by the built-in `cursor/canvas` components. */
export declare const canvasTypography: {
readonly h1: {
readonly fontSize: "24px";
readonly lineHeight: "30px";
readonly fontWeight: 590;
};
readonly h2: {
readonly fontSize: "18px";
readonly lineHeight: "24px";
readonly fontWeight: 590;
};
readonly h3: {
readonly fontSize: "16px";
readonly lineHeight: "22px";
readonly fontWeight: 590;
};
readonly body: {
readonly fontSize: "14px";
readonly lineHeight: "20px";
readonly fontWeight: 400;
};
readonly small: {
readonly fontSize: "12px";
readonly lineHeight: "16px";
readonly fontWeight: 400;
};
};
/** Spacing scale (px). */
export declare const canvasSpacing: {
readonly "0.5": 2;
readonly "1": 4;
readonly "1.5": 6;
readonly "2": 8;
readonly "2.5": 10;
readonly "3": 12;
readonly "3.5": 14;
readonly "4": 16;
readonly "4.5": 18;
readonly "5": 20;
readonly "6": 24;
readonly "7": 28;
readonly "8": 32;
readonly "9": 36;
readonly "10": 40;
};
export type CanvasSpacing = typeof canvasSpacing;
/** Border radius (px). */
export declare const canvasRadius: {
readonly none: 0;
readonly xs: 2;
readonly sm: 4;
readonly md: 6;
readonly lg: 8;
readonly xl: 12;
readonly full: 9999;
};
export type CanvasRadius = typeof canvasRadius;
//# sourceMappingURL=theme.d.ts.map

49
skills-cursor/canvas/sdk/todo-list.d.ts vendored Normal file
View File

@@ -0,0 +1,49 @@
import type { CSSProperties, JSX } from "react";
export type TodoStatus = "pending" | "in_progress" | "completed" | "cancelled";
export interface TodoItem {
readonly id: string;
readonly content: string;
readonly status: TodoStatus;
}
export type TodoListProps = {
todos: readonly TodoItem[];
dimmedTodoIds?: ReadonlySet<string>;
/** Called when a todo row is clicked (entire row is a button). */
onTodoClick?: (todo: TodoItem) => void;
style?: CSSProperties;
};
/**
* Task list with status icons and wrapping text. Each row is a **clickable**
* button; use `onTodoClick` to handle selection or navigation.
*
* @example
* ```tsx
* <TodoList
* todos={items}
* onTodoClick={(todo) => setActiveId(todo.id)}
* />
* ```
*/
export declare function TodoList({ todos, dimmedTodoIds, onTodoClick, style }: TodoListProps): JSX.Element | null;
export type TodoListCardProps = {
todos: readonly TodoItem[];
dimmedTodoIds?: ReadonlySet<string>;
defaultExpanded?: boolean;
onTodoClick?: (todo: TodoItem) => void;
style?: CSSProperties;
};
/**
* Bordered, collapsible todo list with summary header (N of M Done). Compose
* with `onTodoClick` for row actions.
*
* @example
* ```tsx
* <TodoListCard
* todos={items}
* defaultExpanded
* onTodoClick={(todo) => console.log(todo.id)}
* />
* ```
*/
export declare function TodoListCard({ todos, dimmedTodoIds, defaultExpanded, onTodoClick, style }: TodoListCardProps): JSX.Element | null;
//# sourceMappingURL=todo-list.d.ts.map

View File

@@ -0,0 +1,549 @@
/**
* UI primitives for `cursor/canvas`. Styling follows the Cursor dark theme; no extra packages required.
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
/**
* Shallow-merge style objects with `override` taking precedence.
*
* Use for small tweaks on built-in components (e.g. extra padding or width).
* **Do not** use this to build elaborate custom chrome — prefer the built-in
* components and flat solid token colors. No gradients, no box-shadows.
*
* @example
* ```tsx
* // Good — minor override on a built-in component
* <CardBody style={mergeStyle({ padding: 16 })}>…</CardBody>
*
* // Bad — hand-rolled decorative styling
* <div style={mergeStyle(base, { background: "linear-gradient(…)" })}>…</div>
* ```
*/
export declare function mergeStyle(base: CSSProperties, override?: CSSProperties): CSSProperties;
export type StackProps = {
children?: ReactNode;
gap?: number;
style?: CSSProperties;
};
/**
* Vertical flex column. Use as the top-level page wrapper or to stack cards/sections.
*
* @example
* ```tsx
* <Stack gap={16}>
* <H1>Dashboard</H1>
* <Card>…</Card>
* <Card>…</Card>
* </Stack>
* ```
*/
export declare function Stack({ children, gap, style }: StackProps): JSX.Element;
export type RowProps = {
children?: ReactNode;
gap?: number;
align?: "start" | "center" | "end" | "stretch";
justify?: "start" | "center" | "end" | "space-between";
wrap?: boolean;
style?: CSSProperties;
};
/**
* Horizontal flex row. Use for inline groups of buttons, badges, or metadata.
*
* @example
* ```tsx
* <Row gap={8} align="center">
* <Button variant="primary">Save</Button>
* <Button variant="ghost">Cancel</Button>
* </Row>
* ```
*/
export declare function Row({ children, gap, align, justify, wrap, style }: RowProps): JSX.Element;
/**
* CSS Grid with tokenized gap. Prefer this over `Row` + `wrap` when you need a
* fixed number of equal-width columns: wrapped flex items can land on their
* own row and grow to full width (`flex-grow`), which is often surprising for
* boards and dashboards.
*/
export type GridProps = {
children?: ReactNode;
/**
* Equal columns: pass a number (uses `repeat(n, minmax(0, 1fr))`), or a CSS
* `grid-template-columns` string (e.g. `"1fr 2fr"` or `"minmax(0, 200px) 1fr"`).
*/
columns: number | string;
gap?: number;
align?: "start" | "center" | "end" | "stretch";
style?: CSSProperties;
};
export declare function Grid({ children, columns, gap, align, style }: GridProps): JSX.Element;
export type DividerProps = {
style?: CSSProperties;
};
/**
* Horizontal line for visually separating sections. Uses `stroke.tertiary`
* to match the Card/Table hairline weight.
*
* @example
* ```tsx
* <Stack>
* <Text>Section one</Text>
* <Divider />
* <Text>Section two</Text>
* </Stack>
* ```
*/
export declare function Divider({ style }: DividerProps): JSX.Element;
/**
* Flex spacer that pushes siblings apart. Place inside a `Row` to push
* trailing content to the right edge.
*
* @example
* ```tsx
* <Row>
* <Text>Title</Text>
* <Spacer />
* <Button variant="primary">Save</Button>
* </Row>
* ```
*/
export declare function Spacer(): JSX.Element;
/** Horizontal alignment for a table column. */
export type TableColumnAlign = "left" | "center" | "right";
/** Semantic tone for a table row — renders a translucent tinted background. */
export type TableRowTone = "success" | "danger" | "warning" | "info" | "neutral";
export type TableProps = {
/** Column titles, left to right. Column count is fixed by this array. */
headers: ReactNode[];
/**
* Body rows. Each row is an array of cells in the same order as `headers`.
* Shorter rows are padded with empty cells; extra cells are ignored.
*/
rows: ReactNode[][];
/** Optional alignment per column index (headers/rows). Defaults to left. */
columnAlign?: Array<TableColumnAlign | undefined>;
/**
* Optional semantic tone per row index. Applies a translucent tinted
* background — use for status highlighting (e.g. failing services, warnings).
* Sparse: `undefined` entries are uncolored.
*/
rowTone?: Array<TableRowTone | undefined>;
/** When true (default), bordered rounded shell with horizontal scroll if needed. */
framed?: boolean;
/** Alternate subtle fill on even rows for easier scanning in large tables. */
striped?: boolean;
/** Stick the header row when the framed container scrolls vertically. */
stickyHeader?: boolean;
style?: CSSProperties;
/** Shown in a single spanning cell when `rows` is empty. */
emptyMessage?: ReactNode;
};
/**
* Data table with column headers and rows. Framed by default with its own
* bordered container — **do not wrap in a Card** unless the card itself is
* a named entity that happens to contain a table. Render directly under a
* heading in the normal case.
*
* @example
* ```tsx
* // Good — table directly under a heading
* <H2>Active services</H2>
* <Table
* headers={["Service", "Status", "RPS"]}
* rows={[
* ["api-gateway", "Steady", "3.2k"],
* ["workers", "Hot", "8.1k"],
* ]}
* columnAlign={["left", "left", "right"]}
* />
*
* // Allowed — table inside a named-entity card
* <Card>
* <CardHeader>billing-service</CardHeader>
* <CardBody><Table headers={…} rows={…} /></CardBody>
* </Card>
* ```
*/
export declare function Table({ headers, rows, columnAlign, rowTone, framed, striped, stickyHeader, style, emptyMessage }: TableProps): JSX.Element;
export type TextWeight = "normal" | "medium" | "semibold" | "bold";
export type TextProps = {
children?: ReactNode;
tone?: "primary" | "secondary" | "tertiary" | "quaternary";
size?: "body" | "small";
/**
* Element tag to render. Defaults to `"p"` for top-level body copy and
* automatically switches to `"span"` when nested inside another typography
* container so inline emphasis stays valid HTML.
*/
as?: "p" | "span";
/** Font weight. Default is `"normal"` (400). Use `"semibold"` or `"bold"` for emphasis. */
weight?: TextWeight;
/** Render as italic. */
italic?: boolean;
/**
* Truncate overflowing text with an ellipsis on a single line.
* - `true` / `"end"` — ellipsis at the end (default truncation).
* - `"start"` — ellipsis at the start. Useful for file paths where the
* filename matters more than the directory prefix.
*
* Requires the parent to have a bounded width (flex child with
* `minWidth: 0`, fixed width, etc.) — otherwise the text just expands
* and never overflows.
*/
truncate?: boolean | "start" | "end";
style?: CSSProperties;
};
/**
* Body text with tone, size, weight, and italic variants.
*
* Top-level `Text` renders a `<p>`. Nested `Text` automatically renders a
* `<span>` so inline emphasis like `<Text>Use <Text weight="semibold">this</Text></Text>`
* does not emit invalid nested paragraphs. Use `as` to override when needed.
*
* Compose with `<Code>` for inline code and `<Link>` for hyperlinks inside
* the text flow.
*
* @example
* ```tsx
* <Text>Primary body text.</Text>
* <Text weight="semibold">Important note.</Text>
* <Text italic tone="secondary">Supplementary remark.</Text>
* <Text>Run <Code>npm install</Code> to get started.</Text>
* <Text>See the <Link href="https://example.com">docs</Link> for details.</Text>
* ```
*/
export declare function Text({ children, tone, size, as, weight, italic, truncate, style }: TextProps): JSX.Element;
export type H1Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Page-level heading. Use once at the top of a canvas.
* **Do not** place inside `CardHeader` — card headers use their own label.
*
* @example
* ```tsx
* <Stack>
* <H1>Performance Report</H1>
* <Card>…</Card>
* </Stack>
* ```
*/
export declare function H1({ children, style }: H1Props): JSX.Element;
export type H2Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Section heading. Use between groups of cards or sections.
* **Do not** place inside `CardHeader` — card headers use their own label.
*
* @example
* ```tsx
* <Stack>
* <H2>Recent activity</H2>
* <Card>…</Card>
* <Card>…</Card>
* </Stack>
* ```
*/
export declare function H2({ children, style }: H2Props): JSX.Element;
export type H3Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Sub-section heading. Use below `H2` for finer hierarchy.
*
* @example
* ```tsx
* <Stack>
* <H2>API Reference</H2>
* <H3>Authentication</H3>
* <Text>All requests require a bearer token.</Text>
* </Stack>
* ```
*/
export declare function H3({ children, style }: H3Props): JSX.Element;
export type CodeProps = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Inline `<code>` span for identifiers, file names, or short snippets.
* Uses `0.92em` so it scales with surrounding text (headings, body, etc.).
*
* Prefer writing backtick markdown inside `Text` — e.g. `` <Text>Run `npm install`</Text> `` —
* which is automatically parsed. Use `<Code>` only when you need an explicit element.
*
* @example
* ```tsx
* <Text>Run <Code>npm install</Code> to get started.</Text>
* ```
*/
export declare function Code({ children, style }: CodeProps): JSX.Element;
export type LinkProps = {
children?: ReactNode;
href: string;
style?: CSSProperties;
};
/**
* Inline link that opens in the user's default browser.
*
* Prefer writing markdown links inside `Text` — e.g. `<Text>See the [docs](url)</Text>` —
* which are automatically parsed. Use `<Link>` when you need an explicit anchor
* outside of a text flow or when composing with other elements.
*
* @example
* ```tsx
* <Link href="https://docs.example.com">View documentation</Link>
* ```
*/
export declare function Link({ children, href, style }: LinkProps): JSX.Element;
export type CardSize = "base" | "lg";
export type CardVariant = "default" | "borderless";
/**
* Inline chevron SVG used by disclosure-style controls (collapsible cards,
* expandable list items, etc.). Shared by `Card` and `todo-list.tsx` so
* every disclosure in the canvas SDK uses the same glyph.
*/
export declare function CanvasChevron({ expanded }: {
expanded: boolean;
}): JSX.Element;
export type CardProps = {
children?: ReactNode;
/** Default: bordered surface with radius; `borderless` removes both. */
variant?: CardVariant;
/** `lg` uses a taller header and roomier title padding (matches packages/ui). */
size?: CardSize;
/**
* When true, the header uses `position: sticky` so it stays visible while
* the card body scrolls. Requires the card (or a parent) to have a
* constrained height and `overflow: auto` — the canvas host controls this,
* so sticky behavior depends on the host viewport.
*/
stickyHeader?: boolean;
/**
* Make the card collapsible. The header becomes a clickable toggle with
* a leading chevron; `CardBody` renders nothing while the card is closed.
*/
collapsible?: boolean;
/** Initial open state in uncontrolled mode. Ignored when `open` is set. */
defaultOpen?: boolean;
/** Controlled open state. Pair with `onOpenChange`. */
open?: boolean;
/** Fires on every toggle with the next open state. */
onOpenChange?: (open: boolean) => void;
style?: CSSProperties;
};
/**
* Bordered surface for a **labeled, self-contained unit** — a file, a service,
* a config block, or a table with a title. Compose with `CardHeader` + `CardBody`.
*
* **When to use Card:**
* - Displaying a named entity (file path, service name, resource).
* - Wrapping a `<Table>` or `<DiffView>` that needs a title.
* - A distinct, bounded section the user might scan by header label.
*
* **When NOT to use Card:**
* - General text sections — use `<H2>` + `<Text>` instead. Not every section
* needs a border.
* - Page-level layout — use `<Stack>` with headings. A canvas should not be a
* wall of stacked cards.
* - Nesting — do not put cards inside cards. Use `<Divider>` within a card body.
*
* Pass **plain text** as `CardHeader` children — the header provides its own
* 12px font. Do **not** put `<H1>` or `<H2>` inside a card header.
*
* Set `collapsible` to make the header a toggle that shows/hides `CardBody`.
*
* @example
* ```tsx
* // Card wraps a titled diff
* <Card>
* <CardHeader trailing={<DiffStats additions={5} deletions={2} />}>
* src/utils.ts
* </CardHeader>
* <CardBody style={{ padding: 0 }}>
* <DiffView path="src/utils.ts" lines={lines} />
* </CardBody>
* </Card>
*
* // Collapsible card
* <Card collapsible defaultOpen={false}>
* <CardHeader>deploy-service.ts</CardHeader>
* <CardBody>Service handles rolling deployments across regions.</CardBody>
* </Card>
*
* // Bad — card wrapping plain text that should just be a heading
* // Use <H2>Overview</H2><Text>…</Text> instead.
* ```
*/
export declare function Card({ children, variant, size, stickyHeader, collapsible, defaultOpen, open: openProp, onOpenChange, style }: CardProps): JSX.Element;
export type CardHeaderProps = {
/** Plain text title. Do **not** pass headings, buttons, pills, or layout rows. */
children?: ReactNode;
/** Small trailing content aligned to the right edge — a status label, a
* single pill, or a short metadata string. Keep it compact. */
trailing?: ReactNode;
style?: CSSProperties;
};
/**
* 28px header row (32px at `size="lg"`). A compact label for the card.
*
* **`children`** — plain text only. This is a 12px label, not a toolbar.
* Do **not** pass `<H1>`, `<H2>`, `<Text weight="bold">`, `<Pill>`,
* `<Button>`, `<Row>`, or any interactive/layout elements as children.
*
* **`trailing`** — optional right-aligned slot for small status indicators
* (a short label, a single `<Pill>`, a metadata string like a timestamp).
*
* @example
* ```tsx
* // Good — plain title
* <CardHeader>config.yaml</CardHeader>
*
* // Good — title with trailing status
* <CardHeader trailing={<Pill active>Healthy</Pill>}>
* billing-service
* </CardHeader>
*
* // Bad — heading in header (use CardHeader text, not H2)
* // Bad — buttons in header (no room, wrong context)
* // Bad — multiple pills in header (use trailing for one, or move to CardBody)
* ```
*/
export declare function CardHeader({ children, trailing, style }: CardHeaderProps): JSX.Element;
export type CardBodyProps = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Padded content area inside a Card.
* Override `style` to adjust padding for custom layouts.
*
* @example
* ```tsx
* <Card>
* <CardHeader>Overview</CardHeader>
* <CardBody>This service is currently healthy.</CardBody>
* </Card>
* ```
*/
export declare function CardBody({ children, style }: CardBodyProps): JSX.Element | null;
export type ButtonProps = {
children?: ReactNode;
variant?: "primary" | "secondary" | "ghost";
disabled?: boolean;
type?: "button" | "submit" | "reset";
style?: CSSProperties;
onClick?: () => void;
};
/**
* Action button (24px height, sized to its label). **Never stretch to full
* width** — buttons are always inline and hug their text.
*
* @example
* ```tsx
* <Row gap={8}>
* <Button variant="primary" onClick={handleSave}>Save</Button>
* <Button variant="secondary">Export</Button>
* <Button variant="ghost">Cancel</Button>
* </Row>
* ```
*/
export declare function Button({ children, variant, disabled, type, style, onClick }: ButtonProps): JSX.Element;
export type PillTone = "neutral" | "added" | "deleted" | "renamed" | "success" | "warning" | "info";
export type PillSize = "sm" | "md";
export type PillProps = {
children?: ReactNode;
/** Whether the pill is in its selected/active state (filled background). */
active?: boolean;
/**
* Semantic tone. Recolors the border and text. When `active` is also
* set, fills the background with the tone color at low opacity.
* Defaults to `neutral` (current stroke/text tokens).
*/
tone?: PillTone;
/**
* Visual size. `"md"` (default) is the standard pill. `"sm"` is a
* compact variant with smaller text, tighter padding, and no border —
* designed for tight spaces like `CardHeader` trailing slots.
*/
size?: PillSize;
/** Shown before the label (icon, emoji, etc.). */
leadingContent?: ReactNode;
/** e.g. shortcut hint — matches ui `Pill` ghost keyboard hint (muted primary). */
keyboardHint?: string;
disabled?: boolean;
title?: string;
style?: CSSProperties;
onClick?: () => void;
};
/**
* Pill-shaped label or toggle button. Use for tab bars, filter groups, or
* action suggestions. Set `active` for the selected state (filled background).
*
* @example
* ```tsx
* // Tab-style selector
* <Row gap={8}>
* {tabs.map(tab => (
* <Pill key={tab} active={tab === selected} onClick={() => setSelected(tab)}>
* {tab}
* </Pill>
* ))}
* </Row>
*
* // Action suggestion with shortcut hint
* <Pill onClick={handlePlan} keyboardHint="⇧Tab">Plan new idea</Pill>
* ```
*/
export declare function Pill({ children, active, tone, size, leadingContent, keyboardHint, disabled, title, style, onClick }: PillProps): JSX.Element;
export type StatTone = "success" | "danger" | "warning" | "info";
export type StatProps = {
/** The primary metric value (number, percentage, short string). */
value: ReactNode;
/** Label below the value. */
label: string;
/** Semantic color for the value. Omit for default primary text. */
tone?: StatTone;
style?: CSSProperties;
};
/**
* Single metric display — a large value with a compact label beneath it.
* Use inside `<Grid>` for dashboard summary strips.
*
* @example
* ```tsx
* <Grid columns={3} gap={16}>
* <Stat value="4" label="Healthy" tone="success" />
* <Stat value="1" label="Degraded" tone="warning" />
* <Stat value="99.2%" label="Avg Uptime" />
* </Grid>
* ```
*/
export declare function Stat({ value, label, tone, style }: StatProps): JSX.Element;
export type CalloutTone = "info" | "success" | "warning" | "danger" | "neutral";
export type CalloutProps = {
/** Body content. Plain strings, `<Text>`, `<Code>`, `<Link>`, or short lists. */
children?: ReactNode;
/** Semantic tone. Recolors the border, background tint, and title text. */
tone?: CalloutTone;
/** Optional bold title line, shown above the body in the tone color. */
title?: ReactNode;
/** Optional leading icon (emoji, inline SVG, or short text glyph). */
icon?: ReactNode;
style?: CSSProperties;
};
/**
* Tinted, bordered notice block for warnings, tips, or short status messages
* inline within a section.
*
* @example
* ```tsx
* <Callout tone="warning" title="Heads up">
* Rolling deploy is in progress. Metrics may be noisy for 10 minutes.
* </Callout>
* ```
*/
export declare function Callout({ children, tone, title, icon, style }: CalloutProps): JSX.Element;
//# sourceMappingURL=ui-primitives.d.ts.map

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=ui-primitives.test.d.ts.map

56
skills-cursor/canvas/sdk/usage-bar.d.ts vendored Normal file
View File

@@ -0,0 +1,56 @@
/**
* Segmented usage bar primitive — proportional pills + remainder, with an
* optional one-line label row above. Visually matches `packages/ui`
* `ContextUsageTray`'s usage bar, themed for canvas via the shared `Color`
* palette and `useHostTheme()` semantic tokens.
*/
import type { CSSProperties, JSX, ReactNode } from "react";
import { type Color } from "./canvas-tokens.js";
export interface UsageBarSegment {
/** Stable identifier (used as React key). */
readonly id: string;
/** Proportional weight for this segment. Non-finite or `<= 0` is treated as 0. */
readonly value: number;
/**
* Optional explicit color. Defaults to a rotation through
* `usageColorSequence` by index, matching the original `packages/ui`
* `ContextUsageTray` order so the same index lands on the same hue.
*/
readonly color?: Color;
}
export type UsageBarProps = {
/** Segments rendered left-to-right; widths are proportional to `value`. */
readonly segments: readonly UsageBarSegment[];
/** Total weight of the bar. The remainder span fills `max(0, total - sum(values))`. */
readonly total: number;
/** Optional small label rendered above the bar, left-aligned. */
readonly topLeftLabel?: ReactNode;
/** Optional small label rendered above the bar, right-aligned. */
readonly topRightLabel?: ReactNode;
readonly style?: CSSProperties;
};
/**
* Segmented horizontal usage bar — proportional category pills with a
* remainder span. Use to visualize a fixed-budget breakdown (context window
* tokens, storage usage, etc.).
*
* @example
* ```tsx
* <UsageBar
* total={120_000}
* topLeftLabel="64% Full"
* topRightLabel="76.8K / 120K Tokens"
* segments={[
* { id: "system", value: 8_000, color: "gray" },
* { id: "tools", value: 24_000, color: "purple" },
* { id: "rules", value: 12_000, color: "green" },
* { id: "skills", value: 6_000, color: "yellow" },
* { id: "mcp", value: 4_000, color: "pink" },
* { id: "subagents", value: 8_800, color: "blue" },
* { id: "conversation", value: 14_000, color: "orange" },
* ]}
* />
* ```
*/
export declare function UsageBar({ segments, total, topLeftLabel, topRightLabel, style }: UsageBarProps): JSX.Element;
//# sourceMappingURL=usage-bar.d.ts.map

View File

@@ -0,0 +1,239 @@
---
name: create-hook
description: >-
Create Cursor hooks. Use when you want to create a hook, write hooks.json, add
hook scripts, or automate behavior around agent events.
---
# Creating Cursor Hooks
Create hooks when you want Cursor to run custom logic before or after agent events. Hooks are scripts or prompt-based checks that exchange JSON over stdin/stdout and can observe, block, modify, or follow up on behavior.
When the user asks for a hook, don't stop at describing the format. Gather the missing requirements, then create or update the hook files directly.
## Gather Requirements
Before you write anything, determine:
1. **Scope**: Should this be a project hook or a user hook?
2. **Trigger**: Which event should run the hook?
3. **Behavior**: Should it audit, deny/allow, rewrite input, inject context, or continue a workflow?
4. **Implementation**: Should it be a command hook (script) or a prompt hook?
5. **Filtering**: Does it need a matcher so it only runs for certain tools, commands, or subagent types?
6. **Safety**: Should failures fail open or fail closed?
Infer these from the conversation when possible. Only ask for the missing pieces.
## Choose the Right Location
- **Project hooks**: `.cursor/hooks.json` and `.cursor/hooks/*`
- **User hooks**: `~/.cursor/hooks.json` and `~/.cursor/hooks/*`
Path behavior matters:
- **Project hooks** run from the project root, so use paths like `.cursor/hooks/my-hook.sh`
- **User hooks** run from `~/.cursor/`, so use paths like `./hooks/my-hook.sh` or `hooks/my-hook.sh`
Prefer **project hooks** when the behavior should be shared with the repository and checked into version control.
## Choose the Hook Event
Use the narrowest event that matches the user's goal.
### Common Agent events
- `sessionStart`, `sessionEnd`: set up or audit a session
- `preToolUse`, `postToolUse`, `postToolUseFailure`: work across all tools
- `subagentStart`, `subagentStop`: control or continue Task/subagent workflows
- `beforeShellExecution`, `afterShellExecution`: gate or audit terminal commands
- `beforeMCPExecution`, `afterMCPExecution`: gate or audit MCP tool calls
- `beforeReadFile`, `afterFileEdit`: control file reads or post-process edits
- `beforeSubmitPrompt`: validate prompts before they are sent
- `preCompact`: observe context compaction
- `stop`: handle agent completion
- `afterAgentResponse`, `afterAgentThought`: track agent output or reasoning
### Tab events
- `beforeTabFileRead`: control file access for inline completions
- `afterTabFileEdit`: post-process edits made by Tab
### Quick event chooser
- **Block or approve shell commands** -> `beforeShellExecution`
- **Audit shell output** -> `afterShellExecution`
- **Format files after edits** -> `afterFileEdit`
- **Block or rewrite a specific tool call** -> `preToolUse`
- **Add follow-up context after a tool succeeds** -> `postToolUse`
- **Control whether subagents can run** -> `subagentStart`
- **Chain subagent loops** -> `subagentStop`
- **Check prompts for secrets or policy violations** -> `beforeSubmitPrompt`
- **Protect MCP calls** -> `beforeMCPExecution`
## Hooks File Format
Create a `hooks.json` file with schema version 1:
```json
{
"version": 1,
"hooks": {
"afterFileEdit": [
{
"command": ".cursor/hooks/format.sh"
}
]
}
}
```
Each hook definition can include:
- `command`: shell command or script path
- `type`: `"command"` or `"prompt"` (defaults to `"command"`)
- `timeout`: timeout in seconds
- `matcher`: filter for when the hook runs
- `failClosed`: block the action when the hook crashes, times out, or returns invalid JSON
- `loop_limit`: mainly for `stop` and `subagentStop` follow-up loops
## Matchers
Use matchers to avoid running the hook on every event.
- `preToolUse` / `postToolUse` / `postToolUseFailure`: match on tool type such as `Shell`, `Read`, `Write`, `Task`, or MCP tools in `MCP: ...` form
- `subagentStart` / `subagentStop`: match on subagent type such as `generalPurpose`, `explore`, or `shell`
- `beforeShellExecution` / `afterShellExecution`: match on the full shell command string
- `beforeReadFile`: match on tool type such as `Read` or `TabRead`
- `afterFileEdit`: match on tool type such as `Write` or `TabWrite`
- `beforeSubmitPrompt`: matches the value `UserPromptSubmit`
Important matcher warning:
- Matchers use JavaScript-style regular expressions, not POSIX/grep syntax
- Do not use POSIX classes like `[[:space:]]`; use JavaScript equivalents like `\s`
- If the matcher is at all tricky, start by getting the hook working without one or with a very simple matcher, then tighten it after the hook is confirmed to load and fire
If the user wants a hook for only one risky command family, prefer script-side filtering for the first working version and add a matcher afterward only if it is simple and clearly correct.
## Command Hooks
Command hooks are the default. They receive JSON on stdin and can return JSON on stdout.
Before using a command hook, verify that every executable it depends on will actually run in the hook environment:
- the script itself has a valid shebang and is executable
- any helper binary it calls is already installed and on `$PATH`
- if the script depends on tools like `jq`, `python3`, `node`, or repo-local CLIs, verify that explicitly before finishing
Do not assume a binary exists just because it is common on your machine.
### Minimal project-level example
```json
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": ".cursor/hooks/approve-network.sh",
"matcher": "curl|wget|nc ",
"failClosed": true
}
]
}
}
```
```bash
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.command // empty')
if [[ "$command" =~ curl|wget|nc ]]; then
echo '{
"permission": "ask",
"user_message": "This command may make a network request. Please review it before continuing.",
"agent_message": "A hook flagged this shell command as a possible network call."
}'
exit 0
fi
echo '{ "permission": "allow" }'
exit 0
```
Important behavior:
- Exit code `0`: success
- Exit code `2`: block the action, same as returning deny
- Other non-zero exit codes: fail open by default unless `failClosed: true`
Always make hook scripts executable after creating them.
## Prompt Hooks
Prompt hooks are useful when the policy is easier to describe than to script.
```json
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"type": "prompt",
"prompt": "Does this command look safe to execute? Only allow read-only operations. Here is the hook input: $ARGUMENTS",
"timeout": 10
}
]
}
}
```
Use prompt hooks for lightweight policy decisions. Prefer command hooks when the logic must be deterministic or when the user needs exact, auditable behavior.
## Event Output Cheat Sheet
Use the event's supported output fields only.
- `preToolUse`: can return `permission`, `user_message`, `agent_message`, and `updated_input`
- `postToolUse`: can return `additional_context`; for MCP tools it can also return `updated_mcp_tool_output`
- `subagentStart`: can return `permission` and `user_message`
- `subagentStop`: can return `followup_message`
- `beforeShellExecution` / `beforeMCPExecution`: can return `permission`, `user_message`, and `agent_message`
When the user wants to rewrite a tool call, prefer `preToolUse`. When they want to gate only shell commands, prefer `beforeShellExecution`.
## Implementation Workflow
1. Pick the correct location and event
2. Create or update the correct `hooks.json` file
3. Start with no matcher or the simplest safe matcher
4. Create the script under the matching hooks directory
5. Read stdin JSON and implement the required behavior
6. Make the script executable
7. Verify any helper executables the script uses are installed and on `$PATH`
8. Trigger the relevant action to test the hook
9. Verify behavior in Cursor's **Hooks** settings tab or the **Hooks** output channel
If you are editing an existing hooks setup, preserve unrelated hooks and only change the minimum necessary entries.
## Validation and Troubleshooting
- Cursor watches `hooks.json` and reloads on save
- If hooks still do not load, restart Cursor
- Double-check relative paths:
- project hooks -> relative to the project root
- user hooks -> relative to `~/.cursor/`
- If the hook does not appear to load at all, suspect matcher/config parsing first; remove the matcher and confirm the base hook works before tightening it
- If the script runs external commands, verify each one is installed and reachable from the hook process with `command -v` or equivalent
- If the hook should block on failure, set `failClosed: true`
- If a command hook should intentionally block, returning exit code `2` is valid
## Final Checklist
- [ ] Used the correct hook location and path style
- [ ] Chose the narrowest correct event
- [ ] Added a matcher when appropriate
- [ ] Returned only fields supported by that hook event
- [ ] Made the script executable
- [ ] Tested the hook by triggering the real event
- [ ] Checked the Hooks tab or Hooks output channel if debugging was needed

View File

@@ -0,0 +1,164 @@
---
name: create-rule
description: >-
Create Cursor rules for persistent AI guidance. Use when you want to create a
rule, add coding standards, set up project conventions, configure
file-specific patterns, create RULE.md files, or asks about .cursor/rules/ or
AGENTS.md.
---
# Creating Cursor Rules
Create project rules in `.cursor/rules/` to provide persistent context for the AI agent.
## Gather Requirements
Before creating a rule, determine:
1. **Purpose**: What should this rule enforce or teach?
2. **Scope**: Should it always apply, or only for specific files?
3. **File patterns**: If file-specific, which glob patterns?
### Inferring from Context
If you have previous conversation context, infer rules from what was discussed. You can create multiple rules if the conversation covers distinct topics or patterns. Don't ask redundant questions if the context already provides the answers.
### Required Questions
If the user hasn't specified scope, ask:
- "Should this rule always apply, or only when working with specific files?"
If they mentioned specific files and haven't provided concrete patterns, ask:
- "Which file patterns should this rule apply to?" (e.g., `**/*.ts`, `backend/**/*.py`)
It's very important that we get clarity on the file patterns.
Use the AskQuestion tool when available to gather this efficiently.
---
## Rule File Format
Rules are `.mdc` files in `.cursor/rules/` with YAML frontmatter:
```
.cursor/rules/
typescript-standards.mdc
react-patterns.mdc
api-conventions.mdc
```
### File Structure
```markdown
---
description: Brief description of what this rule does
globs: **/*.ts # File pattern for file-specific rules
alwaysApply: false # Set to true if rule should always apply
---
# Rule Title
Your rule content here...
```
### Frontmatter Fields
| Field | Type | Description |
|-------|------|-------------|
| `description` | string | What the rule does (shown in rule picker) |
| `globs` | string | File pattern - rule applies when matching files are open |
| `alwaysApply` | boolean | If true, applies to every session |
---
## Rule Configurations
### Always Apply
For universal standards that should apply to every conversation:
```yaml
---
description: Core coding standards for the project
alwaysApply: true
---
```
### Apply to Specific Files
For rules that apply when working with certain file types:
```yaml
---
description: TypeScript conventions for this project
globs: **/*.ts
alwaysApply: false
---
```
---
## Best Practices
### Keep Rules Concise
- **Under 50 lines**: Rules should be concise and to the point
- **One concern per rule**: Split large rules into focused pieces
- **Actionable**: Write like clear internal docs
- **Concrete examples**: Ideally provide concrete examples of how to fix issues
---
## Example Rules
### TypeScript Standards
```markdown
---
description: TypeScript coding standards
globs: **/*.ts
alwaysApply: false
---
# Error Handling
\`\`\`typescript
// ❌ BAD
try {
await fetchData();
} catch (e) {}
// ✅ GOOD
try {
await fetchData();
} catch (e) {
logger.error('Failed to fetch', { error: e });
throw new DataFetchError('Unable to retrieve data', { cause: e });
}
\`\`\`
```
### React Patterns
```markdown
---
description: React component patterns
globs: **/*.tsx
alwaysApply: false
---
# React Patterns
- Use functional components
- Extract custom hooks for reusable logic
- Colocate styles with components
```
---
## Checklist
- [ ] File is `.mdc` format in `.cursor/rules/`
- [ ] Frontmatter configured correctly
- [ ] Content under 500 lines
- [ ] Includes concrete examples

View File

@@ -0,0 +1,504 @@
---
name: create-skill
description: >-
Create Cursor Agent Skills. Use when authoring a new skill or asking about
SKILL.md structure.
---
# Creating Skills in Cursor
This skill guides you through creating effective Agent Skills for Cursor. Skills are markdown files that teach the agent how to perform specific tasks: reviewing PRs using team standards, generating commit messages in a preferred format, querying database schemas, or any specialized workflow.
## Before You Begin: Gather Requirements
Before creating a skill, gather essential information from the user about:
1. **Purpose and scope**: What specific task or workflow should this skill help with?
2. **Target location**: Should this be a personal skill (~/.cursor/skills/) or project skill (.cursor/skills/)?
3. **Trigger scenarios**: When should the agent automatically apply this skill?
4. **Key domain knowledge**: What specialized information does the agent need that it wouldn't already know?
5. **Output format preferences**: Are there specific templates, formats, or styles required?
6. **Existing patterns**: Are there existing examples or conventions to follow?
### Verbatim text from the user
If the user includes exact wording to use in the skill, respect it and use it **verbatim** in `SKILL.md` (same words, same order). Do not paraphrase, soften, or expand their copy, and do not add unrequested headings or commentary around it.
### Inferring from Context
If you have previous conversation context, infer the skill from what was discussed. You can create skills based on workflows, patterns, or domain knowledge that emerged in the conversation.
### Gathering Additional Information
If you need clarification, use the AskQuestion tool when available:
```
Example AskQuestion usage:
- "Where should this skill be stored?" with options like ["Personal (~/.cursor/skills/)", "Project (.cursor/skills/)"]
- "Should this skill include executable scripts?" with options like ["Yes", "No"]
```
If the AskQuestion tool is not available, ask these questions conversationally.
---
## Skill File Structure
### Directory Layout
Skills are stored as directories containing a `SKILL.md` file:
```
skill-name/
├── SKILL.md # Required - main instructions
├── reference.md # Optional - detailed documentation
├── examples.md # Optional - usage examples
└── scripts/ # Optional - utility scripts
├── validate.py
└── helper.sh
```
### Storage Locations
| Type | Path | Scope |
|------|------|-------|
| Personal | ~/.cursor/skills/skill-name/ | Available across all your projects |
| Project | .cursor/skills/skill-name/ | Shared with anyone using the repository |
**IMPORTANT**: Never create skills in `~/.cursor/skills-cursor/`. This directory is reserved for Cursor's internal built-in skills and is managed automatically by the system.
### SKILL.md Structure
Every skill requires a `SKILL.md` file with YAML frontmatter and markdown body:
```markdown
---
name: your-skill-name
description: Brief description of what this skill does and when to use it
disable-model-invocation: true
---
# Your Skill Name
## Instructions
Clear, step-by-step guidance for the agent.
## Examples
Concrete examples of using this skill.
```
Default `disable-model-invocation: true` so the skill only loads when named explicitly. Omit it only when the agent should auto-invoke from ambient context.
### Required Metadata Fields
| Field | Requirements | Purpose |
|-------|--------------|---------|
| `name` | Max 64 chars, lowercase letters/numbers/hyphens only | Unique identifier for the skill |
| `description` | Max 1024 chars, non-empty | Helps agent decide when to apply the skill |
---
## Writing Effective Descriptions
The description is **critical** for skill discovery. The agent uses it to decide when to apply your skill.
### Description Best Practices
1. **Write in third person** (the description is injected into the system prompt):
- ✅ Good: "Processes Excel files and generates reports"
- ❌ Avoid: "I can help you process Excel files"
- ❌ Avoid: "You can use this to process Excel files"
2. **Be specific and include trigger terms**:
- ✅ Good: "Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction."
- ❌ Vague: "Helps with documents"
3. **Include both WHAT and WHEN**:
- WHAT: What the skill does (specific capabilities)
- WHEN: When the agent should use it (trigger scenarios)
### Description Examples
```yaml
# PDF Processing
description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
# Excel Analysis
description: Analyze Excel spreadsheets, create pivot tables, generate charts. Use when analyzing Excel files, spreadsheets, tabular data, or .xlsx files.
# Git Commit Helper
description: Generate descriptive commit messages by analyzing git diffs. Use when the user asks for help writing commit messages or reviewing staged changes.
# Code Review
description: Review code for quality, security, and best practices following team standards. Use when reviewing pull requests, code changes, or when the user asks for a code review.
```
---
## Core Authoring Principles
### 1. Concise is Key
The context window is shared with conversation history, other skills, and requests. Every token competes for space.
**Default assumption**: The agent is already very smart. Only add context it doesn't already have.
Challenge each piece of information:
- "Does the agent really need this explanation?"
- "Can I assume the agent knows this?"
- "Does this paragraph justify its token cost?"
**Good (concise)**:
```markdown
## Extract PDF text
Use pdfplumber for text extraction:
\`\`\`python
import pdfplumber
with pdfplumber.open("file.pdf") as pdf:
text = pdf.pages[0].extract_text()
\`\`\`
```
**Bad (verbose)**:
```markdown
## Extract PDF text
PDF (Portable Document Format) files are a common file format that contains
text, images, and other content. To extract text from a PDF, you'll need to
use a library. There are many libraries available for PDF processing, but we
recommend pdfplumber because it's easy to use and handles most cases well...
```
### 2. Keep SKILL.md Under 500 Lines
For optimal performance, the main SKILL.md file should be concise. Use progressive disclosure for detailed content.
### 3. Progressive Disclosure
Put essential information in SKILL.md; detailed reference material in separate files that the agent reads only when needed.
```markdown
# PDF Processing
## Quick start
[Essential instructions here]
## Additional resources
- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)
```
**Keep references one level deep** - link directly from SKILL.md to reference files. Deeply nested references may result in partial reads.
### 4. Set Appropriate Degrees of Freedom
Match specificity to the task's fragility:
| Freedom Level | When to Use | Example |
|---------------|-------------|---------|
| **High** (text instructions) | Multiple valid approaches, context-dependent | Code review guidelines |
| **Medium** (pseudocode/templates) | Preferred pattern with acceptable variation | Report generation |
| **Low** (specific scripts) | Fragile operations, consistency critical | Database migrations |
---
## Common Patterns
### Template Pattern
Provide output format templates:
```markdown
## Report structure
Use this template:
\`\`\`markdown
# [Analysis Title]
## Executive summary
[One-paragraph overview of key findings]
## Key findings
- Finding 1 with supporting data
- Finding 2 with supporting data
## Recommendations
1. Specific actionable recommendation
2. Specific actionable recommendation
\`\`\`
```
### Examples Pattern
For skills where output quality depends on seeing examples:
```markdown
## Commit message format
**Example 1:**
Input: Added user authentication with JWT tokens
Output:
\`\`\`
feat(auth): implement JWT-based authentication
Add login endpoint and token validation middleware
\`\`\`
**Example 2:**
Input: Fixed bug where dates displayed incorrectly
Output:
\`\`\`
fix(reports): correct date formatting in timezone conversion
Use UTC timestamps consistently across report generation
\`\`\`
```
### Workflow Pattern
Break complex operations into clear steps with checklists:
```markdown
## Form filling workflow
Copy this checklist and track progress:
\`\`\`
Task Progress:
- [ ] Step 1: Analyze the form
- [ ] Step 2: Create field mapping
- [ ] Step 3: Validate mapping
- [ ] Step 4: Fill the form
- [ ] Step 5: Verify output
\`\`\`
**Step 1: Analyze the form**
Run: \`python scripts/analyze_form.py input.pdf\`
...
```
### Conditional Workflow Pattern
Guide through decision points:
```markdown
## Document modification workflow
1. Determine the modification type:
**Creating new content?** → Follow "Creation workflow" below
**Editing existing content?** → Follow "Editing workflow" below
2. Creation workflow:
- Use docx-js library
- Build document from scratch
...
```
### Feedback Loop Pattern
For quality-critical tasks, implement validation loops:
```markdown
## Document editing process
1. Make your edits
2. **Validate immediately**: \`python scripts/validate.py output/\`
3. If validation fails:
- Review the error message
- Fix the issues
- Run validation again
4. **Only proceed when validation passes**
```
---
## Utility Scripts
Pre-made scripts offer advantages over generated code:
- More reliable than generated code
- Save tokens (no code in context)
- Save time (no code generation)
- Ensure consistency across uses
```markdown
## Utility scripts
**analyze_form.py**: Extract all form fields from PDF
\`\`\`bash
python scripts/analyze_form.py input.pdf > fields.json
\`\`\`
**validate.py**: Check for errors
\`\`\`bash
python scripts/validate.py fields.json
# Returns: "OK" or lists conflicts
\`\`\`
```
Make clear whether the agent should **execute** the script (most common) or **read** it as reference.
---
## Anti-Patterns to Avoid
### 1. Windows-Style Paths
- ✅ Use: `scripts/helper.py`
- ❌ Avoid: `scripts\helper.py`
### 2. Too Many Options
```markdown
# Bad - confusing
"You can use pypdf, or pdfplumber, or PyMuPDF, or..."
# Good - provide a default with escape hatch
"Use pdfplumber for text extraction.
For scanned PDFs requiring OCR, use pdf2image with pytesseract instead."
```
### 3. Time-Sensitive Information
```markdown
# Bad - will become outdated
"If you're doing this before August 2025, use the old API."
# Good - use an "old patterns" section
## Current method
Use the v2 API endpoint.
## Old patterns (deprecated)
<details>
<summary>Legacy v1 API</summary>
...
</details>
```
### 4. Inconsistent Terminology
Choose one term and use it throughout:
- ✅ Always "API endpoint" (not mixing "URL", "route", "path")
- ✅ Always "field" (not mixing "box", "element", "control")
### 5. Vague Skill Names
- ✅ Good: `processing-pdfs`, `analyzing-spreadsheets`
- ❌ Avoid: `helper`, `utils`, `tools`
---
## Skill Creation Workflow
When helping a user create a skill, follow this process:
### Phase 1: Discovery
Gather information about:
1. The skill's purpose and primary use case
2. Storage location (personal vs project)
3. Trigger scenarios
4. Any specific requirements or constraints
5. Existing examples or patterns to follow
If you have access to the AskQuestion tool, use it for efficient structured gathering. Otherwise, ask conversationally.
### Phase 2: Design
1. Draft the skill name (lowercase, hyphens, max 64 chars)
2. Write a specific, third-person description
3. Outline the main sections needed
4. Identify if supporting files or scripts are needed
### Phase 3: Implementation
1. Create the directory structure
2. Write the SKILL.md file with frontmatter
3. Create any supporting reference files
4. Create any utility scripts if needed
### Phase 4: Verification
1. Verify the SKILL.md is under 500 lines
2. Check that the description is specific and includes trigger terms
3. Ensure consistent terminology throughout
4. Verify all file references are one level deep
5. Test that the skill can be discovered and applied
---
## Complete Example
Here's a complete example of a well-structured skill:
**Directory structure:**
```
code-review/
├── SKILL.md
├── STANDARDS.md
└── examples.md
```
**SKILL.md:**
```markdown
---
name: code-review
description: Review code for quality, security, and maintainability following team standards. Use when reviewing pull requests, examining code changes, or when the user asks for a code review.
---
# Code Review
## Quick Start
When reviewing code:
1. Check for correctness and potential bugs
2. Verify security best practices
3. Assess code readability and maintainability
4. Ensure tests are adequate
## Review Checklist
- [ ] Logic is correct and handles edge cases
- [ ] No security vulnerabilities (SQL injection, XSS, etc.)
- [ ] Code follows project style conventions
- [ ] Functions are appropriately sized and focused
- [ ] Error handling is comprehensive
- [ ] Tests cover the changes
## Providing Feedback
Format feedback as:
- 🔴 **Critical**: Must fix before merge
- 🟡 **Suggestion**: Consider improving
- 🟢 **Nice to have**: Optional enhancement
## Additional Resources
- For detailed coding standards, see [STANDARDS.md](STANDARDS.md)
- For example reviews, see [examples.md](examples.md)
```
---
## Summary Checklist
Before finalizing a skill, verify:
### Core Quality
- [ ] Description is specific and includes key terms
- [ ] Description includes both WHAT and WHEN
- [ ] Written in third person
- [ ] SKILL.md body is under 500 lines
- [ ] Consistent terminology throughout
- [ ] Examples are concrete, not abstract
### Structure
- [ ] File references are one level deep
- [ ] Progressive disclosure used appropriately
- [ ] Workflows have clear steps
- [ ] No time-sensitive information
### If Including Scripts
- [ ] Scripts solve problems rather than punt
- [ ] Required packages are documented
- [ ] Error handling is explicit and helpful
- [ ] No Windows-style paths

View File

@@ -0,0 +1,225 @@
---
name: create-subagent
description: >-
Create custom subagents for specialized AI tasks. Use when you want to create
a new type of subagent, set up task-specific agents, configure code reviewers,
debuggers, or domain-specific assistants with custom prompts.
disable-model-invocation: true
---
# Creating Custom Subagents
This skill guides you through creating custom subagents for Cursor. Subagents are specialized AI assistants that run in isolated contexts with custom system prompts.
## When to Use Subagents
Subagents help you:
- **Preserve context** by isolating exploration from your main conversation
- **Specialize behavior** with focused system prompts for specific domains
- **Reuse configurations** across projects with user-level subagents
### Inferring from Context
If you have previous conversation context, infer the subagent's purpose and behavior from what was discussed. Create the subagent based on specialized tasks or workflows that emerged in the conversation.
## Subagent Locations
| Location | Scope | Priority |
|----------|-------|----------|
| `.cursor/agents/` | Current project | Higher |
| `~/.cursor/agents/` | All your projects | Lower |
When multiple subagents share the same name, the higher-priority location wins.
**Project subagents** (`.cursor/agents/`): Ideal for codebase-specific agents. Check into version control to share with your team.
**User subagents** (`~/.cursor/agents/`): Personal agents available across all your projects.
## Subagent File Format
Create a `.md` file with YAML frontmatter and a markdown body (the system prompt):
```markdown
---
name: code-reviewer
description: Reviews code for quality and best practices
---
You are a code reviewer. When invoked, analyze the code and provide
specific, actionable feedback on quality, security, and best practices.
```
### Required Fields
| Field | Description |
|-------|-------------|
| `name` | Unique identifier (lowercase letters and hyphens only) |
| `description` | When to delegate to this subagent (be specific!) |
## Writing Effective Descriptions
The description is **critical** - the AI uses it to decide when to delegate.
```yaml
# ❌ Too vague
description: Helps with code
# ✅ Specific and actionable
description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code.
```
Include "use proactively" to encourage automatic delegation.
## Example Subagents
### Code Reviewer
```markdown
---
name: code-reviewer
description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code.
---
You are a senior code reviewer ensuring high standards of code quality and security.
When invoked:
1. Run git diff to see recent changes
2. Focus on modified files
3. Begin review immediately
Review checklist:
- Code is clear and readable
- Functions and variables are well-named
- No duplicated code
- Proper error handling
- No exposed secrets or API keys
- Input validation implemented
- Good test coverage
- Performance considerations addressed
Provide feedback organized by priority:
- Critical issues (must fix)
- Warnings (should fix)
- Suggestions (consider improving)
Include specific examples of how to fix issues.
```
### Debugger
```markdown
---
name: debugger
description: Debugging specialist for errors, test failures, and unexpected behavior. Use proactively when encountering any issues.
---
You are an expert debugger specializing in root cause analysis.
When invoked:
1. Capture error message and stack trace
2. Identify reproduction steps
3. Isolate the failure location
4. Implement minimal fix
5. Verify solution works
Debugging process:
- Analyze error messages and logs
- Check recent code changes
- Form and test hypotheses
- Add strategic debug logging
- Inspect variable states
For each issue, provide:
- Root cause explanation
- Evidence supporting the diagnosis
- Specific code fix
- Testing approach
- Prevention recommendations
Focus on fixing the underlying issue, not the symptoms.
```
### Data Scientist
```markdown
---
name: data-scientist
description: Data analysis expert for SQL queries, BigQuery operations, and data insights. Use proactively for data analysis tasks and queries.
---
You are a data scientist specializing in SQL and BigQuery analysis.
When invoked:
1. Understand the data analysis requirement
2. Write efficient SQL queries
3. Use BigQuery command line tools (bq) when appropriate
4. Analyze and summarize results
5. Present findings clearly
Key practices:
- Write optimized SQL queries with proper filters
- Use appropriate aggregations and joins
- Include comments explaining complex logic
- Format results for readability
- Provide data-driven recommendations
For each analysis:
- Explain the query approach
- Document any assumptions
- Highlight key findings
- Suggest next steps based on data
Always ensure queries are efficient and cost-effective.
```
## Subagent Creation Workflow
### Step 1: Decide the Scope
- **Project-level** (`.cursor/agents/`): For codebase-specific agents shared with team
- **User-level** (`~/.cursor/agents/`): For personal agents across all projects
### Step 2: Create the File
```bash
# For project-level
mkdir -p .cursor/agents
touch .cursor/agents/my-agent.md
# For user-level
mkdir -p ~/.cursor/agents
touch ~/.cursor/agents/my-agent.md
```
### Step 3: Define Configuration
Write the frontmatter with the required fields (`name` and `description`).
### Step 4: Write the System Prompt
The body becomes the system prompt. Be specific about:
- What the agent should do when invoked
- The workflow or process to follow
- Output format and structure
- Any constraints or guidelines
### Step 5: Test the Agent
Ask the AI to use your new agent:
```
Use the my-agent subagent to [task description]
```
## Best Practices
1. **Design focused subagents**: Each should excel at one specific task
2. **Write detailed descriptions**: Include trigger terms so the AI knows when to delegate
3. **Check into version control**: Share project subagents with your team
4. **Use proactive language**: Include "use proactively" in descriptions
## Troubleshooting
### Subagent Not Found
- Ensure file is in `.cursor/agents/` or `~/.cursor/agents/`
- Check file has `.md` extension
- Verify YAML frontmatter syntax is valid

View File

@@ -0,0 +1,75 @@
---
name: loop
description: >-
Run a prompt or skill on a recurring local interval using monitored background
shell output. Use for /loop, polling status, recurring fixed-interval tasks,
dynamic self-paced loops, local cron-like loops, or waking an agent
periodically. Do not use for one-off tasks.
disabled-environments:
- cloud
---
# Loop
Use monitored shell output when the goal is to wake the agent for recurring local work.
## Parse
Accept `/loop [interval] <prompt>`.
- Leading interval: `5m /foo`, `30s check status`, `2h run report`.
- Trailing interval: `check deploy every 5m`, `run tests every 10 minutes`.
- No interval: dynamic mode; the agent chooses the next delay after each run.
- Empty prompt: show `Usage: /loop [interval] <prompt>`.
Use intervals like `30s`, `5m`, `2h`, `1d`. Convert unit words to short units.
## Fixed Schedule
```bash
while true; do
sleep <seconds>
echo 'AGENT_LOOP_TICK_<purpose> {"prompt":"<prompt>"}'
done
```
1. Check existing terminals for an already-running matching loop.
2. Start one background shell loop with `notify_on_output`.
3. Use a unique sentinel and a regex such as `^AGENT_LOOP_TICK_<purpose>`.
4. Smoke-check once to confirm clean startup.
5. Run the prompt once immediately after arming the loop.
6. The first sentinel should arrive only after the initial sleep, so startup does not double-run the prompt.
7. Track the PID so the agent can stop the loop if asked.
8. Briefly confirm: the interval, that the prompt already ran once, when the first tick will arrive, and that the loop will fire on each tick until stopped. On later ticks, give a short update of what changed. On stop, say the loop has stopped and why.
## Dynamic Schedule
The user wants the agent to self-pace. Decide what makes the next iteration worth running — a passage of time, or an observable event.
1. **Run the prompt now.**
2. **If the next run is gated on an event** (a git ref advancing, a log line matching, a file changing, a CI check completing), arm a background watcher that emits the sentinel only when the event fires, with `notify_on_output` on `^AGENT_LOOP_WAKE_<purpose>`. Arm once; skip on later ticks if it's still running.
3. **At the end of the turn, arm a one-shot time-based wake**:
```bash
sleep <seconds>
echo 'AGENT_LOOP_WAKE_<purpose> {"prompt":"<prompt>"}'
```
With a watcher armed, this is the **fallback heartbeat** — lean long so idle ticks aren't pure overhead. Without a watcher, this is the cadence — pick a delay based on when the result is worth checking again.
4. **On wake**, read the latest payload, execute its `prompt`, then re-arm the next heartbeat (and re-arm the watcher only if it exited). If both an output wake and a completion notification arrive, act on the output and ignore the completion.
5. **To stop**, kill any watcher PID and don't arm the next heartbeat.
6. Briefly confirm: that you're self-pacing, whether a watcher is the primary wake signal, what fallback delay you picked, and that the prompt already ran.
## Prompt Payload
Wake notifications include an output file path, not a submitted prompt. Put the prompt beside the sentinel, preferably as JSON. On wake, read the latest matching line and act on its `prompt`. The prompt may vary by tick.
## Guidance
- Title shell commands as `Loop <schedule>: <prompt>` (e.g. `Loop every 5m: check deploy status`).
- Adapt loop syntax to the user's shell (e.g. PowerShell `while ($true) { ... Start-Sleep }` on Windows). The examples above use bash.
- Prefer monitored shell output over OS cron when the agent needs wake notifications; stdout stays attached to the monitored task.
- Use a unique sentinel per loop so unrelated output does not trigger notifications.
- Avoid noisy commands inside the loop.
- Do not create duplicate fixed loops or dynamic sleepers.
- If the user asks to stop, kill any tracked loop/sleeper PID, then await the shell task so its completion notification is consumed and does not wake the agent later. Do not schedule another dynamic wake.

View File

@@ -0,0 +1,134 @@
---
name: migrate-to-skills
description: >-
Convert 'Applied intelligently' Cursor rules (.cursor/rules/*.mdc) and slash
commands (.cursor/commands/*.md) to Agent Skills format (.cursor/skills/). Use
when you want to migrate rules or commands to skills, convert .mdc rules to
SKILL.md format, or consolidate commands into the skills directory.
disable-model-invocation: true
---
# Migrate Rules and Slash Commands to Skills
Convert Cursor rules ("Applied intelligently") and slash commands to Agent Skills format.
**CRITICAL: Preserve the exact body content. Do not modify, reformat, or "improve" it - copy verbatim.**
## Locations
| Level | Source | Destination |
|-------|--------|-------------|
| Project | `{workspaceFolder}/**/.cursor/rules/*.mdc`, `{workspaceFolder}/.cursor/commands/*.md` |
| User | `~/.cursor/commands/*.md` |
Notes:
- Cursor rules inside the project can live in nested directories. Be thorough in your search and use glob patterns to find them.
- Ignore anything in ~/.cursor/worktrees
- Ignore anything in ~/.cursor/skills-cursor. This is reserved for Cursor's internal built-in skills and is managed automatically by the system.
## Finding Files to Migrate
**Rules**: Migrate if rule has a `description` but NO `globs` and NO `alwaysApply: true`.
**Commands**: Migrate all - they're plain markdown without frontmatter.
## Conversion Format
### Rules: .mdc → SKILL.md
```markdown
# Before: .cursor/rules/my-rule.mdc
---
description: What this rule does
globs:
alwaysApply: false
---
# Title
Body content...
```
```markdown
# After: .cursor/skills/my-rule/SKILL.md
---
name: my-rule
description: What this rule does
---
# Title
Body content...
```
Changes: Add `name` field, remove `globs`/`alwaysApply`, keep body exactly.
### Commands: .md → SKILL.md
```markdown
# Before: .cursor/commands/commit.md
# Commit current work
Instructions here...
```
```markdown
# After: .cursor/skills/commit/SKILL.md
---
name: commit
description: Commit current work with standardized message format
disable-model-invocation: true
---
# Commit current work
Instructions here...
```
Changes: Add frontmatter with `name` (from filename), `description` (infer from content), and `disable-model-invocation: true`, keep body exactly.
**Note:** The `disable-model-invocation: true` field prevents the model from automatically invoking this skill. Slash commands are designed to be explicitly triggered by the user via the `/` menu, not automatically suggested by the model.
## Notes
- `name` must be lowercase with hyphens only
- `description` is critical for skill discovery
- Optionally delete originals after verifying migration works
### Migrate a Rule (.mdc → SKILL.md)
1. Read the rule file
2. Extract the `description` from the frontmatter
3. Extract the body content (everything after the closing `---` of the frontmatter)
4. Create the skill directory: `.cursor/skills/{skill-name}/` (skill name = filename without .mdc)
5. Write `SKILL.md` with new frontmatter (`name` and `description`) + the EXACT original body content (preserve all whitespace, formatting, code blocks verbatim)
6. Delete the original rule file
### Migrate a Command (.md → SKILL.md)
1. Read the command file
2. Extract description from the first heading (remove `#` prefix)
3. Create the skill directory: `.cursor/skills/{skill-name}/` (skill name = filename without .md)
4. Write `SKILL.md` with new frontmatter (`name`, `description`, and `disable-model-invocation: true`) + blank line + the EXACT original file content (preserve all whitespace, formatting, code blocks verbatim)
5. Delete the original command file
**CRITICAL: Copy the body content character-for-character. Do not reformat, fix typos, or "improve" anything.**
## Workflow
If you have the Task tool available:
DO NOT start to read all of the files yourself. That function should be delegated to the subagents. Your job is to dispatch the subagents for each category of files and wait for the results.
1. [ ] Create the skills directories if they don't exist (`.cursor/skills/` for project, `~/.cursor/skills/` for user)
2. Dispatch three fast general purpose subagents (NOT explore) in parallel to do the following steps for project rules (pattern: `{workspaceFolder}/**/.cursor/rules/*.mdc`), user commands (pattern: `~/.cursor/commands/*.md`), and project commands (pattern: `{workspaceFolder}/**/.cursor/commands/*.md`):
I. [ ] Find files to migrate in the given pattern
II. [ ] For rules, check if it's an "applied intelligently" rule (has `description`, no `globs`, no `alwaysApply: true`). Commands are always migrated. DO NOT use the terminal to read files. Use the read tool.
III. [ ] Make a list of files to migrate. If empty, done.
IV. [ ] For each file, read it, then write the new skill file preserving the body content EXACTLY. DO NOT use the terminal to write these files. Use the edit tool.
V. [ ] Delete the original file. DO NOT use the terminal to delete these files. Use the delete tool.
VI. [ ] Return a list of all the skill files that were migrated along with the original file paths.
3. [ ] Wait for all subagents to complete and summarize the results to the user. IMPORTANT: Make sure to let them know if they want to undo the migration, to ask you to.
4. [ ] If the user asks you to undo the migration, do the opposite of the above steps to restore the original files.
If you don't have the Task tool available:
1. [ ] Create the skills directories if they don't exist (`.cursor/skills/` for project, `~/.cursor/skills/` for user)
2. [ ] Find files to migrate in both project (`.cursor/`) and user (`~/.cursor/`) directories
3. [ ] For rules, check if it's an "applied intelligently" rule (has `description`, no `globs`, no `alwaysApply: true`). Commands are always migrated. DO NOT use the terminal to read files. Use the read tool.
4. [ ] Make a list of files to migrate. If empty, done.
5. [ ] For each file, read it, then write the new skill file preserving the body content EXACTLY. DO NOT use the terminal to write these files. Use the edit tool.
6. [ ] Delete the original file. DO NOT use the terminal to delete these files. Use the delete tool.
7. [ ] Summarize the results to the user. IMPORTANT: Make sure to let them know if they want to undo the migration, to ask you to.
8. [ ] If the user asks you to undo the migration, do the opposite of the above steps to restore the original files.

380
skills-cursor/sdk/SKILL.md Normal file
View File

@@ -0,0 +1,380 @@
---
name: sdk
description: >-
Guide users building apps, scripts, CI pipelines, or automations on top of the
Cursor SDK - TypeScript (`@cursor/sdk`) or Python (`cursor-sdk` /
`cursor_sdk`). Use when the user mentions integrating, installing, or writing
code against the Cursor SDK; says `Agent.create`, `Agent.prompt`,
`Agent.resume`, `agent.send`, `run.stream`, `run.messages`,
`CursorAgentError`, `@cursor/sdk`, `cursor-sdk`, or `cursor_sdk`; asks to run
Cursor agents programmatically from a script, CI/CD pipeline, GitHub Action,
backend service, or other code outside the Cursor IDE; wants to pick between
local and cloud runtime, configure MCP servers for an SDK agent, or handle
streaming, cancellation, or errors; or is wiring Cursor into an automation,
bot, or REST `/v1/agents` migration. Use eagerly rather than answering from
memory; the SDK surface evolves and this skill is the source of truth for the
external packages.
---
# Cursor SDK
The Cursor SDK runs Cursor agents programmatically. Two language variants share the same concepts:
- **TypeScript** (`@cursor/sdk`, npm) - docs at [cursor.com/docs/sdk/typescript](https://cursor.com/docs/sdk/typescript)
- **Python** (`cursor-sdk`, pip) - docs at [cursor.com/docs/sdk/python](https://cursor.com/docs/sdk/python)
Both are in public beta and follow the same `Agent``Run` model across local (runs on the caller's machine against `cwd`) and cloud (runs on a Cursor-hosted VM against a cloned repo) runtimes.
Use this skill to help someone **bootstrap a working integration quickly** and **avoid the traps that bite new users**. The canonical docs cover the full reference; this skill adds decision-making, failure-mode prevention, and ready-to-extend patterns on top.
## Pick the language
Decide before writing any code. The wrong choice means rewriting the whole integration. Order:
1. **The user named it.** `@cursor/sdk`, `cursor-sdk`, `cursor_sdk`, `npm install`, `pip install`, `import { Agent } from "@cursor/sdk"`, `from cursor_sdk import` - go with what they named.
2. **The codebase signals it.** `pyproject.toml` / `requirements.txt` / `uv.lock` / `.py` files → Python. `package.json` / `tsconfig.json` / `.ts` files → TypeScript. If the integration will live in a polyglot repo, ask which subdirectory it goes into.
3. **No signal either way.** Ask one short question and wait: *"TypeScript or Python?"* Don't pick for them.
The rest of this skill shows TypeScript and Python side by side. Concepts are identical; syntax differs - camelCase vs snake_case, async-by-default vs sync-default-with-async-mirror, `await using` vs `with`.
## Voice and Posture
This skill helps the user **build** with the SDK. It is not the place to validate, congratulate, or sell the SDK as a choice. The user's intent is the input; your job is execution.
- **When the user names the SDK explicitly** (says "Cursor SDK", `@cursor/sdk`, `cursor-sdk`, `Agent.create`, `Agent.prompt`, etc.): assume they know what the SDK is and have decided to use it. Skip framing, skip pep talk, go straight to producing the integration. No "good news", no "the SDK is perfect for this", no "this is almost exactly the pattern X is designed for".
- **When the user describes a problem the SDK fits but doesn't name it** ("I want a bot that reviews my PRs", "I want a script that asks Cursor questions about my repo"): the SDK isn't yet a confirmed choice. Surface it as a question, briefly, then wait: *"The Cursor SDK is what I'd reach for here - want me to design it that way, or do you have a different runtime in mind?"* If they confirm, proceed. If they push back or want options, give options.
- **In either case, don't restate the user's intent back to them.** They know what they want. Get to the design.
Avoid these specific openers (and their close cousins):
- "Good news: this is exactly the pattern..."
- "The SDK is built for this shape."
- "Great, you've come to the right place."
- "This is almost exactly the X the SDK is designed for."
- Any lede that compliments the user's choice or restates their goal in flattering terms.
Prefer:
- Open with the design decision or the first thing they need to know.
- If you genuinely have a design choice to flag (local vs cloud, prompt vs send, sync vs stream, sync Python vs async Python), name it in one sentence and explain why.
## The Three Invocation Patterns
Almost every SDK integration collapses to one of these three shapes. Pick the one that fits the job, don't mix them.
### 1. `Agent.prompt(...)` - one-shot
Use for fire-and-forget scripts, GitHub Actions steps, or any "send this prompt, get a result, exit" flow. No streaming, no follow-ups, no cleanup to remember. If you're reaching for this and then immediately resuming, you wanted pattern 2 instead.
**TypeScript:**
```typescript
import { Agent } from "@cursor/sdk";
const result = await Agent.prompt("Refactor src/utils.ts for readability", {
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
local: { cwd: process.cwd() },
});
console.log(result.status, result.result);
```
**Python:**
```python
import os
from cursor_sdk import Agent, AgentOptions, LocalAgentOptions
result = Agent.prompt(
"Refactor src/utils.py for readability",
AgentOptions(
api_key=os.environ["CURSOR_API_KEY"],
model="composer-2.5",
local=LocalAgentOptions(cwd=os.getcwd()),
),
)
print(result.status, result.result)
```
### 2. `Agent.create(...)` + `agent.send(...)` - durable with follow-ups
Use when you need streaming, multi-turn conversation, or lifecycle operations (cancel, status listener). This is the shape of most non-trivial integrations.
**TypeScript:**
```typescript
import { Agent } from "@cursor/sdk";
await using agent = await Agent.create({
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
local: { cwd: process.cwd() },
});
const run = await agent.send("Find the bug in src/auth.ts");
for await (const event of run.stream()) {
if (event.type === "assistant") {
for (const block of event.message.content) {
if (block.type === "text") process.stdout.write(block.text);
}
}
}
await run.wait();
// Follow-up keeps full conversation context.
const run2 = await agent.send("Now write a regression test for it");
await run2.wait();
```
**Python:**
```python
import os
from cursor_sdk import Agent, LocalAgentOptions
with Agent.create(
model="composer-2.5",
api_key=os.environ["CURSOR_API_KEY"],
local=LocalAgentOptions(cwd=os.getcwd()),
) as agent:
run = agent.send("Find the bug in src/auth.py")
for message in run.messages():
if message.type == "assistant":
for block in message.message.content:
if block.type == "text":
print(block.text, end="")
run.wait()
# Follow-up keeps full conversation context.
run2 = agent.send("Now write a regression test for it")
run2.wait()
```
The Python SDK is sync by default. For servers, bots, and concurrent orchestration, use `AsyncClient.launch_bridge(...)` as an async context manager and `AsyncAgent`. There's no global async default client - instantiate one per event loop, and never mix sync and async clients in the same code path.
### 3. `Agent.resume(...)` - pick up an existing agent later
Use across process boundaries: a cron that continues last night's cleanup, a webhook that extends a user's agent, an interactive CLI that reloads conversation state. Runtime is auto-detected from the ID prefix - `bc-` is cloud, anything else is local.
**TypeScript:**
```typescript
await using agent = await Agent.resume(previousAgentId, {
apiKey: process.env.CURSOR_API_KEY!,
});
const run = await agent.send("Also update the changelog");
await run.wait();
```
**Python:**
```python
import os
from cursor_sdk import Agent, AgentOptions
with Agent.resume(
previous_agent_id,
AgentOptions(api_key=os.environ["CURSOR_API_KEY"]),
) as agent:
run = agent.send("Also update the changelog")
run.wait()
```
**Inline MCP servers are not persisted across resume** - they often carry secrets and live in memory only. Pass them again on the resume call in either language.
## Top Five Traps
These trip up almost every new integration. They're all easy to prevent once you know about them.
### 1. Picking the wrong runtime by accident
`AgentOptions` doesn't require `local` or `cloud`; the SDK selects local when neither is set. The trap: if you intended a cloud agent and forgot the `cloud` field, you get a local agent silently - no error, just a local agent ID and a local executor.
- **TypeScript:** pass `cloud: { repos: [...] }` for cloud, `local: { cwd }` for local - even though local is the default.
- **Python:** pass `cloud=CloudAgentOptions(repos=[...])` or `local=LocalAgentOptions(cwd=...)`.
Always set one of the two explicitly. The cost is one line; the cost of not noticing for an hour is much more.
### 2. Two kinds of failure, one instinct to conflate them
A thrown `CursorAgentError` means the run **never executed** (auth, config, network). A returned `result.status == "error"` means the run **did execute and failed**. Different fixes, different exit codes, different observability.
**TypeScript:**
```typescript
import { Agent, CursorAgentError } from "@cursor/sdk";
try {
const run = await agent.send(prompt);
const result = await run.wait();
if (result.status === "error") {
// Run started but failed mid-flight. Inspect transcript, git state, tool outputs.
console.error("run failed: " + result.id);
process.exit(2);
}
} catch (err) {
if (err instanceof CursorAgentError) {
// Didn't start. Auth, config, network. Fix environment, retry.
console.error("startup failed: " + err.message + ", retryable=" + err.isRetryable);
process.exit(1);
}
throw err;
}
```
**Python:**
```python
import sys
from cursor_sdk import CursorAgentError
try:
run = agent.send(prompt)
result = run.wait()
if result.status == "error":
# Run started but failed mid-flight. Inspect transcript, git state, tool outputs.
print("run failed: " + result.id, file=sys.stderr)
sys.exit(2)
except CursorAgentError as err:
# Didn't start. Auth, config, network. Fix environment, retry.
print(
"startup failed: " + err.message + ", retryable=" + str(err.is_retryable),
file=sys.stderr,
)
sys.exit(1)
```
### 3. Forgetting to dispose leaks resources
The SDK holds handles to local executors, persisted run stores, and HTTP clients. Skipping disposal leaks child processes, open databases, and (in long-running services) memory.
- **TypeScript:** `await using agent = await Agent.create(...)` is the cleanest path. Otherwise wrap in `try/finally` with `await agent[Symbol.asyncDispose]()`. `Agent.prompt(...)` disposes for you.
- **Python (sync):** `with Agent.create(...) as agent:` is the cleanest path. Otherwise call `agent.close()` in a `finally`. Long-running processes that rely on the module-level default client should call `close_default_client()` at shutdown.
- **Python (async):** nest two async context managers - `async with await AsyncClient.launch_bridge(...) as client:` then `async with await client.agents.create(...) as agent:`. `Agent.prompt(...)` disposes for you.
### 4. Streaming is optional but `wait()` is almost always required
The stream is how you observe; `wait()` is how you get the terminal result. You can skip streaming, but skipping `wait()` means you can't tell whether the run finished, errored, or was cancelled, and you'll leak the run's internal watchers. Always call `wait()`. If you don't want live output, call `wait()` alone.
- **TypeScript:** `run.stream()` is an async iterable of `SDKMessage`. `await run.wait()` returns `RunResult`.
- **Python:** `run.messages()` yields typed SDK messages; `run.events()` yields lower-level envelopes. `run.wait()` returns `RunResult`. Async equivalents are `async for ...` and `await run.wait()`. Convenience helpers: `run.text()` blocks on `wait()` and returns the final assistant text; `run.iter_text()` streams text-only chunks. `run.stream()` is an alias for `run.messages()`.
### 5. Not every `run` operation is supported on every runtime
`Run` exposes a handful of operations - stream/messages, wait, cancel, conversation - and detached or rehydrated runs (handles you got from `Agent.getRun(...)` after the live event store closed) may not support all of them. Guard with `supports(...)`:
```typescript
if (run.supports("cancel")) await run.cancel();
if (run.supports("conversation")) console.log(await run.conversation());
```
```python
if run.supports("cancel"):
run.cancel()
if run.supports("conversation"):
print(run.conversation())
```
`run.unsupportedReason(op)` (TypeScript) / `run.unsupported_reason(op)` (Python) tells you why.
## Local vs Cloud, in one sentence each
- **Local** - runs on the caller's machine against `cwd`, reuses their environment and credentials. Good for dev loops and CI that already has a repo checkout.
- **Cloud** - runs on a Cursor-hosted VM against a freshly cloned repo. Good for long jobs, fire-and-forget automation, and opening real PRs (`autoCreatePR: true` in TypeScript; `auto_create_pr=True` in Python).
## Auth, minimum viable
```bash
export CURSOR_API_KEY="cursor_..." # user API key or team service-account key
```
Both SDKs read `CURSOR_API_KEY` when no key is passed explicitly. User keys live at [Cursor Dashboard → Integrations](https://cursor.com/dashboard/integrations); team service-account keys live in Team Settings → Service accounts. Team Admin API keys are not yet supported.
If you're seeing 401s, the usual suspects are: key pasted with surrounding whitespace, key minted against a different environment, or the key belongs to a user without repo access for a cloud run.
## Model Selection
Don't hardcode unusual model IDs without confirming the caller has access - model lists evolve.
**TypeScript:**
```typescript
import { Cursor } from "@cursor/sdk";
const models = await Cursor.models.list({ apiKey: process.env.CURSOR_API_KEY! });
```
**Python:**
```python
from cursor_sdk import Cursor
models = Cursor.models.list() # falls back to CURSOR_API_KEY
```
`composer-2.5` is the current default for most integrations. `{ id: "auto" }` (TS) / `model="auto"` (Python) lets the server pick. `Cursor.models.list()` returns valid IDs, per-model parameter definitions (reasoning effort, max mode), and preset variants for the calling account.
Model is **required for local** in both SDKs. For cloud, TypeScript falls back to a server-resolved default when omitted; Python documents it as required across both runtimes. Pass one regardless to keep behavior predictable.
## MCP Servers
Both SDKs use the same conceptual model: HTTP transport (with static `headers` or OAuth `auth`) or stdio (with `command` / `args` / `env`). Pass servers inline on `Agent.create` or `agent.send` for the most common case.
- **Local agents** can use stdio or HTTP servers available on the caller's machine. If a local MCP server requires OAuth login, the SDK can reuse a saved login from the Cursor app but can't open a browser to sign you in.
- **Cloud agents** support both HTTP and stdio (stdio runs inside the cloud VM). HTTP `headers` and `auth` are handled by Cursor's backend and redacted before the VM sees them. Stdio `env` values are passed into the VM - treat them like any runtime secret.
- **Inline servers fully replace creation-time servers on a per-send override - not merged.**
- **If you resume an agent and still need MCP tools, pass servers again on the resume call.** Inline servers are not persisted.
For full schema and authentication options, see the SDK docs for each language and [Cursor MCP](https://cursor.com/docs/mcp).
## Production Best Practices
Apply these to any integration that runs unattended:
1. **Always dispose.** `await using` (TypeScript) or `with ... as agent:` (Python) is the cleanest path. Non-negotiable.
2. **Distinguish startup failures from run failures.** Exit code 1 for thrown `CursorAgentError`, exit code 2 for `result.status == "error"`, exit code 0 only for `finished`.
3. **Log `run.id` and `agent.agentId` / `agent.agent_id` immediately after `send()`** before streaming. If the stream hangs, the IDs are what you need to investigate in the dashboard or via `Agent.getRun(...)` / `Agent.get_run(...)`.
4. **Respect `error.isRetryable` / `err.is_retryable`.** It's the backend telling you the specific failure is safe to retry. Python errors also expose `retry_after` (HTTP-style seconds value or date) - honor it before falling back to exponential backoff. Blind retries can cause duplicate cloud runs.
5. **Don't load ambient settings unless you mean to.** The default in TypeScript is `local.settingSources: []`; the default in Python is no `local.setting_sources`. Both mean "inline config only". Opting into `"all"` loads project/user/team/MDM settings from the caller's environment, which is rarely what you want from a service. Setting sources have no effect on cloud agents - cloud always honors team/project/plugins.
6. **For cloud agents in CI, set `skipReviewerRequest: true` (TypeScript) / `skip_reviewer_request=True` (Python)** unless a human should be paged - it suppresses the reviewer-request step and keeps PR notifications quiet.
7. **Always pass `apiKey` / `api_key` explicitly** in shared-infrastructure code instead of relying on the env var. Makes the credential dependency obvious and prevents cross-tenant mistakes.
8. **Prefer the one-shot `Agent.prompt(...)` for true one-shots** - it disposes for you and is harder to leak.
## Observing a Run You Didn't Launch
You can inspect any agent or run by ID later. Cloud IDs that start with `bc-` auto-route to the cloud API; anything else is local.
**TypeScript:**
```typescript
const info = await Agent.get("bc-abc123", { apiKey });
const run = await Agent.getRun(runId, { runtime: "cloud", agentId: "bc-abc123", apiKey });
// Local: you need the cwd where the agent was created.
const localList = await Agent.list({ runtime: "local", cwd: process.cwd() });
```
**Python:**
```python
from cursor_sdk import CursorClient
with CursorClient.launch_bridge(workspace=".") as client:
info = client.agents.get("bc-abc123")
run = client.agents.get_run(run_id)
# Local: filter by cwd.
local_list = client.agents.list(runtime="local", cwd=".")
```
A cloud `bc-`-prefixed agent ID is **not** a run ID. If you only have a run ID (from a log or a webhook), pass it to `getRun` / `get_run` with the runtime hint; don't confuse the two.
## Offering a Canvas
If the user's integration monitors, lists, or visualizes agents - dashboards of active runs, conversation replays, tool-call timelines - offer a Cursor Canvas to render it. If they accept, defer entirely to the `canvas` skill.
## What This Skill Doesn't Cover
- The [Cloud Agents REST API](https://cursor.com/docs/cloud-agent/api/endpoints) (`/v1/agents/*`). Use it from languages without a first-party SDK, or when you want a minimal HTTP surface.
- `.cursor/hooks.json` hooks. Both SDKs respect them; neither manages them. See [Hooks](https://cursor.com/docs/hooks).
- Self-hosted cloud (private workers, self-hosted pools, my-machines). See [Self-hosted pool](https://cursor.com/docs/cloud-agent/self-hosted-pool) and related docs.
- SDKs in languages other than TypeScript and Python. The REST API is the portable option there.

View File

@@ -0,0 +1,24 @@
---
name: shell
description: >-
Runs the rest of a /shell request as a literal shell command. Use only when
the user explicitly invokes /shell and wants the following text executed
directly in the terminal.
disable-model-invocation: true
---
# Run Shell Commands
Use this skill only when the user explicitly invokes `/shell`.
## Behavior
1. Treat all user text after the `/shell` invocation as the literal shell command to run.
2. Execute that command immediately with the terminal tool.
3. Do not rewrite, explain, or "improve" the command before running it.
4. Do not inspect the repository first unless the command itself requires repository context.
5. If the user invokes `/shell` without any following text, ask them which command to run.
## Response
- Run the command first.
- Then briefly report the exit status and any important stdout or stderr.

View File

@@ -0,0 +1,49 @@
---
name: split-to-prs
description: >-
Split current work into small reviewable PRs. Use when the user asks to split
a chat, set of changes, branch, or PR.
---
# Split to PRs
Turn one pile of work into a few small PRs.
## Hard rules
- Do not create branches, commit, push, or open PRs until the user approves the split plan.
- Never discard user work. No destructive git commands (`reset --hard`, `clean -fdx`, branch deletion, force-push, history rewrite) without explicit approval.
- Always save a recoverable snapshot before moving work around. This often starts from dirty work on `main`, so do not assume there is already a safe branch.
- Stage only named files or hunks. No `git add .` / `git add -A`.
## 1. Check the state
Compare the current work to the repo's default branch, including committed and uncommitted changes. Summarize the real slices you see, and use the chat history to recover intent.
Before proposing slices, find ownership signals for the touched paths (`CODEOWNERS`, nested ownership files, `tools/ownership/PRODUCTOWNERS`, or repo equivalents) and use them to identify natural reviewer boundaries.
## 2. Propose the split
Use judgment on detail. Usually PR titles are enough. Add a one-line scope note only when a title is unclear. Show a Mermaid diagram when there are multiple slices.
Optimize for reviewer-aligned PRs with minimal unrelated diff: split independent owners or concerns, keep tightly coupled changes together, and when stacking is necessary, order foundations before consumers.
Default to independent PRs off the default branch. Stack PRs only when the dependency is real.
Ask for approval before starting.
## 3. Execute the split
- If there is uncommitted work, save a recoverable snapshot without changing the working tree:
```bash
SHA=$(git stash create "pre-split")
if [ -n "$SHA" ]; then
git update-ref "refs/backup/pre-split-$(date +%s)" "$SHA"
fi
```
- For each approved slice, create a branch from the right base, stage and commit only the planned files or hunks, then push and open the PR.
## 4. Report back
Keep it short: PR titles and URLs, plus anything left on the starting branch or working tree. Do not delete the backup ref or original branch unless the user asks.

View File

@@ -0,0 +1,196 @@
---
name: statusline
description: >-
Configure a custom status line in the CLI. Use when the user mentions status
line, statusline, statusLine, CLI status bar, prompt footer customization, or
wants to add session context above the prompt.
---
# CLI Status Line
The CLI supports a user-configurable status line rendered above the prompt. A command is spawned on each conversation update, receives a JSON payload on stdin describing the session, and its stdout is displayed as the status line. The spec is aligned with [Claude Code's status line](https://code.claude.com/docs/en/statusline).
## Configuration
Add a `statusLine` entry to `~/.cursor/cli-config.json`:
```json
{
"statusLine": {
"type": "command",
"command": "~/.cursor/statusline.sh",
"padding": 2
}
}
```
The `command` field supports full paths, `~` expansion, and shell-style argument splitting. You can point it at a script file or use an inline command like `jq -r '...'`.
| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `type` | yes | — | Must be `"command"` |
| `command` | yes | — | Path to an executable or inline command. `~` is expanded. |
| `padding` | no | `0` | Horizontal inset (in characters) for the status line container. |
| `updateIntervalMs` | no | `300` | Minimum interval between invocations. Clamped to >= 300ms. |
| `timeoutMs` | no | `2000` | Maximum time the command may run before it is killed. |
## Stdin payload
The command receives a JSON object on stdin. The TypeScript interface is `StatusLinePayload` in `packages/agent-cli/src/hooks/use-status-line.ts`.
### Full JSON schema
```json
{
"session_id": "abc123",
"session_name": "my session",
"transcript_path": "/path/to/transcript.jsonl",
"render_width_chars": 120,
"cwd": "/Users/me/project",
"autorun": false,
"model": {
"id": "claude-4-opus",
"display_name": "Claude 4 Opus",
"param_summary": "(Thinking)",
"max_mode": true
},
"workspace": {
"current_dir": "/Users/me/project",
"project_dir": "/Users/me/project/.cursor/transcripts",
"added_dirs": []
},
"version": "1.2.3",
"output_style": {
"name": "default"
},
"context_window": {
"total_input_tokens": 15234,
"total_output_tokens": null,
"context_window_size": 200000,
"used_percentage": 34.5,
"remaining_percentage": 65.5,
"current_usage": null
},
"vim": {
"mode": "NORMAL"
},
"worktree": {
"name": "my-feature",
"path": "/Users/me/.cursor/worktrees/repo/my-feature"
}
}
```
### Available fields
| Field | Description |
|-------|-------------|
| `session_id` | Unique session identifier |
| `session_name` | Custom session name. Absent if no name has been set |
| `transcript_path` | Path to conversation transcript file |
| `render_width_chars` | Usable terminal columns minus built-in padding |
| `cwd`, `workspace.current_dir` | Current working directory (both contain the same value) |
| `autorun` | `true` when auto-run is enabled for the current session |
| `workspace.project_dir` | Directory where transcripts are stored |
| `workspace.added_dirs` | Additional directories (empty array for now) |
| `model.id`, `model.display_name` | Current model identifier and display name |
| `model.param_summary` | Formatted parameter summary (e.g. "(Thinking)", "High"). Absent when empty |
| `model.max_mode` | `true` when max mode is enabled. Absent otherwise |
| `version` | CLI version string |
| `output_style.name` | `"default"` or `"compact"` |
| `context_window.total_input_tokens` | Estimated input tokens (derived from used_percentage) |
| `context_window.total_output_tokens` | Cumulative output tokens (null when not tracked) |
| `context_window.context_window_size` | Maximum context window size in tokens |
| `context_window.used_percentage` | Percentage of context window used |
| `context_window.remaining_percentage` | Percentage of context window remaining |
| `context_window.current_usage` | Token counts from the last API call (null before first call) |
| `vim.mode` | `"NORMAL"` or `"INSERT"` when vim mode is enabled |
| `worktree.name` | Worktree name when running inside a worktree |
| `worktree.path` | Absolute path to the worktree directory |
### Fields that may be absent
- `session_name` — only present when a custom name has been set
- `model.param_summary` — only present when model has non-default parameters
- `model.max_mode` — only present when max mode is enabled
- `vim` — only present when vim mode is enabled
- `worktree` — only present when running in a worktree
### Fields that may be null
- `context_window.current_usage` — null before the first API call
- `context_window.used_percentage`, `context_window.remaining_percentage` — may be null early in the session
## Stdout / rendering
- **Multiple lines** are supported: each line of stdout renders as a separate row in the status area.
- **ANSI color codes** are supported (use chalk, tput, `\033[32m`, etc.).
- If the command exits non-zero with empty stdout, the status line is not updated (previous text is kept).
- If the command times out or a new update arrives while the script is running, the in-flight process is killed.
- The status line runs locally and does not consume API tokens.
## Examples
### Basic: model + context usage
```bash
#!/usr/bin/env bash
payload=$(cat)
model=$(echo "$payload" | jq -r '.model.display_name')
pct=$(echo "$payload" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
printf "\033[90m%s ctx %s%%\033[0m" "$model" "$pct"
```
### Context progress bar
```bash
#!/usr/bin/env bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
BAR_WIDTH=10
FILLED=$((PCT * BAR_WIDTH / 100))
EMPTY=$((BAR_WIDTH - FILLED))
BAR=""
[ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" && BAR="${FILL// /▓}"
[ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" && BAR="${BAR}${PAD// /░}"
echo "[$MODEL] $BAR $PCT%"
```
### Multi-line with git info
```bash
#!/usr/bin/env bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
BRANCH=""
git rev-parse --git-dir > /dev/null 2>&1 && BRANCH=" | 🌿 $(git branch --show-current 2>/dev/null)"
echo -e "\033[36m[$MODEL]\033[0m 📁 ${DIR##*/}$BRANCH"
echo -e "ctx $PCT%"
```
### Inline jq command (no script file)
```json
{
"statusLine": {
"type": "command",
"command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}
}
```
## Testing
Test a script with mock input:
```bash
echo '{"model":{"display_name":"Opus"},"context_window":{"used_percentage":25}}' | ./statusline.sh
```
The command is spawned with `child_process.spawn` (no shell on Unix, `shell: true` on Windows for .cmd/.bat compatibility). Updates are debounced at the configured interval. If a new update triggers while a script is running, the in-flight process is killed via `AbortController` and the new invocation starts immediately.

View File

@@ -0,0 +1,87 @@
---
name: update-cli-config
description: >-
View and modify Cursor CLI configuration settings in
~/.cursor/cli-config.json. Use when the user wants to change CLI settings,
configure permissions, switch approval mode, enable vim mode, toggle display
options, configure sandbox, or manage any CLI preferences.
metadata:
surfaces:
- cli
---
# Cursor CLI Configuration
This skill explains how to view and modify Cursor CLI settings stored in `~/.cursor/cli-config.json`.
## Config File Location
The config file is `~/.cursor/cli-config.json`.
Projects can layer overrides via `.cursor/cli.json` files. The CLI walks from the git root to the current working directory and merges each `.cursor/cli.json` it finds (deeper files take precedence). Project overrides only affect the current session; they are not written back to the home config.
## How to Modify
Read `~/.cursor/cli-config.json`, apply changes, and write it back. The file is standard JSON. Changes take effect after restarting the CLI.
## Available Settings
### `permissions` (required)
Tool permission rules. Each entry is a string pattern.
- `allow`: string[] — patterns for allowed tool calls (e.g. `"Shell(**)"`, `"Mcp(server-name, tool-name)"`)
- `deny`: string[] — patterns for denied tool calls
### `editor`
- `vimMode`: boolean — enable vim keybindings in the CLI input
- `defaultBehavior`: `"ide"` | `"agent"` — default behavior mode
### `display` (optional)
- `showLineNumbers`: boolean (default: false) — show line numbers in code output
- `showThinkingBlocks`: boolean (default: false) — show model thinking/reasoning blocks
- `showStatusIndicators`: boolean (default: false) — show status indicators in the UI
### `channel` (optional)
Release channel: `"prod"` | `"staging"` | `"lab"` | `"static"`
### `maxMode` (optional)
boolean (default: false) — enable max mode for higher-quality model responses
### `approvalMode` (optional)
Controls tool approval behavior:
- `"allowlist"` (default) — require approval for tools not in the allow list
- `"unrestricted"` — auto-approve all tool calls (yolo mode)
### `sandbox` (optional)
Sandbox execution environment settings:
- `mode`: `"disabled"` | `"enabled"` (default: `"disabled"`)
- `networkAccess`: `"user_config_only"` | `"user_config_with_defaults"` | `"allow_all"` — controls network access from sandbox
- `networkAllowlist`: string[] — domains the sandbox is allowed to reach
### `network` (optional)
- `useHttp1ForAgent`: boolean (default: false) — use HTTP/1.1 instead of HTTP/2 for agent connections (enables SSE-based streaming)
### `bedrock` (optional)
AWS Bedrock integration settings:
- `enabled`: boolean (default: false)
- `mode`: `"access-key"` | `"team-role"` (default: `"access-key"`)
- `region`: string — AWS region
- `testModel`: string — model to use for testing
- `teamRoleArn`: string — IAM role ARN for team mode
- `teamExternalId`: string — external ID for STS assume-role
### `attribution` (optional)
Controls how agent work is attributed in git:
- `attributeCommitsToAgent`: boolean (default: true) — attribute commits to the agent
- `attributePRsToAgent`: boolean (default: true) — attribute PRs to the agent
### `webFetchDomainAllowlist` (optional)
string[] — domains the web fetch tool is allowed to access (e.g. `"docs.github.com"`, `"*.example.com"`, `"*"`)
## Fields You Should NOT Modify
These are internal/cached state and should not be edited manually:
- `version` — config schema version
- `model` / `selectedModel` / `modelParameters` / `hasChangedDefaultModel` — managed by the model picker
- `privacyCache` — cached privacy mode state
- `authInfo` — cached authentication info
- `showSandboxIntro` — one-time UI flag
- `conversationClassificationScoredConversations` — internal cache

View File

@@ -0,0 +1,122 @@
---
name: update-cursor-settings
description: >-
Modify Cursor/VSCode user settings in settings.json. Use when you want to
change editor settings, preferences, configuration, themes, font size, tab
size, format on save, auto save, keybindings, or any settings.json values.
metadata:
surfaces:
- ide
---
# Updating Cursor Settings
This skill guides you through modifying Cursor/VSCode user settings. Use this when you want to change editor settings, preferences, configuration, themes, keybindings, or any `settings.json` values.
## Settings File Location
| OS | Path |
|----|------|
| macOS | ~/Library/Application Support/Cursor/User/settings.json |
| Linux | ~/.config/Cursor/User/settings.json |
| Windows | %APPDATA%\Cursor\User\settings.json |
## Before Modifying Settings
1. **Read the existing settings file** to understand current configuration
2. **Preserve existing settings** - only add/modify what the user requested
3. **Validate JSON syntax** before writing to avoid breaking the editor
## Modifying Settings
### Step 1: Read Current Settings
```typescript
// Read the settings file first
const settingsPath = "~/Library/Application Support/Cursor/User/settings.json";
// Use the Read tool to get current contents
```
### Step 2: Identify the Setting to Change
Common setting categories:
- **Editor**: `editor.fontSize`, `editor.tabSize`, `editor.wordWrap`, `editor.formatOnSave`
- **Workbench**: `workbench.colorTheme`, `workbench.iconTheme`, `workbench.sideBar.location`
- **Files**: `files.autoSave`, `files.exclude`, `files.associations`
- **Terminal**: `terminal.integrated.fontSize`, `terminal.integrated.shell.*`
- **Cursor-specific**: Settings prefixed with `cursor.` or `aipopup.`
### Step 3: Update the Setting
When modifying settings.json:
1. Parse the existing JSON (handle comments - VSCode settings support JSON with comments)
2. Add or update the requested setting
3. Preserve all other existing settings
4. Write back with proper formatting (2-space indentation)
### Example: Changing Font Size
If user says "make the font bigger":
```json
{
"editor.fontSize": 16
}
```
### Example: Enabling Format on Save
If user says "format my code when I save":
```json
{
"editor.formatOnSave": true
}
```
### Example: Changing Theme
If user says "use dark theme" or "change my theme":
```json
{
"workbench.colorTheme": "Default Dark Modern"
}
```
## Important Notes
1. **JSON with Comments**: VSCode/Cursor settings.json supports comments (`//` and `/* */`). When reading, be aware comments may exist. When writing, preserve comments if possible.
2. **Restart May Be Required**: Some settings take effect immediately, others require reloading the window or restarting Cursor. Inform the user if a restart is needed.
3. **Backup**: For significant changes, consider mentioning the user can undo via Ctrl/Cmd+Z in the settings file or by reverting git changes if tracked.
4. **Workspace vs User Settings**:
- User settings (what this skill covers): Apply globally to all projects
- Workspace settings (`.vscode/settings.json`): Apply only to the current project
5. **Commit Attribution**: When the user asks about commit attribution, clarify whether they want to edit the **CLI agent** or the **IDE agent**. For the CLI agent, modify `~/.cursor/cli-config.json`. For the IDE agent, it is controlled from the UI at **Cursor Settings > Agent > Attribution** (not settings.json).
## Common User Requests → Settings
| User Request | Setting |
|--------------|---------|
| "bigger/smaller font" | `editor.fontSize` |
| "change tab size" | `editor.tabSize` |
| "format on save" | `editor.formatOnSave` |
| "word wrap" | `editor.wordWrap` |
| "change theme" | `workbench.colorTheme` |
| "hide minimap" | `editor.minimap.enabled` |
| "auto save" | `files.autoSave` |
| "line numbers" | `editor.lineNumbers` |
| "bracket matching" | `editor.bracketPairColorization.enabled` |
| "cursor style" | `editor.cursorStyle` |
| "smooth scrolling" | `editor.smoothScrolling` |
## Workflow
1. Read ~/Library/Application Support/Cursor/User/settings.json
2. Parse the JSON content
3. Add/modify the requested setting(s)
4. Write the updated JSON back to the file
5. Inform the user the setting has been changed and whether a reload is needed