mirror of
https://github.com/soconnor0919/robot-plugins.git
synced 2026-03-24 03:37:51 -04:00
Compare commits
5 Commits
31beaffc5b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d772aecc54 | ||
|
|
14137ba631 | ||
|
|
9e0921c69c | ||
|
|
d3a9093d67 | ||
|
|
817ee69b87 |
@@ -243,10 +243,7 @@
|
|||||||
"description": "Angular velocity in rad/s"
|
"description": "Angular velocity in rad/s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["linear", "angular"]
|
||||||
"linear",
|
|
||||||
"angular"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "geometry_msgs/msg/Twist",
|
"messageType": "geometry_msgs/msg/Twist",
|
||||||
@@ -289,9 +286,7 @@
|
|||||||
"description": "Duration to walk in seconds (0 = indefinite)"
|
"description": "Duration to walk in seconds (0 = indefinite)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["speed"]
|
||||||
"speed"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "geometry_msgs/msg/Twist",
|
"messageType": "geometry_msgs/msg/Twist",
|
||||||
@@ -356,9 +351,7 @@
|
|||||||
"description": "Duration to walk in seconds (0 = indefinite)"
|
"description": "Duration to walk in seconds (0 = indefinite)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["speed"]
|
||||||
"speed"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "geometry_msgs/msg/Twist",
|
"messageType": "geometry_msgs/msg/Twist",
|
||||||
@@ -423,9 +416,7 @@
|
|||||||
"description": "Duration to turn in seconds (0 = indefinite)"
|
"description": "Duration to turn in seconds (0 = indefinite)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["speed"]
|
||||||
"speed"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "geometry_msgs/msg/Twist",
|
"messageType": "geometry_msgs/msg/Twist",
|
||||||
@@ -490,9 +481,7 @@
|
|||||||
"description": "Duration to turn in seconds (0 = indefinite)"
|
"description": "Duration to turn in seconds (0 = indefinite)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["speed"]
|
||||||
"speed"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "geometry_msgs/msg/Twist",
|
"messageType": "geometry_msgs/msg/Twist",
|
||||||
@@ -587,9 +576,7 @@
|
|||||||
"description": "Text to speak"
|
"description": "Text to speak"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["text"]
|
||||||
"text"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "std_msgs/msg/String",
|
"messageType": "std_msgs/msg/String",
|
||||||
@@ -615,10 +602,10 @@
|
|||||||
{
|
{
|
||||||
"id": "say_with_emotion",
|
"id": "say_with_emotion",
|
||||||
"name": "Say Text with Emotion",
|
"name": "Say Text with Emotion",
|
||||||
"description": "Speak text with emotional expression using SSML-like markup",
|
"description": "Speak text with emotional expression and animated gestures. Emotions: happy (excited gestures), sad (slower, lower pitch), neutral, excited (fast, animated), calm (slower, relaxed)",
|
||||||
"category": "interaction",
|
"category": "interaction",
|
||||||
"icon": "heart",
|
"icon": "heart",
|
||||||
"timeout": 15000,
|
"timeout": 20000,
|
||||||
"retryable": true,
|
"retryable": true,
|
||||||
"parameterSchema": {
|
"parameterSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -630,15 +617,9 @@
|
|||||||
},
|
},
|
||||||
"emotion": {
|
"emotion": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["neutral", "happy", "sad", "excited", "calm"],
|
||||||
"neutral",
|
|
||||||
"happy",
|
|
||||||
"sad",
|
|
||||||
"excited",
|
|
||||||
"calm"
|
|
||||||
],
|
|
||||||
"default": "neutral",
|
"default": "neutral",
|
||||||
"description": "Emotional tone for speech"
|
"description": "Emotional tone: happy (animated), sad (slow/low pitch), excited (fast + gestures), calm (slower + relaxed)"
|
||||||
},
|
},
|
||||||
"speed": {
|
"speed": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -648,18 +629,14 @@
|
|||||||
"description": "Speech speed multiplier"
|
"description": "Speech speed multiplier"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["text"]
|
||||||
"text"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "std_msgs/msg/String",
|
"messageType": "std_msgs/msg/String",
|
||||||
"topic": "/speech",
|
"topic": "/speech",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "static",
|
"type": "transform",
|
||||||
"payload": {
|
"transformFn": "transformToEmotionalSpeech"
|
||||||
"data": "\\rspd={{speed}}\\\\rst={{emotion}}\\{{text}}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"implementation": {
|
"implementation": {
|
||||||
@@ -690,9 +667,7 @@
|
|||||||
"description": "Volume level (0.0 = silent, 1.0 = maximum)"
|
"description": "Volume level (0.0 = silent, 1.0 = maximum)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["volume"]
|
||||||
"volume"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "std_msgs/msg/Float32",
|
"messageType": "std_msgs/msg/Float32",
|
||||||
@@ -733,9 +708,7 @@
|
|||||||
"description": "Speech language"
|
"description": "Speech language"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["language"]
|
||||||
"language"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "std_msgs/msg/String",
|
"messageType": "std_msgs/msg/String",
|
||||||
@@ -781,10 +754,7 @@
|
|||||||
"description": "Movement speed (0.1 = slow, 1.0 = fast)"
|
"description": "Movement speed (0.1 = slow, 1.0 = fast)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["yaw", "pitch"]
|
||||||
"yaw",
|
|
||||||
"pitch"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
@@ -792,14 +762,8 @@
|
|||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "static",
|
"type": "static",
|
||||||
"payload": {
|
"payload": {
|
||||||
"joint_names": [
|
"joint_names": ["HeadYaw", "HeadPitch"],
|
||||||
"HeadYaw",
|
"joint_angles": ["{{yaw}}", "{{pitch}}"],
|
||||||
"HeadPitch"
|
|
||||||
],
|
|
||||||
"joint_angles": [
|
|
||||||
"{{yaw}}",
|
|
||||||
"{{pitch}}"
|
|
||||||
],
|
|
||||||
"speed": "{{speed}}"
|
"speed": "{{speed}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,10 +792,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"arm": {
|
"arm": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["left", "right"],
|
||||||
"left",
|
|
||||||
"right"
|
|
||||||
],
|
|
||||||
"default": "right",
|
"default": "right",
|
||||||
"description": "Which arm to control"
|
"description": "Which arm to control"
|
||||||
},
|
},
|
||||||
@@ -900,6 +861,26 @@
|
|||||||
"speed": "{{speed}}"
|
"speed": "{{speed}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"messageTemplate": {
|
||||||
|
"joint_names": [
|
||||||
|
"{{arm === 'left' ? 'L' : 'R'}}ShoulderPitch",
|
||||||
|
"{{arm === 'left' ? 'L' : 'R'}}ShoulderRoll",
|
||||||
|
"{{arm === 'left' ? 'L' : 'R'}}ElbowYaw",
|
||||||
|
"{{arm === 'left' ? 'L' : 'R'}}ElbowRoll"
|
||||||
|
],
|
||||||
|
"joint_angles": [
|
||||||
|
"{{shoulder_pitch}}",
|
||||||
|
"{{shoulder_roll}}",
|
||||||
|
"{{elbow_yaw}}",
|
||||||
|
"{{elbow_roll}}"
|
||||||
|
],
|
||||||
|
"speed": "{{speed}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -958,10 +939,7 @@
|
|||||||
"description": "Movement speed (fraction of max)"
|
"description": "Movement speed (fraction of max)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["joint_name", "angle"]
|
||||||
"joint_name",
|
|
||||||
"angle"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
@@ -1005,10 +983,7 @@
|
|||||||
"description": "Movement speed fraction"
|
"description": "Movement speed fraction"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["yaw", "pitch"]
|
||||||
"yaw",
|
|
||||||
"pitch"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
@@ -1042,21 +1017,16 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"camera": {
|
"camera": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["front", "bottom"],
|
||||||
"front",
|
|
||||||
"bottom"
|
|
||||||
],
|
|
||||||
"default": "front",
|
"default": "front",
|
||||||
"description": "Camera to use"
|
"description": "Camera to use"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["camera"]
|
||||||
"camera"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "sensor_msgs/msg/Image",
|
"messageType": "sensor_msgs/msg/Image",
|
||||||
"topic": "/naoqi_driver/camera/{camera}/image_raw",
|
"topic": "/camera/{camera}/image_raw",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getCameraImage"
|
"transformFn": "getCameraImage"
|
||||||
@@ -1082,7 +1052,7 @@
|
|||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "sensor_msgs/msg/JointState",
|
"messageType": "sensor_msgs/msg/JointState",
|
||||||
"topic": "/naoqi_driver/joint_states",
|
"topic": "/joint_states",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getJointStates"
|
"transformFn": "getJointStates"
|
||||||
@@ -1108,7 +1078,7 @@
|
|||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "sensor_msgs/msg/Imu",
|
"messageType": "sensor_msgs/msg/Imu",
|
||||||
"topic": "/naoqi_driver/imu/torso",
|
"topic": "/imu/torso",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getImuData"
|
"transformFn": "getImuData"
|
||||||
@@ -1134,7 +1104,7 @@
|
|||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "naoqi_bridge_msgs/msg/Bumper",
|
"messageType": "naoqi_bridge_msgs/msg/Bumper",
|
||||||
"topic": "/naoqi_driver/bumper",
|
"topic": "/bumper",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getBumperStatus"
|
"transformFn": "getBumperStatus"
|
||||||
@@ -1154,21 +1124,16 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"sensor_type": {
|
"sensor_type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["hand", "head"],
|
||||||
"hand",
|
|
||||||
"head"
|
|
||||||
],
|
|
||||||
"default": "hand",
|
"default": "hand",
|
||||||
"description": "Touch sensor type to read"
|
"description": "Touch sensor type to read"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["sensor_type"]
|
||||||
"sensor_type"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "naoqi_bridge_msgs/msg/HandTouch",
|
"messageType": "naoqi_bridge_msgs/msg/HandTouch",
|
||||||
"topic": "/naoqi_driver/{sensor_type}_touch",
|
"topic": "/{sensor_type}_touch",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getTouchSensors"
|
"transformFn": "getTouchSensors"
|
||||||
@@ -1188,22 +1153,16 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["left", "right", "both"],
|
||||||
"left",
|
|
||||||
"right",
|
|
||||||
"both"
|
|
||||||
],
|
|
||||||
"default": "both",
|
"default": "both",
|
||||||
"description": "Sonar sensor to read"
|
"description": "Sonar sensor to read"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["sensor"]
|
||||||
"sensor"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "sensor_msgs/msg/Range",
|
"messageType": "sensor_msgs/msg/Range",
|
||||||
"topic": "/naoqi_driver/sonar/{sensor}",
|
"topic": "/sonar/{sensor}",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getSonarRange"
|
"transformFn": "getSonarRange"
|
||||||
@@ -1225,7 +1184,7 @@
|
|||||||
},
|
},
|
||||||
"ros2": {
|
"ros2": {
|
||||||
"messageType": "naoqi_bridge_msgs/msg/RobotInfo",
|
"messageType": "naoqi_bridge_msgs/msg/RobotInfo",
|
||||||
"topic": "/naoqi_driver/info",
|
"topic": "/info",
|
||||||
"payloadMapping": {
|
"payloadMapping": {
|
||||||
"type": "transform",
|
"type": "transform",
|
||||||
"transformFn": "getRobotInfo"
|
"transformFn": "getRobotInfo"
|
||||||
@@ -1277,6 +1236,312 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bow",
|
||||||
|
"name": "Bow",
|
||||||
|
"description": "Perform a polite bow gesture (head down + lean forward + return)",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "user-check",
|
||||||
|
"timeout": 2000,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "static",
|
||||||
|
"payload": {
|
||||||
|
"joint_names": ["HeadYaw", "HeadPitch"],
|
||||||
|
"joint_angles": [0, 0.5],
|
||||||
|
"speed": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"messageTemplate": {
|
||||||
|
"joint_names": ["HeadYaw", "HeadPitch"],
|
||||||
|
"joint_angles": [0, 0.5],
|
||||||
|
"speed": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wave",
|
||||||
|
"name": "Wave",
|
||||||
|
"description": "Perform a friendly wave gesture with right arm",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "hand",
|
||||||
|
"timeout": 2000,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "static",
|
||||||
|
"payload": {
|
||||||
|
"joint_names": [
|
||||||
|
"RShoulderPitch",
|
||||||
|
"RShoulderRoll",
|
||||||
|
"RElbowYaw",
|
||||||
|
"RElbowRoll"
|
||||||
|
],
|
||||||
|
"joint_angles": [1.5, 0.2, -1.0, 0.5],
|
||||||
|
"speed": 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"messageTemplate": {
|
||||||
|
"joint_names": [
|
||||||
|
"RShoulderPitch",
|
||||||
|
"RShoulderRoll",
|
||||||
|
"RElbowYaw",
|
||||||
|
"RElbowRoll"
|
||||||
|
],
|
||||||
|
"joint_angles": [1.5, 0.2, -1.0, 0.5],
|
||||||
|
"speed": 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nod",
|
||||||
|
"name": "Nod",
|
||||||
|
"description": "Perform a nodding gesture (head up and down)",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "chevrons-down",
|
||||||
|
"timeout": 1500,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "static",
|
||||||
|
"payload": {
|
||||||
|
"joint_names": ["HeadPitch"],
|
||||||
|
"joint_angles": [0.3],
|
||||||
|
"speed": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"messageTemplate": {
|
||||||
|
"joint_names": ["HeadPitch"],
|
||||||
|
"joint_angles": [0.3],
|
||||||
|
"speed": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shake_head",
|
||||||
|
"name": "Shake Head",
|
||||||
|
"description": "Perform a head shaking gesture (no)",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "x-circle",
|
||||||
|
"timeout": 1500,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "static",
|
||||||
|
"payload": {
|
||||||
|
"joint_names": ["HeadYaw"],
|
||||||
|
"joint_angles": [0.4],
|
||||||
|
"speed": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"messageTemplate": {
|
||||||
|
"joint_names": ["HeadYaw"],
|
||||||
|
"joint_angles": [0.4],
|
||||||
|
"speed": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "point",
|
||||||
|
"name": "Point",
|
||||||
|
"description": "Point at something with left arm",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "finger-pointer",
|
||||||
|
"timeout": 1500,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "static",
|
||||||
|
"payload": {
|
||||||
|
"joint_names": [
|
||||||
|
"LShoulderPitch",
|
||||||
|
"LShoulderRoll",
|
||||||
|
"LElbowYaw",
|
||||||
|
"LElbowRoll",
|
||||||
|
"LWristYaw"
|
||||||
|
],
|
||||||
|
"joint_angles": [0.8, 0.3, -1.0, 0.1, 0],
|
||||||
|
"speed": 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"messageTemplate": {
|
||||||
|
"joint_names": [
|
||||||
|
"LShoulderPitch",
|
||||||
|
"LShoulderRoll",
|
||||||
|
"LElbowYaw",
|
||||||
|
"LElbowRoll",
|
||||||
|
"LWristYaw"
|
||||||
|
],
|
||||||
|
"joint_angles": [0.8, 0.3, -1.0, 0.1, 0],
|
||||||
|
"speed": 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "greet",
|
||||||
|
"name": "Greet",
|
||||||
|
"description": "Combined greeting gesture: bow + wave",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "sparkles",
|
||||||
|
"timeout": 3000,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"topic": "/joint_angles",
|
||||||
|
"messageType": "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "static",
|
||||||
|
"payload": {
|
||||||
|
"joint_names": ["HeadYaw", "HeadPitch"],
|
||||||
|
"joint_angles": [0, 0.3],
|
||||||
|
"speed": 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wave_goodbye",
|
||||||
|
"name": "Wave Goodbye",
|
||||||
|
"description": "Animated wave goodbye gesture with speech",
|
||||||
|
"category": "interaction",
|
||||||
|
"icon": "hand",
|
||||||
|
"timeout": 4000,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Goodbye!",
|
||||||
|
"description": "Text to say while waving"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"messageType": "std_msgs/msg/String",
|
||||||
|
"topic": "/speech",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "transform",
|
||||||
|
"transformFn": "transformToWaveGoodbye"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/speech",
|
||||||
|
"messageType": "std_msgs/msg/String",
|
||||||
|
"messageTemplate": {
|
||||||
|
"data": "{{text}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "play_animation",
|
||||||
|
"name": "Play Animation",
|
||||||
|
"description": "Play a predefined NAO animation/gesture",
|
||||||
|
"category": "movement",
|
||||||
|
"icon": "play",
|
||||||
|
"timeout": 5000,
|
||||||
|
"retryable": true,
|
||||||
|
"parameterSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"animation": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Hey_1",
|
||||||
|
"Happy_1",
|
||||||
|
"Happy_4",
|
||||||
|
"Enthusiastic_1",
|
||||||
|
"Yes_1",
|
||||||
|
"Yes_2",
|
||||||
|
"No_1",
|
||||||
|
"Blow_1",
|
||||||
|
"Gesture_Ok_1",
|
||||||
|
"Gesture_Nice_1",
|
||||||
|
"Gesture_You_1"
|
||||||
|
],
|
||||||
|
"default": "Hey_1",
|
||||||
|
"description": "Animation to play"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["animation"]
|
||||||
|
},
|
||||||
|
"ros2": {
|
||||||
|
"messageType": "std_msgs/msg/String",
|
||||||
|
"topic": "/speech",
|
||||||
|
"payloadMapping": {
|
||||||
|
"type": "transform",
|
||||||
|
"transformFn": "transformToAnimation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"type": "ros2_topic",
|
||||||
|
"topic": "/speech",
|
||||||
|
"messageType": "std_msgs/msg/String",
|
||||||
|
"messageTemplate": {
|
||||||
|
"data": "^start(animations/Stand/Gestures/{{animation}})"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,35 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
// Color output helpers
|
// Color output helpers
|
||||||
const colors = {
|
const colors = {
|
||||||
red: '\x1b[31m',
|
red: "\x1b[31m",
|
||||||
green: '\x1b[32m',
|
green: "\x1b[32m",
|
||||||
yellow: '\x1b[33m',
|
yellow: "\x1b[33m",
|
||||||
blue: '\x1b[34m',
|
blue: "\x1b[34m",
|
||||||
reset: '\x1b[0m'
|
reset: "\x1b[0m",
|
||||||
};
|
};
|
||||||
|
|
||||||
function log(message, color = 'reset') {
|
function log(message, color = "reset") {
|
||||||
console.log(`${colors[color]}${message}${colors.reset}`);
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function error(message) {
|
function error(message) {
|
||||||
log(`❌ ${message}`, 'red');
|
log(`❌ ${message}`, "red");
|
||||||
}
|
}
|
||||||
|
|
||||||
function success(message) {
|
function success(message) {
|
||||||
log(`✅ ${message}`, 'green');
|
log(`✅ ${message}`, "green");
|
||||||
}
|
}
|
||||||
|
|
||||||
function warn(message) {
|
function warn(message) {
|
||||||
log(`⚠️ ${message}`, 'yellow');
|
log(`⚠️ ${message}`, "yellow");
|
||||||
}
|
}
|
||||||
|
|
||||||
function info(message) {
|
function info(message) {
|
||||||
log(`ℹ️ ${message}`, 'blue');
|
log(`ℹ️ ${message}`, "blue");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin schema validation
|
// Plugin schema validation
|
||||||
@@ -40,7 +40,7 @@ function validatePlugin(pluginPath) {
|
|||||||
|
|
||||||
let plugin;
|
let plugin;
|
||||||
try {
|
try {
|
||||||
plugin = JSON.parse(fs.readFileSync(pluginPath, 'utf8'));
|
plugin = JSON.parse(fs.readFileSync(pluginPath, "utf8"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Invalid JSON syntax: ${e.message}`);
|
throw new Error(`Invalid JSON syntax: ${e.message}`);
|
||||||
}
|
}
|
||||||
@@ -50,17 +50,17 @@ function validatePlugin(pluginPath) {
|
|||||||
|
|
||||||
// Required fields validation
|
// Required fields validation
|
||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
'robotId',
|
"robotId",
|
||||||
'name',
|
"name",
|
||||||
'platform',
|
"platform",
|
||||||
'version',
|
"version",
|
||||||
'pluginApiVersion',
|
"pluginApiVersion",
|
||||||
'hriStudioVersion',
|
"hriStudioVersion",
|
||||||
'trustLevel',
|
"trustLevel",
|
||||||
'category'
|
"category",
|
||||||
];
|
];
|
||||||
|
|
||||||
requiredFields.forEach(field => {
|
requiredFields.forEach((field) => {
|
||||||
if (!plugin[field]) {
|
if (!plugin[field]) {
|
||||||
errors.push(`Missing required field: ${field}`);
|
errors.push(`Missing required field: ${field}`);
|
||||||
}
|
}
|
||||||
@@ -68,36 +68,43 @@ function validatePlugin(pluginPath) {
|
|||||||
|
|
||||||
// Field format validation
|
// Field format validation
|
||||||
if (plugin.robotId && !/^[a-z0-9-]+$/.test(plugin.robotId)) {
|
if (plugin.robotId && !/^[a-z0-9-]+$/.test(plugin.robotId)) {
|
||||||
errors.push('robotId must be lowercase with hyphens only');
|
errors.push("robotId must be lowercase with hyphens only");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.version && !/^\d+\.\d+\.\d+/.test(plugin.version)) {
|
if (plugin.version && !/^\d+\.\d+\.\d+/.test(plugin.version)) {
|
||||||
errors.push('version must follow semantic versioning (e.g., 1.0.0)');
|
errors.push("version must follow semantic versioning (e.g., 1.0.0)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.trustLevel && !['official', 'verified', 'community'].includes(plugin.trustLevel)) {
|
if (
|
||||||
errors.push(`Invalid trustLevel: ${plugin.trustLevel}. Must be: official, verified, or community`);
|
plugin.trustLevel &&
|
||||||
|
!["official", "verified", "community"].includes(plugin.trustLevel)
|
||||||
|
) {
|
||||||
|
errors.push(
|
||||||
|
`Invalid trustLevel: ${plugin.trustLevel}. Must be: official, verified, or community`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category validation
|
// Category validation
|
||||||
const validCategories = [
|
const validCategories = [
|
||||||
'mobile-robot',
|
"mobile-robot",
|
||||||
'humanoid-robot',
|
"humanoid-robot",
|
||||||
'manipulator',
|
"manipulator",
|
||||||
'drone',
|
"drone",
|
||||||
'sensor-platform',
|
"sensor-platform",
|
||||||
'simulation'
|
"simulation",
|
||||||
];
|
];
|
||||||
|
|
||||||
if (plugin.category && !validCategories.includes(plugin.category)) {
|
if (plugin.category && !validCategories.includes(plugin.category)) {
|
||||||
errors.push(`Invalid category: ${plugin.category}. Valid categories: ${validCategories.join(', ')}`);
|
errors.push(
|
||||||
|
`Invalid category: ${plugin.category}. Valid categories: ${validCategories.join(", ")}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions validation
|
// Actions validation
|
||||||
if (!plugin.actions || !Array.isArray(plugin.actions)) {
|
if (!plugin.actions || !Array.isArray(plugin.actions)) {
|
||||||
errors.push('Plugin must have an actions array');
|
errors.push("Plugin must have an actions array");
|
||||||
} else if (plugin.actions.length === 0) {
|
} else if (plugin.actions.length === 0) {
|
||||||
warnings.push('Plugin has no actions defined');
|
warnings.push("Plugin has no actions defined");
|
||||||
} else {
|
} else {
|
||||||
plugin.actions.forEach((action, index) => {
|
plugin.actions.forEach((action, index) => {
|
||||||
const actionErrors = validateAction(action, index);
|
const actionErrors = validateAction(action, index);
|
||||||
@@ -108,37 +115,43 @@ function validatePlugin(pluginPath) {
|
|||||||
// Assets validation
|
// Assets validation
|
||||||
if (plugin.assets) {
|
if (plugin.assets) {
|
||||||
if (!plugin.assets.thumbnailUrl) {
|
if (!plugin.assets.thumbnailUrl) {
|
||||||
errors.push('assets.thumbnailUrl is required');
|
errors.push("assets.thumbnailUrl is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if asset paths exist
|
// Check if asset paths exist
|
||||||
const assetChecks = [
|
const assetChecks = [
|
||||||
['thumbnailUrl', plugin.assets.thumbnailUrl],
|
["thumbnailUrl", plugin.assets.thumbnailUrl],
|
||||||
['main image', plugin.assets.images?.main],
|
["main image", plugin.assets.images?.main],
|
||||||
['logo', plugin.assets.images?.logo]
|
["logo", plugin.assets.images?.logo],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (plugin.assets.images?.angles) {
|
if (plugin.assets.images?.angles) {
|
||||||
Object.entries(plugin.assets.images.angles).forEach(([angle, assetPath]) => {
|
Object.entries(plugin.assets.images.angles).forEach(
|
||||||
|
([angle, assetPath]) => {
|
||||||
assetChecks.push([`${angle} angle`, assetPath]);
|
assetChecks.push([`${angle} angle`, assetPath]);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assetChecks.forEach(([description, assetPath]) => {
|
assetChecks.forEach(([description, assetPath]) => {
|
||||||
if (assetPath && assetPath.startsWith('assets/')) {
|
if (assetPath && assetPath.startsWith("assets/")) {
|
||||||
const fullPath = path.resolve(path.dirname(pluginPath), '..', assetPath);
|
const fullPath = path.resolve(
|
||||||
|
path.dirname(pluginPath),
|
||||||
|
"..",
|
||||||
|
assetPath,
|
||||||
|
);
|
||||||
if (!fs.existsSync(fullPath)) {
|
if (!fs.existsSync(fullPath)) {
|
||||||
warnings.push(`Asset not found: ${description} (${assetPath})`);
|
warnings.push(`Asset not found: ${description} (${assetPath})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
errors.push('Plugin must have assets definition');
|
errors.push("Plugin must have assets definition");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manufacturer validation
|
// Manufacturer validation
|
||||||
if (!plugin.manufacturer?.name) {
|
if (!plugin.manufacturer?.name) {
|
||||||
warnings.push('manufacturer.name is recommended');
|
warnings.push("manufacturer.name is recommended");
|
||||||
}
|
}
|
||||||
|
|
||||||
return { errors, warnings, plugin };
|
return { errors, warnings, plugin };
|
||||||
@@ -148,8 +161,8 @@ function validateAction(action, index) {
|
|||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
// Required action fields
|
// Required action fields
|
||||||
const requiredFields = ['id', 'name', 'category', 'parameterSchema'];
|
const requiredFields = ["id", "name", "category", "parameterSchema"];
|
||||||
requiredFields.forEach(field => {
|
requiredFields.forEach((field) => {
|
||||||
if (!action[field]) {
|
if (!action[field]) {
|
||||||
errors.push(`Action ${index}: missing required field '${field}'`);
|
errors.push(`Action ${index}: missing required field '${field}'`);
|
||||||
}
|
}
|
||||||
@@ -157,18 +170,22 @@ function validateAction(action, index) {
|
|||||||
|
|
||||||
// Action ID format
|
// Action ID format
|
||||||
if (action.id && !/^[a-z_]+$/.test(action.id)) {
|
if (action.id && !/^[a-z_]+$/.test(action.id)) {
|
||||||
errors.push(`Action ${index}: id must be snake_case (lowercase with underscores)`);
|
errors.push(
|
||||||
|
`Action ${index}: id must be snake_case (lowercase with underscores)`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action category validation
|
// Action category validation
|
||||||
const validActionCategories = ['movement', 'interaction', 'sensors', 'logic'];
|
const validActionCategories = ["movement", "interaction", "sensors", "logic"];
|
||||||
if (action.category && !validActionCategories.includes(action.category)) {
|
if (action.category && !validActionCategories.includes(action.category)) {
|
||||||
errors.push(`Action ${index}: invalid category '${action.category}'. Valid: ${validActionCategories.join(', ')}`);
|
errors.push(
|
||||||
|
`Action ${index}: invalid category '${action.category}'. Valid: ${validActionCategories.join(", ")}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameter schema validation
|
// Parameter schema validation
|
||||||
if (action.parameterSchema) {
|
if (action.parameterSchema) {
|
||||||
if (action.parameterSchema.type !== 'object') {
|
if (action.parameterSchema.type !== "object") {
|
||||||
errors.push(`Action ${index}: parameterSchema.type must be 'object'`);
|
errors.push(`Action ${index}: parameterSchema.type must be 'object'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +204,9 @@ function validateAction(action, index) {
|
|||||||
const hasRestApi = action.restApi;
|
const hasRestApi = action.restApi;
|
||||||
|
|
||||||
if (!hasRos2 && !hasNaoqi && !hasRestApi) {
|
if (!hasRos2 && !hasNaoqi && !hasRestApi) {
|
||||||
errors.push(`Action ${index}: must have at least one communication protocol (ros2, naoqi, or restApi)`);
|
errors.push(
|
||||||
|
`Action ${index}: must have at least one communication protocol (ros2, naoqi, or restApi)`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
@@ -195,15 +214,15 @@ function validateAction(action, index) {
|
|||||||
|
|
||||||
// Repository validation
|
// Repository validation
|
||||||
function validateRepository() {
|
function validateRepository() {
|
||||||
const repoPath = path.resolve('repository.json');
|
const repoPath = path.resolve("repository.json");
|
||||||
|
|
||||||
if (!fs.existsSync(repoPath)) {
|
if (!fs.existsSync(repoPath)) {
|
||||||
throw new Error('repository.json not found');
|
throw new Error("repository.json not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let repo;
|
let repo;
|
||||||
try {
|
try {
|
||||||
repo = JSON.parse(fs.readFileSync(repoPath, 'utf8'));
|
repo = JSON.parse(fs.readFileSync(repoPath, "utf8"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Invalid repository.json: ${e.message}`);
|
throw new Error(`Invalid repository.json: ${e.message}`);
|
||||||
}
|
}
|
||||||
@@ -212,22 +231,30 @@ function validateRepository() {
|
|||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
|
||||||
// Required repository fields
|
// Required repository fields
|
||||||
const requiredFields = ['id', 'name', 'apiVersion', 'pluginApiVersion', 'trust'];
|
const requiredFields = [
|
||||||
requiredFields.forEach(field => {
|
"id",
|
||||||
|
"name",
|
||||||
|
"apiVersion",
|
||||||
|
"pluginApiVersion",
|
||||||
|
"trust",
|
||||||
|
];
|
||||||
|
requiredFields.forEach((field) => {
|
||||||
if (!repo[field]) {
|
if (!repo[field]) {
|
||||||
errors.push(`Missing required repository field: ${field}`);
|
errors.push(`Missing required repository field: ${field}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate plugin count
|
// Validate plugin count
|
||||||
const indexPath = path.resolve('plugins/index.json');
|
const indexPath = path.resolve("plugins/index.json");
|
||||||
if (fs.existsSync(indexPath)) {
|
if (fs.existsSync(indexPath)) {
|
||||||
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
||||||
const actualCount = index.length;
|
const actualCount = index.length;
|
||||||
const reportedCount = repo.stats?.plugins || 0;
|
const reportedCount = repo.stats?.plugins || 0;
|
||||||
|
|
||||||
if (actualCount !== reportedCount) {
|
if (actualCount !== reportedCount) {
|
||||||
errors.push(`Plugin count mismatch: reported ${reportedCount}, actual ${actualCount}`);
|
errors.push(
|
||||||
|
`Plugin count mismatch: reported ${reportedCount}, actual ${actualCount}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,24 +263,25 @@ function validateRepository() {
|
|||||||
|
|
||||||
// Update plugin index
|
// Update plugin index
|
||||||
function updateIndex() {
|
function updateIndex() {
|
||||||
const pluginsDir = path.resolve('plugins');
|
const pluginsDir = path.resolve("plugins");
|
||||||
const indexPath = path.join(pluginsDir, 'index.json');
|
const indexPath = path.join(pluginsDir, "index.json");
|
||||||
|
|
||||||
if (!fs.existsSync(pluginsDir)) {
|
if (!fs.existsSync(pluginsDir)) {
|
||||||
throw new Error('plugins directory not found');
|
throw new Error("plugins directory not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginFiles = fs.readdirSync(pluginsDir)
|
const pluginFiles = fs
|
||||||
.filter(file => file.endsWith('.json') && file !== 'index.json')
|
.readdirSync(pluginsDir)
|
||||||
|
.filter((file) => file.endsWith(".json") && file !== "index.json")
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
fs.writeFileSync(indexPath, JSON.stringify(pluginFiles, null, 2));
|
fs.writeFileSync(indexPath, JSON.stringify(pluginFiles, null, 2));
|
||||||
success(`Updated index.json with ${pluginFiles.length} plugins`);
|
success(`Updated index.json with ${pluginFiles.length} plugins`);
|
||||||
|
|
||||||
// Update repository stats
|
// Update repository stats
|
||||||
const repoPath = path.resolve('repository.json');
|
const repoPath = path.resolve("repository.json");
|
||||||
if (fs.existsSync(repoPath)) {
|
if (fs.existsSync(repoPath)) {
|
||||||
const repo = JSON.parse(fs.readFileSync(repoPath, 'utf8'));
|
const repo = JSON.parse(fs.readFileSync(repoPath, "utf8"));
|
||||||
repo.stats = repo.stats || {};
|
repo.stats = repo.stats || {};
|
||||||
repo.stats.plugins = pluginFiles.length;
|
repo.stats.plugins = pluginFiles.length;
|
||||||
fs.writeFileSync(repoPath, JSON.stringify(repo, null, 2));
|
fs.writeFileSync(repoPath, JSON.stringify(repo, null, 2));
|
||||||
@@ -268,10 +296,10 @@ function main() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'validate':
|
case "validate":
|
||||||
const pluginPath = args[1];
|
const pluginPath = args[1];
|
||||||
if (!pluginPath) {
|
if (!pluginPath) {
|
||||||
error('Usage: validate <plugin-file>');
|
error("Usage: validate <plugin-file>");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,53 +307,53 @@ function main() {
|
|||||||
const { errors, warnings } = validatePlugin(pluginPath);
|
const { errors, warnings } = validatePlugin(pluginPath);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
error('Validation failed:');
|
error("Validation failed:");
|
||||||
errors.forEach(err => console.log(` - ${err}`));
|
errors.forEach((err) => console.log(` - ${err}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warnings.length > 0) {
|
if (warnings.length > 0) {
|
||||||
warn('Warnings:');
|
warn("Warnings:");
|
||||||
warnings.forEach(warn => console.log(` - ${warn}`));
|
warnings.forEach((warn) => console.log(` - ${warn}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
success('Plugin validation passed!');
|
success("Plugin validation passed!");
|
||||||
if (warnings.length === 0) {
|
if (warnings.length === 0) {
|
||||||
success('No warnings found');
|
success("No warnings found");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'validate-all':
|
case "validate-all":
|
||||||
info('Validating all plugins...');
|
info("Validating all plugins...");
|
||||||
|
|
||||||
// Validate repository
|
// Validate repository
|
||||||
const repoResult = validateRepository();
|
const repoResult = validateRepository();
|
||||||
if (repoResult.errors.length > 0) {
|
if (repoResult.errors.length > 0) {
|
||||||
error('Repository validation failed:');
|
error("Repository validation failed:");
|
||||||
repoResult.errors.forEach(err => console.log(` - ${err}`));
|
repoResult.errors.forEach((err) => console.log(` - ${err}`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all plugins
|
// Validate all plugins
|
||||||
const indexPath = path.resolve('plugins/index.json');
|
const indexPath = path.resolve("plugins/index.json");
|
||||||
if (!fs.existsSync(indexPath)) {
|
if (!fs.existsSync(indexPath)) {
|
||||||
error('plugins/index.json not found');
|
error("plugins/index.json not found");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
||||||
let allValid = true;
|
let allValid = true;
|
||||||
|
|
||||||
for (const pluginFile of index) {
|
for (const pluginFile of index) {
|
||||||
const pluginPath = path.resolve('plugins', pluginFile);
|
const pluginPath = path.resolve("plugins", pluginFile);
|
||||||
try {
|
try {
|
||||||
const { errors } = validatePlugin(pluginPath);
|
const { errors } = validatePlugin(pluginPath);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
error(`${pluginFile}: ${errors.length} errors`);
|
error(`${pluginFile}: ${errors.length} errors`);
|
||||||
errors.forEach(err => console.log(` - ${err}`));
|
errors.forEach((err) => console.log(` - ${err}`));
|
||||||
allValid = false;
|
allValid = false;
|
||||||
} else {
|
} else {
|
||||||
success(`${pluginFile}: valid`);
|
success(`${pluginFile}: valid`);
|
||||||
@@ -337,18 +365,18 @@ function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (allValid) {
|
if (allValid) {
|
||||||
success('All plugins are valid!');
|
success("All plugins are valid!");
|
||||||
} else {
|
} else {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'update-index':
|
case "update-index":
|
||||||
info('Updating plugin index...');
|
info("Updating plugin index...");
|
||||||
updateIndex();
|
updateIndex();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'help':
|
case "help":
|
||||||
default:
|
default:
|
||||||
console.log(`
|
console.log(`
|
||||||
HRIStudio Plugin Validator
|
HRIStudio Plugin Validator
|
||||||
@@ -379,5 +407,5 @@ if (require.main === module) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
validatePlugin,
|
validatePlugin,
|
||||||
validateRepository,
|
validateRepository,
|
||||||
updateIndex
|
updateIndex,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user