Bonapara/twenty codex plugin (#20857)
@martmull v2.0 ;) --------- Co-authored-by: martmull <martmull@hotmail.fr> Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
committed by
GitHub
parent
cb744b2eeb
commit
1642be86f5
@@ -0,0 +1,155 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const {
|
||||
PLUGIN_ROOT,
|
||||
LEGACY_SKILL_NAMES,
|
||||
readText,
|
||||
listFiles,
|
||||
parseSkillFrontmatter,
|
||||
parseQuotedYamlField,
|
||||
} = require('./lib');
|
||||
|
||||
const EXPECTED_CANONICAL_SKILLS = [
|
||||
'create-app',
|
||||
'develop-app',
|
||||
'manage-app',
|
||||
'publish-app',
|
||||
'use-twenty-mcp',
|
||||
];
|
||||
|
||||
const assertSkills = (fail) => {
|
||||
const skillsRoot = path.join(PLUGIN_ROOT, 'skills');
|
||||
const skillDirectories = fs
|
||||
.readdirSync(skillsRoot, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name)
|
||||
.sort();
|
||||
|
||||
for (const skillName of EXPECTED_CANONICAL_SKILLS) {
|
||||
if (!skillDirectories.includes(skillName)) {
|
||||
fail(`canonical skill is missing: ${skillName}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const skillName of LEGACY_SKILL_NAMES) {
|
||||
if (skillDirectories.includes(skillName)) {
|
||||
fail(`legacy skill directory must be transferred out of skills/: ${skillName}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const skillName of skillDirectories) {
|
||||
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md');
|
||||
const agentsPath = path.join(skillsRoot, skillName, 'agents', 'openai.yaml');
|
||||
|
||||
if (!fs.existsSync(skillPath)) {
|
||||
fail(`${skillName} is missing SKILL.md`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const frontmatter = parseSkillFrontmatter(skillPath);
|
||||
|
||||
if (!frontmatter) {
|
||||
fail(`${skillName}/SKILL.md is missing YAML frontmatter`);
|
||||
} else {
|
||||
const frontmatterKeys = Object.keys(frontmatter).sort();
|
||||
|
||||
if (frontmatter.name !== skillName) {
|
||||
fail(`${skillName}/SKILL.md frontmatter name must match its directory`);
|
||||
}
|
||||
|
||||
if (!frontmatter.description) {
|
||||
fail(`${skillName}/SKILL.md frontmatter description is required`);
|
||||
}
|
||||
|
||||
if (frontmatterKeys.some((key) => !['description', 'name'].includes(key))) {
|
||||
fail(`${skillName}/SKILL.md frontmatter should only include name and description`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(agentsPath)) {
|
||||
fail(`${skillName} is missing agents/openai.yaml`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const agentsYaml = readText(agentsPath);
|
||||
const displayName = parseQuotedYamlField(agentsYaml, 'display_name');
|
||||
const shortDescription = parseQuotedYamlField(agentsYaml, 'short_description');
|
||||
const defaultPrompt = parseQuotedYamlField(agentsYaml, 'default_prompt');
|
||||
|
||||
if (!displayName) {
|
||||
fail(`${skillName}/agents/openai.yaml is missing interface.display_name`);
|
||||
}
|
||||
|
||||
if (!shortDescription) {
|
||||
fail(`${skillName}/agents/openai.yaml is missing interface.short_description`);
|
||||
} else if (shortDescription.length > 64) {
|
||||
fail(`${skillName}/agents/openai.yaml short_description must be 64 characters or fewer`);
|
||||
}
|
||||
|
||||
if (!defaultPrompt) {
|
||||
fail(`${skillName}/agents/openai.yaml is missing interface.default_prompt`);
|
||||
} else if (!defaultPrompt.includes(`$${skillName}`)) {
|
||||
fail(`${skillName}/agents/openai.yaml default_prompt must mention $${skillName}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const assertSkillTriggerPhrases = (fail) => {
|
||||
const skillsRoot = path.join(PLUGIN_ROOT, 'skills');
|
||||
const skillDirectories = fs
|
||||
.readdirSync(skillsRoot, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name);
|
||||
|
||||
for (const skillName of skillDirectories) {
|
||||
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md');
|
||||
|
||||
if (!fs.existsSync(skillPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const contents = readText(skillPath);
|
||||
|
||||
if (!/^#+\s+When To Use\s*$/m.test(contents)) {
|
||||
fail(`${skillName}/SKILL.md must include a "When To Use" section with representative trigger phrases`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const assertNoLegacySkillReferences = (fail) => {
|
||||
const filesToCheck = listFiles(PLUGIN_ROOT).filter((filePath) => {
|
||||
const extension = path.extname(filePath);
|
||||
|
||||
return ['.md', '.yaml', '.yml'].includes(extension);
|
||||
});
|
||||
|
||||
for (const filePath of filesToCheck) {
|
||||
const relativePath = path.relative(PLUGIN_ROOT, filePath);
|
||||
const contents = readText(filePath);
|
||||
|
||||
for (const legacySkillName of LEGACY_SKILL_NAMES) {
|
||||
if (contents.includes(`name: ${legacySkillName}`)) {
|
||||
fail(`${relativePath} must not declare legacy skill name ${legacySkillName}`);
|
||||
}
|
||||
|
||||
const mentionPattern =
|
||||
legacySkillName === 'setup-mcp'
|
||||
? /(^|[^A-Za-z0-9_-])setup-mcp(?!\.sh)(?=$|[^A-Za-z0-9_-])/
|
||||
: new RegExp(
|
||||
`(^|[^A-Za-z0-9_-])${legacySkillName}(?=$|[^A-Za-z0-9_-])`,
|
||||
);
|
||||
|
||||
if (mentionPattern.test(contents)) {
|
||||
fail(`${relativePath} must not mention legacy skill name ${legacySkillName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
EXPECTED_CANONICAL_SKILLS,
|
||||
assertSkills,
|
||||
assertSkillTriggerPhrases,
|
||||
assertNoLegacySkillReferences,
|
||||
};
|
||||
Reference in New Issue
Block a user