@rainbow-robotics/plugin-ui 1.3.8-dev.3 → 1.10.1-dev.0

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/src/manifest.ts CHANGED
@@ -1,135 +1,135 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- /**
3
- * Plugin manifest schema — the single source of truth shared by the front end
4
- * (nexus reads it) and the back end (rby2 mirrors it for validation).
5
- *
6
- * Evolve additively only.
7
- */
8
-
9
- /** Known contribution points the host understands. */
10
- export const CONTRIBUTION_POINTS = [
11
- "toolbar",
12
- "lnb",
13
- "move.panel",
14
- "program.panel",
15
- ] as const;
16
-
17
- export type ContributionPoint = (typeof CONTRIBUTION_POINTS)[number];
18
-
19
- /** How a header.toolbar contribution opens when clicked. */
20
- export type ContributionOpens = "popover" | "panel" | "page";
21
-
22
- export interface PluginContribution {
23
- /** Where this contribution mounts. */
24
- point: ContributionPoint | (string & {});
25
- /** UI entry name relative to ui/ (without extension). Defaults to "content". */
26
- ui?: string;
27
- /** Icon path relative to the plugin root (e.g. "assets/icon.svg"). */
28
- icon?:
29
- | string
30
- | {
31
- "16"?: string;
32
- "48"?: string;
33
- "126"?: string;
34
- };
35
- /** Display label. */
36
- label?: string | Record<string, string>;
37
- /** For header.toolbar: how the contribution surfaces. */
38
- opens?: ContributionOpens;
39
- commandInfo?: {
40
- name: string;
41
- icon: string;
42
- funcName: string;
43
- args?: Record<string, any>;
44
- };
45
- }
46
-
47
- export interface PluginAction {
48
- /** Action id referenced by `rb.action(id, payload)`. */
49
- id: string;
50
- /** Transport topic (Zenoh / ROS2) the back end routes to. */
51
- topic: string;
52
- /** Optional payload schema hint (field → type name). */
53
- schema?: Record<string, string>;
54
- }
55
-
56
- /** Allowed variable value types exposed to pyfm. */
57
- export const VARIABLE_TYPES = ["number", "string", "boolean", "array"] as const;
58
-
59
- export type PluginVariableType = (typeof VARIABLE_TYPES)[number];
60
-
61
- /** A variable the plugin pre-declares for use in pyfm programs. */
62
- export interface PluginVariable {
63
- /** Identifier, must start with "RBP_" (then letters, digits, underscore). Unique per plugin. */
64
- name: `RBP_${string}`;
65
- type: PluginVariableType;
66
- /** Initial value — must match `type`. */
67
- init: number | string | boolean | any[];
68
- /** Optional. Without it the UI cannot explain what the variable is for. */
69
- description?: string | Record<string, string>;
70
- }
71
-
72
- export type PluginRuntime =
73
- | { type: "none" }
74
- | { type: "python"; entry: string }
75
- | {
76
- type: "ros2";
77
- package: string;
78
- launch: string;
79
- rosClient: "rclpy" | "rclcpp";
80
- };
81
-
82
- export interface PluginManifest {
83
- /** Reverse-DNS unique id, e.g. "com.rainbow.gripper-tool". */
84
- id: string;
85
- /** Human-readable name. */
86
- name: string;
87
- /** Plugin version (semver). */
88
- version: string;
89
- /** Minimum host (Muscat) version this plugin supports. */
90
- minAppVersion?: string;
91
- /** Icon path relative to plugin root. */
92
- icons:
93
- | string
94
- | {
95
- "16"?: string;
96
- "48"?: string;
97
- "128"?: string;
98
- };
99
- /** UI / chrome contributions. */
100
- contributes?: PluginContribution[];
101
- /** Host actions the plugin can invoke. */
102
- actions?: PluginAction[];
103
- /** Variables pre-declared for pyfm; the variable picker UI lists these. */
104
- variables?: PluginVariable[];
105
- /** Back-end runtime declaration. */
106
- runtime?: PluginRuntime;
107
- }
108
-
109
- /**
110
- * Lightweight structural check. Returns the list of problems (empty = valid).
111
- * Kept dependency-free so both front and back ends can reuse it; richer
112
- * validation (zod) can layer on top later without breaking this contract.
113
- */
114
- export function validateManifest(value: unknown): string[] {
115
- const errors: string[] = [];
116
- if (typeof value !== "object" || value === null) {
117
- return ["manifest must be an object"];
118
- }
119
- const m = value as Record<string, unknown>;
120
- if (typeof m.id !== "string" || m.id.length === 0)
121
- errors.push("manifest.id is required (string)");
122
- else if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*(\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*$/.test(m.id))
123
- errors.push('manifest.id must be "."-separated parts of lowercase letters/digits and single "-", each starting with a letter (e.g. com.example.my-plugin)');
124
- if (typeof m.name !== "string" || m.name.length === 0)
125
- errors.push("manifest.name is required (string)");
126
- if (typeof m.version !== "string" || m.version.length === 0)
127
- errors.push("manifest.version is required (string)");
128
- if (m.contributes !== undefined && !Array.isArray(m.contributes))
129
- errors.push("manifest.contributes must be an array");
130
- if (m.actions !== undefined && !Array.isArray(m.actions))
131
- errors.push("manifest.actions must be an array");
132
- if (m.variables !== undefined && !Array.isArray(m.variables))
133
- errors.push("manifest.variables must be an array");
134
- return errors;
135
- }
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * Plugin manifest schema — the single source of truth shared by the front end
4
+ * (nexus reads it) and the back end (rby2 mirrors it for validation).
5
+ *
6
+ * Evolve additively only.
7
+ */
8
+
9
+ /** Known contribution points the host understands. */
10
+ export const CONTRIBUTION_POINTS = [
11
+ "toolbar",
12
+ "lnb",
13
+ "move.panel",
14
+ "program.panel",
15
+ ] as const;
16
+
17
+ export type ContributionPoint = (typeof CONTRIBUTION_POINTS)[number];
18
+
19
+ /** How a header.toolbar contribution opens when clicked. */
20
+ export type ContributionOpens = "popover" | "panel" | "page";
21
+
22
+ export interface PluginContribution {
23
+ /** Where this contribution mounts. */
24
+ point: ContributionPoint | (string & {});
25
+ /** UI entry name relative to ui/ (without extension). Defaults to "content". */
26
+ ui?: string;
27
+ /** Icon path relative to the plugin root (e.g. "assets/icon.svg"). */
28
+ icon?:
29
+ | string
30
+ | {
31
+ "16"?: string;
32
+ "48"?: string;
33
+ "126"?: string;
34
+ };
35
+ /** Display label. */
36
+ label?: string | Record<string, string>;
37
+ /** For header.toolbar: how the contribution surfaces. */
38
+ opens?: ContributionOpens;
39
+ commandInfo?: {
40
+ name: string;
41
+ icon: string;
42
+ funcName: string;
43
+ args?: Record<string, any>;
44
+ };
45
+ }
46
+
47
+ export interface PluginAction {
48
+ /** Action id referenced by `rb.action(id, payload)`. */
49
+ id: string;
50
+ /** Transport topic (Zenoh / ROS2) the back end routes to. */
51
+ topic: string;
52
+ /** Optional payload schema hint (field → type name). */
53
+ schema?: Record<string, string>;
54
+ }
55
+
56
+ /** Allowed variable value types exposed to pyfm. */
57
+ export const VARIABLE_TYPES = ["number", "string", "boolean", "array"] as const;
58
+
59
+ export type PluginVariableType = (typeof VARIABLE_TYPES)[number];
60
+
61
+ /** A variable the plugin pre-declares for use in pyfm programs. */
62
+ export interface PluginVariable {
63
+ /** Identifier, must start with "RBP_" (then letters, digits, underscore). Unique per plugin. */
64
+ name: `RBP_${string}`;
65
+ type: PluginVariableType;
66
+ /** Initial value — must match `type`. */
67
+ init: number | string | boolean | any[];
68
+ /** Optional. Without it the UI cannot explain what the variable is for. */
69
+ description?: string | Record<string, string>;
70
+ }
71
+
72
+ export type PluginRuntime =
73
+ | { type: "none" }
74
+ | { type: "python"; entry: string }
75
+ | {
76
+ type: "ros2";
77
+ package: string;
78
+ launch: string;
79
+ rosClient: "rclpy" | "rclcpp";
80
+ };
81
+
82
+ export interface PluginManifest {
83
+ /** Reverse-DNS unique id, e.g. "com.rainbow.gripper-tool". */
84
+ id: string;
85
+ /** Human-readable name. */
86
+ name: string;
87
+ /** Plugin version (semver). */
88
+ version: string;
89
+ /** Minimum host (Muscat) version this plugin supports. */
90
+ minAppVersion?: string;
91
+ /** Icon path relative to plugin root. */
92
+ icons:
93
+ | string
94
+ | {
95
+ "16"?: string;
96
+ "48"?: string;
97
+ "128"?: string;
98
+ };
99
+ /** UI / chrome contributions. */
100
+ contributes?: PluginContribution[];
101
+ /** Host actions the plugin can invoke. */
102
+ actions?: PluginAction[];
103
+ /** Variables pre-declared for pyfm; the variable picker UI lists these. */
104
+ variables?: PluginVariable[];
105
+ /** Back-end runtime declaration. */
106
+ runtime?: PluginRuntime;
107
+ }
108
+
109
+ /**
110
+ * Lightweight structural check. Returns the list of problems (empty = valid).
111
+ * Kept dependency-free so both front and back ends can reuse it; richer
112
+ * validation (zod) can layer on top later without breaking this contract.
113
+ */
114
+ export function validateManifest(value: unknown): string[] {
115
+ const errors: string[] = [];
116
+ if (typeof value !== "object" || value === null) {
117
+ return ["manifest must be an object"];
118
+ }
119
+ const m = value as Record<string, unknown>;
120
+ if (typeof m.id !== "string" || m.id.length === 0)
121
+ errors.push("manifest.id is required (string)");
122
+ else if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*(\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*$/.test(m.id))
123
+ errors.push('manifest.id must be "."-separated parts of lowercase letters/digits and single "-", each starting with a letter (e.g. com.example.my-plugin)');
124
+ if (typeof m.name !== "string" || m.name.length === 0)
125
+ errors.push("manifest.name is required (string)");
126
+ if (typeof m.version !== "string" || m.version.length === 0)
127
+ errors.push("manifest.version is required (string)");
128
+ if (m.contributes !== undefined && !Array.isArray(m.contributes))
129
+ errors.push("manifest.contributes must be an array");
130
+ if (m.actions !== undefined && !Array.isArray(m.actions))
131
+ errors.push("manifest.actions must be an array");
132
+ if (m.variables !== undefined && !Array.isArray(m.variables))
133
+ errors.push("manifest.variables must be an array");
134
+ return errors;
135
+ }