@openhands/extensions 0.13.0 → 0.15.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/.release-please-manifest.json +1 -1
- package/README.md +7 -3
- package/automations/catalog/upstream-fork-sync/manifest.json +85 -0
- package/automations/catalog-index.js +3 -1
- package/marketplaces/large-codebase.json +2 -2
- package/marketplaces/openhands-extensions.json +61 -47
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/scripts/build-skills-catalog.mjs +76 -3
- package/skills/index.d.ts +21 -0
- package/skills/index.js +116 -51
- package/skills/upstream-fork-sync/.plugin/plugin.json +21 -0
- package/skills/upstream-fork-sync/README.md +38 -0
- package/skills/upstream-fork-sync/SKILL.md +122 -0
- package/skills/upstream-fork-sync/commands/upstream-fork-sync-setup.md +8 -0
- package/tests/fixtures/automations/upstream-fork-sync.json +223 -0
- package/tests/test_skills_catalog.py +214 -4
|
@@ -14,6 +14,62 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
14
14
|
const SKILLS_DIR = join(__dirname, "..", "skills");
|
|
15
15
|
const OUTPUT = join(SKILLS_DIR, "index.js");
|
|
16
16
|
|
|
17
|
+
const MARKETPLACES_DIR = join(__dirname, "..", "marketplaces");
|
|
18
|
+
const SKILL_SOURCE_PREFIX = "./skills/";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Categories for skill entries, consumed by the agent-canvas /skills facet rail.
|
|
22
|
+
*
|
|
23
|
+
* Distinct from the `category` on marketplace *plugin* entries, which serves Claude Code marketplace browsing and keeps its own values.
|
|
24
|
+
*/
|
|
25
|
+
export const SKILL_CATEGORY_IDS = [
|
|
26
|
+
"automations",
|
|
27
|
+
"environment",
|
|
28
|
+
"code-hosting",
|
|
29
|
+
"agent-authoring",
|
|
30
|
+
"code-quality",
|
|
31
|
+
"integrations",
|
|
32
|
+
"writing",
|
|
33
|
+
"design",
|
|
34
|
+
"other",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const FALLBACK_CATEGORY = "other";
|
|
38
|
+
|
|
39
|
+
/** Build a `skill directory name -> {category, file}` map from every manifest. */
|
|
40
|
+
export function buildCategoryMap(marketplacesDir) {
|
|
41
|
+
const map = new Map();
|
|
42
|
+
|
|
43
|
+
for (const filename of readdirSync(marketplacesDir).filter((f) => f.endsWith(".json")).sort()) {
|
|
44
|
+
const manifest = JSON.parse(readFileSync(join(marketplacesDir, filename), "utf-8"));
|
|
45
|
+
|
|
46
|
+
for (const entry of manifest.plugins ?? []) {
|
|
47
|
+
const source = entry.source ?? "";
|
|
48
|
+
if (!source.startsWith(SKILL_SOURCE_PREFIX)) continue;
|
|
49
|
+
|
|
50
|
+
const name = source.slice(SKILL_SOURCE_PREFIX.length);
|
|
51
|
+
const { category } = entry;
|
|
52
|
+
|
|
53
|
+
if (!SKILL_CATEGORY_IDS.includes(category)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`${filename}: skill "${name}" has category "${category}", expected one of: ${SKILL_CATEGORY_IDS.join(", ")}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const existing = map.get(name);
|
|
60
|
+
if (existing && existing.category !== category) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Conflicting categories for skill "${name}": ${existing.file} says "${existing.category}", ${filename} says "${category}"`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
map.set(name, { category, file: filename });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return map;
|
|
71
|
+
}
|
|
72
|
+
|
|
17
73
|
/** Minimal YAML frontmatter parser for the flat format used by SKILL.md. */
|
|
18
74
|
export function parseFrontmatter(raw) {
|
|
19
75
|
const result = {};
|
|
@@ -51,9 +107,15 @@ export function parseFrontmatter(raw) {
|
|
|
51
107
|
};
|
|
52
108
|
}
|
|
53
109
|
|
|
54
|
-
/**
|
|
55
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Build the catalog from SKILL.md files in the given directory.
|
|
112
|
+
*
|
|
113
|
+
* Pass an isolated `marketplacesDir` when building from fixtures; the default reads this repo's real manifests.
|
|
114
|
+
*/
|
|
115
|
+
export function buildCatalog(skillsDir, marketplacesDir = MARKETPLACES_DIR) {
|
|
56
116
|
const entries = [];
|
|
117
|
+
const categories = buildCategoryMap(marketplacesDir);
|
|
118
|
+
const uncategorized = [];
|
|
57
119
|
|
|
58
120
|
for (const dir of readdirSync(skillsDir).sort()) {
|
|
59
121
|
const dirPath = join(skillsDir, dir);
|
|
@@ -71,16 +133,26 @@ export function buildCatalog(skillsDir) {
|
|
|
71
133
|
const fm = parseFrontmatter(parts[1]);
|
|
72
134
|
const body = parts.slice(2).join("---").trim();
|
|
73
135
|
|
|
136
|
+
const mapped = categories.get(dir);
|
|
137
|
+
if (!mapped) uncategorized.push(dir);
|
|
138
|
+
|
|
74
139
|
entries.push({
|
|
75
140
|
name: fm.name?.trim() || dir,
|
|
76
141
|
description: fm.description,
|
|
77
142
|
triggers: fm.triggers,
|
|
78
143
|
content: body,
|
|
144
|
+
category: mapped?.category ?? FALLBACK_CATEGORY,
|
|
79
145
|
...(fm.license ? { license: fm.license } : {}),
|
|
80
146
|
...(fm.compatibility ? { compatibility: fm.compatibility } : {}),
|
|
81
147
|
});
|
|
82
148
|
}
|
|
83
149
|
|
|
150
|
+
if (uncategorized.length > 0) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`Warning: no marketplace entry, category defaults to "${FALLBACK_CATEGORY}": ${uncategorized.join(", ")}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
84
156
|
return entries;
|
|
85
157
|
}
|
|
86
158
|
|
|
@@ -90,7 +162,8 @@ if (isMain) {
|
|
|
90
162
|
const entries = buildCatalog(SKILLS_DIR);
|
|
91
163
|
|
|
92
164
|
const source = `// Auto-generated by scripts/build-skills-catalog.mjs — do not edit.
|
|
93
|
-
// Source of truth: skills/*/SKILL.md
|
|
165
|
+
// Source of truth: skills/*/SKILL.md and marketplaces/*.json (category)
|
|
166
|
+
export const SKILL_CATEGORY_IDS = ${JSON.stringify(SKILL_CATEGORY_IDS)};
|
|
94
167
|
export const SKILLS_CATALOG = ${JSON.stringify(entries, null, 2)};
|
|
95
168
|
export default SKILLS_CATALOG;
|
|
96
169
|
`;
|
package/skills/index.d.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Categories for skill entries, consumed by the agent-canvas /skills facet rail.
|
|
3
|
+
*
|
|
4
|
+
* Sourced from the `category` field on marketplace entries whose `source` starts with `./skills/`.
|
|
5
|
+
* Distinct from the `category` on marketplace *plugin* entries, which serves Claude Code marketplace browsing.
|
|
6
|
+
*/
|
|
7
|
+
export type SkillCategoryId =
|
|
8
|
+
| "automations"
|
|
9
|
+
| "environment"
|
|
10
|
+
| "code-hosting"
|
|
11
|
+
| "agent-authoring"
|
|
12
|
+
| "code-quality"
|
|
13
|
+
| "integrations"
|
|
14
|
+
| "writing"
|
|
15
|
+
| "design"
|
|
16
|
+
| "other";
|
|
17
|
+
|
|
18
|
+
export const SKILL_CATEGORY_IDS: readonly SkillCategoryId[];
|
|
19
|
+
|
|
1
20
|
export interface SkillCatalogEntry {
|
|
2
21
|
name: string;
|
|
3
22
|
description: string;
|
|
4
23
|
triggers: string[];
|
|
5
24
|
content: string;
|
|
25
|
+
/** `"other"` when the skill has no marketplace entry. */
|
|
26
|
+
category: SkillCategoryId;
|
|
6
27
|
license?: string;
|
|
7
28
|
compatibility?: string;
|
|
8
29
|
}
|