@artemis-studio/plugin-sdk 2026.9.42 → 2026.9.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -9,6 +9,8 @@ export const ApiError = outsideStudio('ApiError');
9
9
  export const CONTRACT = outsideStudio('CONTRACT');
10
10
  export const CapabilityGate = outsideStudio('CapabilityGate');
11
11
  export const ConfirmByTyping = outsideStudio('ConfirmByTyping');
12
+ export const METRIC_RANGES = outsideStudio('METRIC_RANGES');
13
+ export const MetricChart = outsideStudio('MetricChart');
12
14
  export const NAV_GROUPS = outsideStudio('NAV_GROUPS');
13
15
  export const NodeOutcomeSummary = outsideStudio('NodeOutcomeSummary');
14
16
  export const OutcomeSummary = outsideStudio('OutcomeSummary');
@@ -27,3 +29,4 @@ export const request = outsideStudio('request');
27
29
  export const rootRoute = outsideStudio('rootRoute');
28
30
  export const useCan = outsideStudio('useCan');
29
31
  export const useMe = outsideStudio('useMe');
32
+ export const usePluginSeries = outsideStudio('usePluginSeries');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artemis-studio/plugin-sdk",
3
- "version": "2026.9.42",
3
+ "version": "2026.9.44",
4
4
  "description": "Build the UI of an Artemis Studio plugin: typings for the Studio APIs a plugin may use, and a Vite preset.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1700,6 +1700,22 @@ export interface paths {
1700
1700
  patch?: never;
1701
1701
  trace?: never;
1702
1702
  };
