Skip to content

Commit 8feae89

Browse files
authored
Merge pull request #153 from stacklok/feat/phase5a-plugin-type
feat(registry/types): add Plugin catalog type (Phase 5a, THV-0077)
2 parents 536dc85 + 8c71652 commit 8feae89

10 files changed

Lines changed: 878 additions & 22 deletions

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ task license-fix # Add missing license headers
8282
| `recovery` | HTTP panic recovery middleware (Beta) |
8383
| `validation/http` | RFC 7230/8707 compliant HTTP header and URI validation |
8484
| `validation/group` | Group name validation (lowercase alphanumeric, underscore, dash, space) |
85+
| `registry/types` | Skill/Server/Plugin catalog types + JSON-schema validation (Alpha) |
8586

8687
### Mock Generation
8788

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/plugin.schema.json",
4+
"title": "ToolHive Plugin Schema",
5+
"description": "Schema for a ToolHive plugin entry in the registry",
6+
"type": "object",
7+
"required": [
8+
"namespace",
9+
"name",
10+
"description",
11+
"version"
12+
],
13+
"properties": {
14+
"namespace": {
15+
"type": "string",
16+
"description": "Ownership scope using reverse-DNS pattern (e.g. io.github.stacklok)",
17+
"minLength": 3,
18+
"maxLength": 128
19+
},
20+
"name": {
21+
"type": "string",
22+
"description": "Plugin identifier in lowercase alphanumeric with hyphens",
23+
"minLength": 1,
24+
"maxLength": 64,
25+
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$"
26+
},
27+
"description": {
28+
"type": "string",
29+
"description": "Human-readable explanation of the plugin",
30+
"minLength": 1,
31+
"maxLength": 1024
32+
},
33+
"version": {
34+
"type": "string",
35+
"description": "Version identifier (semantic version or commit hash)",
36+
"minLength": 1
37+
},
38+
"status": {
39+
"type": "string",
40+
"description": "Current status of the plugin",
41+
"enum": [
42+
"active",
43+
"deprecated",
44+
"archived"
45+
],
46+
"default": "active"
47+
},
48+
"title": {
49+
"type": "string",
50+
"description": "Human-readable display title",
51+
"maxLength": 100
52+
},
53+
"license": {
54+
"type": "string",
55+
"description": "SPDX license identifier",
56+
"maxLength": 256
57+
},
58+
"repository": {
59+
"$ref": "#/$defs/plugin_repository"
60+
},
61+
"icons": {
62+
"type": "array",
63+
"description": "Display icons for the plugin",
64+
"items": {
65+
"$ref": "#/$defs/plugin_icon"
66+
}
67+
},
68+
"packages": {
69+
"type": "array",
70+
"description": "Distribution packages (OCI or Git)",
71+
"items": {
72+
"$ref": "#/$defs/plugin_package"
73+
}
74+
},
75+
"metadata": {
76+
"type": "object",
77+
"description": "Official metadata from the plugin manifest file",
78+
"additionalProperties": true
79+
},
80+
"_meta": {
81+
"type": "object",
82+
"description": "Extended metadata with reverse-DNS namespaced keys",
83+
"additionalProperties": true
84+
}
85+
},
86+
"$defs": {
87+
"plugin_package": {
88+
"type": "object",
89+
"description": "Distribution package reference (OCI or Git)",
90+
"required": [
91+
"registryType"
92+
],
93+
"properties": {
94+
"registryType": {
95+
"type": "string",
96+
"description": "Package registry type",
97+
"enum": [
98+
"oci",
99+
"git"
100+
]
101+
},
102+
"identifier": {
103+
"type": "string",
104+
"description": "OCI image reference"
105+
},
106+
"digest": {
107+
"type": "string",
108+
"description": "Content digest (sha256 format)"
109+
},
110+
"mediaType": {
111+
"type": "string",
112+
"description": "Media type of the package"
113+
},
114+
"url": {
115+
"type": "string",
116+
"description": "Git repository URL",
117+
"format": "uri"
118+
},
119+
"ref": {
120+
"type": "string",
121+
"description": "Git branch or tag reference"
122+
},
123+
"commit": {
124+
"type": "string",
125+
"description": "Git commit SHA"
126+
},
127+
"subfolder": {
128+
"type": "string",
129+
"description": "Path within the repository"
130+
}
131+
}
132+
},
133+
"plugin_icon": {
134+
"type": "object",
135+
"description": "Display icon for the plugin",
136+
"required": [
137+
"src"
138+
],
139+
"properties": {
140+
"src": {
141+
"type": "string",
142+
"description": "Icon source URL or path"
143+
},
144+
"size": {
145+
"type": "string",
146+
"description": "Icon dimensions"
147+
},
148+
"type": {
149+
"type": "string",
150+
"description": "Icon media type"
151+
},
152+
"label": {
153+
"type": "string",
154+
"description": "Accessibility label for the icon"
155+
}
156+
}
157+
},
158+
"plugin_repository": {
159+
"type": "object",
160+
"description": "Source repository metadata",
161+
"properties": {
162+
"url": {
163+
"type": "string",
164+
"description": "Repository URL",
165+
"format": "uri"
166+
},
167+
"type": {
168+
"type": "string",
169+
"description": "Repository type (e.g. git)"
170+
}
171+
}
172+
}
173+
}
174+
}

registry/types/data/upstream-registry.schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
"items": {
5555
"$ref": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/skill.schema.json"
5656
}
57+
},
58+
"plugins": {
59+
"type": "array",
60+
"description": "Array of plugins in the registry",
61+
"items": {
62+
"$ref": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/plugin.schema.json"
63+
}
5764
}
5865
}
5966
}

registry/types/plugins_types.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package registry
5+
6+
// Plugin is a single plugin in list and get responses or publish requests.
7+
type Plugin struct {
8+
// Namespace is the namespace of the plugin.
9+
// The format is reverse-DNS, e.g. "io.github.user".
10+
Namespace string `json:"namespace"`
11+
// Name is the name of the plugin.
12+
// The format is that of identifiers, e.g. "my-plugin".
13+
Name string `json:"name"`
14+
// Description is the description of the plugin.
15+
Description string `json:"description"`
16+
// Version is the version of the plugin.
17+
// Any non-empty string is valid, but ideally it should be either a
18+
// semantic version or a commit hash.
19+
Version string `json:"version"`
20+
// Status is the status of the plugin.
21+
// Can be one of "active", "deprecated", or "archived".
22+
Status string `json:"status,omitempty"` // active, deprecated, archived
23+
// Title is the title of the plugin.
24+
// This is for human consumption, not an identifier.
25+
Title string `json:"title,omitempty"`
26+
// License is the SPDX license identifier of the plugin.
27+
License string `json:"license,omitempty"`
28+
// Repository is the source repository of the plugin.
29+
Repository *SkillRepository `json:"repository,omitempty"`
30+
// Icons is the list of icons for the plugin.
31+
Icons []SkillIcon `json:"icons,omitempty"`
32+
// Packages is the list of packages for the plugin.
33+
Packages []SkillPackage `json:"packages,omitempty"`
34+
// Metadata is the official metadata of the plugin as reported in the
35+
// plugin manifest file.
36+
Metadata map[string]any `json:"metadata,omitempty"`
37+
// Meta is an opaque payload with extended meta data details of the plugin.
38+
Meta map[string]any `json:"_meta,omitempty"`
39+
}

0 commit comments

Comments
 (0)