Files
tkmind_go/ui/h5/skills-registry.mjs
john 4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search),
MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 21:30:20 +08:00

156 lines
4.7 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
ensurePublishSkillInstalled,
PUBLISH_SKILL_NAME,
renderPublishSkill,
resolvePublicBaseUrl,
} from './user-publish.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export const DEFAULT_USER_SKILLS = {
[PUBLISH_SKILL_NAME]: false,
};
export function parseSkillFrontmatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return { name: null, description: '' };
const block = match[1];
const name = block.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? null;
const description = block.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? '';
return { name, description };
}
export function listPlatformSkillCatalog(h5Root = __dirname) {
const skillsRoot = path.join(h5Root, 'skills');
if (!fs.existsSync(skillsRoot)) return [];
const catalog = [];
for (const entry of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const skillPath = path.join(skillsRoot, entry.name, 'SKILL.md');
if (!fs.existsSync(skillPath)) continue;
const raw = fs.readFileSync(skillPath, 'utf8');
const meta = parseSkillFrontmatter(raw);
const name = meta.name || entry.name;
catalog.push({
name,
dirName: entry.name,
label: name,
description: meta.description || '平台通用技能',
category: 'platform',
requiresPublish: name === PUBLISH_SKILL_NAME,
});
}
return catalog.sort((a, b) => a.name.localeCompare(b.name));
}
export function catalogSkillNames(catalog) {
return catalog.map((item) => item.name);
}
export function isValidSkillName(catalog, name) {
return catalog.some((item) => item.name === name);
}
export function normalizeSkillPatch(catalog, patch) {
const normalized = {};
for (const [key, value] of Object.entries(patch ?? {})) {
if (!isValidSkillName(catalog, key)) continue;
normalized[key] = Boolean(value);
}
return normalized;
}
export function resolveSkillMap(roleGrants, userOverrides, catalog) {
const resolved = {};
for (const item of catalog) {
const key = item.name;
if (key in userOverrides) {
resolved[key] = userOverrides[key];
} else if (key in roleGrants) {
resolved[key] = roleGrants[key];
} else {
resolved[key] = DEFAULT_USER_SKILLS[key] ?? false;
}
}
return resolved;
}
export function grantedSkillNames(skillMap) {
return Object.entries(skillMap)
.filter(([, enabled]) => enabled)
.map(([name]) => name);
}
export function applySkillGrantsToCapabilities(capabilities, skillMap) {
const effective = { ...capabilities };
const enabled = grantedSkillNames(skillMap);
if (enabled.length > 0) {
effective.skills = true;
}
if (enabled.includes(PUBLISH_SKILL_NAME)) {
effective.static_publish = true;
}
return effective;
}
function copySkillTree(srcDir, destDir) {
fs.mkdirSync(destDir, { recursive: true });
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
const from = path.join(srcDir, entry.name);
const to = path.join(destDir, entry.name);
if (entry.isDirectory()) {
copySkillTree(from, to);
} else {
fs.copyFileSync(from, to);
}
}
}
export function syncSkillsToWorkspace({
h5Root,
publishDir,
skillMap,
catalog,
user,
publicBaseUrl,
}) {
const enabled = new Set(grantedSkillNames(skillMap));
const platformNames = new Set(catalog.map((item) => item.name));
const agentsSkills = path.join(publishDir, '.agents', 'skills');
if (fs.existsSync(agentsSkills)) {
for (const entry of fs.readdirSync(agentsSkills, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const skillName = entry.name;
const catalogItem = catalog.find((item) => item.name === skillName || item.dirName === skillName);
const resolvedName = catalogItem?.name ?? skillName;
if (platformNames.has(resolvedName) && !enabled.has(resolvedName)) {
fs.rmSync(path.join(agentsSkills, entry.name), { recursive: true, force: true });
}
}
}
for (const item of catalog) {
if (!enabled.has(item.name)) continue;
const srcDir = path.join(h5Root, 'skills', item.dirName);
const destDir = path.join(agentsSkills, item.name);
if (item.name === PUBLISH_SKILL_NAME && user) {
const slug = String(user.username).trim().toLowerCase();
ensurePublishSkillInstalled(publishDir, {
slug,
publicBaseUrl,
publishDir,
});
continue;
}
if (fs.existsSync(srcDir)) {
if (fs.existsSync(destDir)) fs.rmSync(destDir, { recursive: true, force: true });
copySkillTree(srcDir, destDir);
}
}
}