1703
+ "/api/v1/clusters/{clusterId}/metrics/plugin": {
1704
+ parameters: {
1705
+ query?: never;
1706
+ header?: never;
1707
+ path?: never;
1708
+ cookie?: never;
1709
+ };
1710
+ get: operations["plugin"];
1711
+ put?: never;
1712
+ post?: never;
1713
+ delete?: never;
1714
+ options?: never;
1715
+ head?: never;
1716
+ patch?: never;
1717
+ trace?: never;
1718
+ };
1703
1719
  "/api/v1/clusters/{clusterId}/health": {
1704
1720
  parameters: {
1705
1721
  query?: never;
@@ -2068,6 +2084,22 @@ export interface paths {
2068
2084
  patch?: never;
2069
2085
  trace?: never;
2070
2086
  };
2087
+ "/api/v1/clusters/{clusterId}/alerts/plugin-metrics": {
2088
+ parameters: {
2089
+ query?: never;
2090
+ header?: never;
2091
+ path?: never;
2092
+ cookie?: never;
2093
+ };
2094
+ get: operations["pluginMetrics"];
2095
+ put?: never;
2096
+ post?: never;
2097
+ delete?: never;
2098
+ options?: never;
2099
+ head?: never;
2100
+ patch?: never;
2101
+ trace?: never;
2102
+ };
2071
2103
  "/api/v1/clusters/{clusterId}/alerts/history": {
2072
2104
  parameters: {
2073
2105
  query?: never;
@@ -2686,6 +2718,8 @@ export interface components {
2686
2718
  scope?: string | null;
2687
2719
  enabled: boolean;
2688
2720
  channelIds: string[];
2721
+ /** @description False when the rule watches a plugin metric whose plugin is not running */
2722
+ sourceAvailable: boolean;
2689
2723
  /** Format: date-time */
2690
2724
  createdAt: string;
2691
2725
  /** Format: date-time */
@@ -5168,6 +5202,13 @@ export interface components {
5168
5202
  /** Format: int32 */
5169
5203
  pageSize: number;
5170
5204
  };
5205
+ PluginMetricView: {
5206
+ metric: string;
5207
+ plugin: string;
5208
+ description: string;
5209
+ unit: string;
5210
+ subject: string;
5211
+ };
5171
5212
  AlertFiringPageView: {
5172
5213
  items: components["schemas"]["AlertFiringView"][];
5173
5214
  /** Format: int64 */
@@ -8542,6 +8583,34 @@ export interface operations {
8542
8583
  };
8543
8584
  };
8544
8585
  };
8586
+ plugin: {
8587
+ parameters: {
8588
+ query?: {
8589
+ metric?: string;
8590
+ subject?: string;
8591
+ from?: string;
8592
+ to?: string;
8593
+ step?: string;
8594
+ };
8595
+ header?: never;
8596
+ path: {
8597
+ clusterId: string;
8598
+ };
8599
+ cookie?: never;
8600
+ };
8601
+ requestBody?: never;
8602
+ responses: {
8603
+ /** @description OK */
8604
+ 200: {
8605
+ headers: {
8606
+ [name: string]: unknown;
8607
+ };
8608
+ content: {
8609
+ "*/*": components["schemas"]["MetricSeriesResponse"];
8610
+ };
8611
+ };
8612
+ };
8613
+ };
8545
8614
  health: {
8546
8615
  parameters: {
8547
8616
  query?: never;
@@ -9095,6 +9164,28 @@ export interface operations {
9095
9164
  };
9096
9165
  };
9097
9166
  };
9167
+ pluginMetrics: {
9168
+ parameters: {
9169
+ query?: never;
9170
+ header?: never;
9171
+ path: {
9172
+ clusterId: string;
9173
+ };
9174
+ cookie?: never;
9175
+ };
9176
+ requestBody?: never;
9177
+ responses: {
9178
+ /** @description OK */
9179
+ 200: {
9180
+ headers: {
9181
+ [name: string]: unknown;
9182
+ };
9183
+ content: {
9184
+ "*/*": components["schemas"]["PluginMetricView"][];
9185
+ };
9186
+ };
9187
+ };
9188
+ };
9098
9189
  history_2: {
9099
9190
  parameters: {
9100
9191
  query?: {
@@ -5,7 +5,7 @@ import type { AnyRoute } from '@tanstack/react-router';
5
5
  import type { NavGroupId } from './nav/groups.ts';
6
6
  import type { SlotContributions } from './slots.ts';
7
7
  /** The extension contract version, shared with the backend's `Contract.VERSION` (ADR-0070). */
8
- export declare const CONTRACT = 1;
8
+ export declare const CONTRACT = 2;
9
9
  /** The module ids the backend manifest reports. A frontend feature uses the same id as its backend module. */
10
10
  export declare const FEATURE_IDS: readonly ["security", "audit", "settings", "stream", "broker", "clusters", "governance", "scrape", "mcp", "queues", "resources", "messages", "routing", "metrics", "alerting", "events", "rr", "sql", "brokerconfig", "flow", "triage", "bulk", "transfer", "setupreview", "apitokens", "plugins", "identity-local", "identity-oidc"];
11
11
  export type FeatureId = (typeof FEATURE_IDS)[number];
@@ -0,0 +1,28 @@
1
+ import type { ApiError } from '../api/request.ts';
2
+ /** Every metric plot is this tall, whatever it is currently able to show. */
3
+ export declare const CHART_HEIGHT = 220;
4
+ /**
5
+ * A titled panel around one metric plot, and the three states it can be in.
6
+ *
7
+ * Failure, emptiness and loading are rendered as three different things at one
8
+ * fixed height (ADR-0055). Both halves of that matter:
9
+ *
10
+ * - a metrics read that returned 500 used to render "No samples in this window
11
+ * yet", which presents an outage as a fact about the cluster — and a flat empty
12
+ * chart during an incident reads as "quiet", the most expensive possible
13
+ * misreading;
14
+ * - a panel that collapses while loading and expands when data arrives moves the
15
+ * page under a pointer already travelling towards something else.
16
+ */
17
+ export declare function ChartPanel({ title, unit, isPending, error, isEmpty, emptyLabel, note, children, }: {
18
+ title: string;
19
+ unit: string;
20
+ isPending: boolean;
21
+ error: ApiError | null;
22
+ /** True when the read succeeded and carried nothing for this window. */
23
+ isEmpty: boolean;
24
+ emptyLabel: string;
25
+ /** A coverage caveat that belongs beside the title, not inside a tooltip. */
26
+ note?: React.ReactNode;
27
+ children: React.ReactNode;
28
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import type { MetricRange } from '../time/ranges.ts';
2
+ /**
3
+ * Studio's time-proportional chart of one plugin metric for one subject (ADR-0055, ADR-0113):
4
+ * a titled panel that tells loading, failure and an empty window apart, over a live range.
5
+ */
6
+ export declare function MetricChart({ clusterId, metric, subject, title, range, emptyLabel, }: {
7
+ clusterId: string;
8
+ metric: string;
9
+ subject: string;
10
+ title: string;
11
+ range?: MetricRange;
12
+ emptyLabel?: string;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,71 @@
1
+ import type { components } from '../api/schema.d.ts';
2
+ import { type MetricRange } from '../time/ranges.ts';
3
+ type MetricSeries = components['schemas']['MetricSeries'];
4
+ /**
5
+ * Format an instant in the operator's chosen zone.
6
+ *
7
+ * Bare `dayjs(ms).format(...)` renders in the browser's zone, which used to leave
8
+ * the charts and the tables disagreeing on every deployment where the two differed
9
+ * — the axis in local time, every table in UTC, and nothing saying so. Everything
10
+ * that renders a metric timestamp goes through here.
11
+ */
12
+ export declare function formatInZone(ms: number, pattern: string): string;
13
+ export declare function tickFormatter(range: MetricRange): (ms: number) => string;
14
+ export declare function labelFormatter(range: MetricRange): (ms: number) => string;
15
+ /**
16
+ * Props for the recharts `XAxis` behind every metric chart.
17
+ *
18
+ * `type: 'number'` with `scale: 'time'` is the whole decision. The explicit
19
+ * `[from, to]` domain — rather than `['dataMin', 'dataMax']` — keeps the plotted
20
+ * width equal to the *requested* window, so a series that stops halfway through
21
+ * the range renders as a chart that is half empty rather than one that silently
22
+ * rescales to whatever data survived.
23
+ */
24
+ export declare function timeAxisProps(range: MetricRange, from: number, to: number): {
25
+ type: "number";
26
+ scale: "time";
27
+ domain: [number, number];
28
+ tickFormatter: (ms: number) => string;
29
+ minTickGap: number;
30
+ stroke: string;
31
+ };
32
+ /**
33
+ * `integral` is for a metric that only ever takes whole values — a consumer
34
+ * count. Without it recharts picks fractional ticks for a small domain, and the
35
+ * integer formatter renders every one of them as the same "0": an axis of five
36
+ * identical labels beside bars of visibly different heights.
37
+ */
38
+ export declare function yAxisProps(options?: {
39
+ integral?: boolean;
40
+ }): {
41
+ allowDecimals?: boolean | undefined;
42
+ stroke: string;
43
+ width: number;
44
+ };
45
+ export declare function gridProps(): {
46
+ stroke: string;
47
+ };
48
+ /** Bucket boundaries a series is expected to cover, used to size the empty case. */
49
+ export declare function bucketCount(range: MetricRange): number;
50
+ /**
51
+ * Merge any number of series onto one row per timestamp, keyed by epoch ms.
52
+ *
53
+ * Series are merged by timestamp rather than by index because a metric with no
54
+ * samples in a bucket is simply omitted from its series, so two series over the
55
+ * same window can carry different bucket sets. Zipping them by position would
56
+ * silently pair a value with the wrong instant.
57
+ */
58
+ export declare function mergeByTimestamp(named: Array<{
59
+ name: string;
60
+ series: MetricSeries | undefined;
61
+ field?: 'value' | 'peak';
62
+ }>): Array<Record<string, number>>;
63
+ /** The last point of a series, or `null` when there is none to state. */
64
+ export declare function latest(series: MetricSeries | undefined): number | null;
65
+ /** The first point of a series, for stating movement across the window. */
66
+ export declare function earliest(series: MetricSeries | undefined): number | null;
67
+ /** Depth runs to millions on a real cluster; an axis tick cannot carry the digits. */
68
+ export declare function formatCount(value: number): string;
69
+ export declare function formatExact(value: number): string;
70
+ export declare function formatRate(value: number): string;
71
+ export {};
@@ -0,0 +1,17 @@
1
+ import { type UseQueryResult } from '@tanstack/react-query';
2
+ import { ApiError } from '../api/request.ts';
3
+ import type { components } from '../api/schema.d.ts';
4
+ import { type MetricRange } from '../time/ranges.ts';
5
+ type MetricSeriesResponse = components['schemas']['MetricSeriesResponse'];
6
+ /**
7
+ * A plugin metric's series for one subject over a live range (ADR-0113). Needs the permission the
8
+ * plugin declared for the metric; without it the read fails like any cluster read the user may
9
+ * not make.
10
+ */
11
+ export declare function usePluginSeries(clusterId: string, metric: string, subject: string, range?: MetricRange): UseQueryResult<MetricSeriesResponse, ApiError> & {
12
+ window: {
13
+ from: string;
14
+ to: string;
15
+ };
16
+ };
17
+ export {};
@@ -22,6 +22,9 @@ export { ConfirmByTyping } from '../ui/ConfirmByTyping.tsx';
22
22
  export { NodeOutcomeSummary, OutcomeSummary, type OutcomeRow } from '../ui/NodeOutcomeSummary.tsx';
23
23
  export { Pager } from '../ui/Pager.tsx';
24
24
  export { VirtualTable, type GridColumn } from '../ui/VirtualTable.tsx';
25
+ export { MetricChart } from '../kernel/metrics/MetricChart.tsx';
26
+ export { usePluginSeries } from '../kernel/metrics/pluginSeries.ts';
27
+ export { METRIC_RANGES, type MetricRange } from '../kernel/time/ranges.ts';
25
28
  /**
26
29
  * Shows a notification in Studio's own notification area. Import this, never
27
30
  * `@mantine/notifications` directly: that is not shared, so a plugin's own copy would show nothing.