plugin
**Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. **Version range**: Perses v0.45+ (Module Federation plugin architecture) **Generated**: 2026-05-09 —
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
**Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. **Version range**: Perses v0.45+ (Module Federation plugin architecture) **Generated**: 2026-05-09 —
Agent definition
plugin.mdPerses Plugin Development Reference
> **Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. > **Version range**: Perses v0.45+ (Module Federation plugin architecture) > **Generated**: 2026-05-09 — verify against https://github.com/perses/perses/tree/main/docs/plugins
---
Overview
Perses plugins are independently deployable modules that extend the platform with new panel types, datasources, query types, or variable resolvers. Each plugin has three layers: a CUE schema (defines the spec shape), a Go backend (optional — only for datasource/query plugins), and a React/TypeScript frontend component. The most common failure mode is a mismatch between the CUE schema field names and the TypeScript prop names — the backend validates schema but the frontend silently ignores unknown fields.
---
Pattern Table
| Plugin Type | Has Backend | Frontend Component | CUE Schema Required | |------------|-------------|-------------------|---------------------| | Panel | No | Yes — renders data | Yes | | Datasource | Yes — handles HTTP proxying | Yes — editor UI | Yes | | Query | Yes — transforms raw data | Yes — query editor | Yes | | Variable | Yes (optional) | Yes — variable editor | Yes | | Explore | No | Yes — explore view | Yes |
---
Plugin Scaffolding: percli
Generate a new plugin
# Scaffold a panel plugin
percli plugin generate --type panel --name my-chart --output ./plugins/my-chart
# Scaffold a datasource plugin
percli plugin generate --type datasource --name my-datasource --output ./plugins/my-datasource
# Build plugin archive for distribution
percli plugin build --dir ./plugins/my-chart --output ./dist/my-chart.tar.gz
Plugin directory structure (panel example)
plugins/my-chart/
├── plugin.json # Plugin manifest — name, version, type
├── cue/
│ └── schemas/
│ └── my-chart.cue # CUE schema for panel spec
├── src/
│ ├── index.tsx # Plugin entry point — exports PanelPlugin
│ ├── MyChart.tsx # React component
│ └── types.ts # TypeScript types matching CUE schema
├── package.json
└── webpack.config.js # Module Federation config
---
CUE Schema Authoring
Minimal panel spec schema
// cue/schemas/my-chart.cue
package schemas
// MyChartSpec defines the configuration for the MyChart panel.
#MyChartSpec: {
// query is required — references a query variable or inline query
query: string
// thresholds are optional visual thresholds
thresholds?: [...{
value: number
color: string
}]
// legend controls display; defaults to true
showLegend?: bool | *true
}**Why**: Field names in CUE must exactly match the JSON keys sent by the frontend. A field named `showLegend` in CUE but `show_legend` in TypeScript will validate but never populate — the JSON key mismatch is silent.
---
CUE schema validation
# Validate a dashboard JSON against Perses CUE schemas
cue vet -d '#Dashboard' ./schemas/ dashboard.json
# Validate a plugin spec specifically
cue vet -d '#MyChartSpec' ./plugins/my-chart/cue/schemas/ spec.json
# Export CUE schema as JSON Schema for editor tooling
cue export --out json ./plugins/my-chart/cue/schemas/
---
React/TypeScript Frontend Patterns
Plugin entry point (index.tsx)
import { PanelPlugin } from '@perses-dev/plugin-system';
import { MyChart } from './MyChart';
import { MyChartEditor } from './MyChartEditor';
import type { MyChartSpec } from './types';
export const MyChartPanel: PanelPlugin<MyChartSpec> = {
PanelComponent: MyChart,
spec: {
// Default spec used when panel is first added
initSpec: (): MyChartSpec => ({
query: '',
showLegend: true,
}),
},
editor: {
EditorComponent: MyChartEditor,
},
};**Why**: The `initSpec` function must return a valid default that matches the CUE schema. A missing required field in `initSpec` causes the panel editor to open with a validation error before the user touches anything.
---
Accessing query data in a panel component
import { useDataQueries } from '@perses-dev/plugin-system';
import type { TimeSeriesData } from '@perses-dev/core';
export function MyChart({ spec }: PanelProps<MyChartSpec>) {
const { queryResults, isFetching, error } = useDataQueries('TimeSeriesQuery');
if (isFetching) return <LoadingOverlay />;
if (error) return <ErrorAlert error={error} />;
const data = queryResults[0]?.data as TimeSeriesData | undefined;
if (!data) return <NoDataOverlay />;
return <canvas>{/* render data.series */}</canvas>;
}**Why**: Always handle all three states (`isFetching`, `error`, `!data`). A panel that only handles the happy path shows a blank panel during loading and crashes on query errors, degrading the whole dashboard view.
---
Plugin manifest (plugin.json)
{
"name": "my-chart",
"displayName": "My Chart",
"version": "0.1.0",
"pluginType": "Panel",
"components": [
{
"kind": "MyChart",
"display": {
"name": "My Chart",
"description": "A custom chart panel"
}
}
]
}**Why**: The `kind` field in `plugin.json` must match the `kind` string used in dashboard JSON panel specs. A mismatch causes the panel to render as "Unknown Panel Type" with no useful error.
---
Pattern Catalog: Detection and Fixes
CUE/TypeScript field name mismatch
**Detection**:
# Extract CUE field names
grep -rn '^\s*\w\+?:' --include="*.cue" | sed 's/:.*//' | tr -d ' '
# Compare against TypeScript interface fields
grep -rn '^\s*\w\+\?:' --include="*.ts" | sed 's/:.*//' | tr -d ' '
**Signal**:
// CUE schema:
#Spec: { showLegend?: bool }// TypeScript type:
interface Spec { show_legendRead more
Perses Plugin Development Reference
> **Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. > **Version range**: Perses v0.45+ (Module Federation plugin architecture) > **Generated**: 2026-05-09 — verify against https://github.com/perses/perses/tree/main/docs/plugins
---
Overview
Perses plugins are independently deployable modules that extend the platform with new panel types, datasources, query types, or variable resolvers. Each plugin has three layers: a CUE schema (defines the spec shape), a Go backend (optional — only for datasource/query plugins), and a React/TypeScript frontend component. The most common failure mode is a mismatch between the CUE schema field names and the TypeScript prop names — the backend validates schema but the frontend silently ignores unknown fields.
---
Pattern Table
| Plugin Type | Has Backend | Frontend Component | CUE Schema Required | |------------|-------------|-------------------|---------------------| | Panel | No | Yes — renders data | Yes | | Datasource | Yes — handles HTTP proxying | Yes — editor UI | Yes | | Query | Yes — transforms raw data | Yes — query editor | Yes | | Variable | Yes (optional) | Yes — variable editor | Yes | | Explore | No | Yes — explore view | Yes |
---
Plugin Scaffolding: percli
Generate a new plugin
# Scaffold a panel plugin percli plugin generate --type panel --name my-chart --output ./plugins/my-chart # Scaffold a datasource plugin percli plugin generate --type datasource --name my-datasource --output ./plugins/my-datasource # Build plugin archive for distribution percli plugin build --dir ./plugins/my-chart --output ./dist/my-chart.tar.gz
Plugin directory structure (panel example)
plugins/my-chart/ ├── plugin.json # Plugin manifest — name, version, type ├── cue/ │ └── schemas/ │ └── my-chart.cue # CUE schema for panel spec ├── src/ │ ├── index.tsx # Plugin entry point — exports PanelPlugin │ ├── MyChart.tsx # React component │ └── types.ts # TypeScript types matching CUE schema ├── package.json └── webpack.config.js # Module Federation config
---
CUE Schema Authoring
Minimal panel spec schema
// cue/schemas/my-chart.cue
package schemas
// MyChartSpec defines the configuration for the MyChart panel.
#MyChartSpec: {
// query is required — references a query variable or inline query
query: string
// thresholds are optional visual thresholds
thresholds?: [...{
value: number
color: string
}]
// legend controls display; defaults to true
showLegend?: bool | *true
}**Why**: Field names in CUE must exactly match the JSON keys sent by the frontend. A field named `showLegend` in CUE but `show_legend` in TypeScript will validate but never populate — the JSON key mismatch is silent.
---
CUE schema validation
# Validate a dashboard JSON against Perses CUE schemas cue vet -d '#Dashboard' ./schemas/ dashboard.json # Validate a plugin spec specifically cue vet -d '#MyChartSpec' ./plugins/my-chart/cue/schemas/ spec.json # Export CUE schema as JSON Schema for editor tooling cue export --out json ./plugins/my-chart/cue/schemas/
---
React/TypeScript Frontend Patterns
Plugin entry point (index.tsx)
import { PanelPlugin } from '@perses-dev/plugin-system';
import { MyChart } from './MyChart';
import { MyChartEditor } from './MyChartEditor';
import type { MyChartSpec } from './types';
export const MyChartPanel: PanelPlugin<MyChartSpec> = {
PanelComponent: MyChart,
spec: {
// Default spec used when panel is first added
initSpec: (): MyChartSpec => ({
query: '',
showLegend: true,
}),
},
editor: {
EditorComponent: MyChartEditor,
},
};**Why**: The `initSpec` function must return a valid default that matches the CUE schema. A missing required field in `initSpec` causes the panel editor to open with a validation error before the user touches anything.
---
Accessing query data in a panel component
import { useDataQueries } from '@perses-dev/plugin-system';
import type { TimeSeriesData } from '@perses-dev/core';
export function MyChart({ spec }: PanelProps<MyChartSpec>) {
const { queryResults, isFetching, error } = useDataQueries('TimeSeriesQuery');
if (isFetching) return <LoadingOverlay />;
if (error) return <ErrorAlert error={error} />;
const data = queryResults[0]?.data as TimeSeriesData | undefined;
if (!data) return <NoDataOverlay />;
return <canvas>{/* render data.series */}</canvas>;
}**Why**: Always handle all three states (`isFetching`, `error`, `!data`). A panel that only handles the happy path shows a blank panel during loading and crashes on query errors, degrading the whole dashboard view.
---
Plugin manifest (plugin.json)
{
"name": "my-chart",
"displayName": "My Chart",
"version": "0.1.0",
"pluginType": "Panel",
"components": [
{
"kind": "MyChart",
"display": {
"name": "My Chart",
"description": "A custom chart panel"
}
}
]
}**Why**: The `kind` field in `plugin.json` must match the `kind` string used in dashboard JSON panel specs. A mismatch causes the panel to render as "Unknown Panel Type" with no useful error.
---
Pattern Catalog: Detection and Fixes
CUE/TypeScript field name mismatch
**Detection**:
# Extract CUE field names grep -rn '^\s*\w\+?:' --include="*.cue" | sed 's/:.*//' | tr -d ' ' # Compare against TypeScript interface fields grep -rn '^\s*\w\+\?:' --include="*.ts" | sed 's/:.*//' | tr -d ' '
**Signal**:
// CUE schema:
#Spec: { showLegend?: bool }// TypeScript type:
interface Spec { show_legendEssays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

