Files

152 lines
6.0 KiB
PHP

<?php
namespace AskTamMCP\Tools;
defined('ABSPATH') || exit;
/**
* Skill Tools — read .md skill files from the plugin's skills/ directory.
*
* Skills are markdown files that contain design, copywriting, and build instructions
* for Claude to follow when constructing landing pages, Elementor layouts, etc.
*
* Tools:
* list_skills — list all available skills with metadata
* get_skill — read a single skill file by name
* get_page_context — read all auto-load skills in one call
*
* To add a skill: drop a .md file in skills/ with YAML front matter. No code changes needed.
*/
class SkillTools {
private static string $skills_dir = '';
private static function dir(): string {
if (!self::$skills_dir) {
self::$skills_dir = plugin_dir_path(dirname(__DIR__)) . 'skills/';
}
return self::$skills_dir;
}
public static function listSkills(array $args): array {
$dir = self::dir();
$tag = sanitize_key($args['tag'] ?? '');
$files = glob($dir . '*.md') ?: [];
$skills = [];
foreach ($files as $filepath) {
$meta = self::parseFrontMatter($filepath);
if (!$meta) continue;
if ($tag) {
$tags = $meta['tags'] ?? [];
if (!in_array($tag, (array)$tags)) continue;
}
$skills[] = [
'name' => $meta['name'] ?? basename($filepath, '.md'),
'description' => $meta['description'] ?? '',
'version' => $meta['version'] ?? '1.0.0',
'auto_load' => (bool)($meta['auto-load'] ?? false),
'tags' => (array)($meta['tags'] ?? []),
'filename' => basename($filepath),
];
}
usort($skills, fn($a, $b) => $b['auto_load'] <=> $a['auto_load']);
return [
'total' => count($skills),
'skills' => $skills,
'skills_dir' => str_replace(ABSPATH, '/', $dir),
'usage' => 'Call get_skill(name) to read any skill. Call get_page_context() to load all auto-load skills at once before building a landing page.',
];
}
public static function getSkill(array $args): array {
$name = sanitize_file_name($args['name'] ?? '');
if (empty($name)) throw new \RuntimeException('name is required.');
$name = preg_replace('/\.md$/i', '', $name);
$filepath = self::dir() . $name . '.md';
$real = realpath($filepath);
$dir = realpath(self::dir());
if (!$real || !str_starts_with($real, $dir)) {
throw new \RuntimeException("Skill '{$name}' not found.");
}
$raw = file_get_contents($real);
$meta = self::parseFrontMatterFromContent($raw);
$body = self::stripFrontMatter($raw);
return [
'name' => $meta['name'] ?? $name,
'description' => $meta['description'] ?? '',
'version' => $meta['version'] ?? '1.0.0',
'auto_load' => (bool)($meta['auto-load'] ?? false),
'tags' => (array)($meta['tags'] ?? []),
'content' => $body,
'char_count' => strlen($body),
];
}
public static function getPageContext(array $args): array {
$dir = self::dir();
$files = glob($dir . '*.md') ?: [];
$loaded = [];
$skipped = [];
foreach ($files as $filepath) {
$raw = file_get_contents($filepath);
$meta = self::parseFrontMatterFromContent($raw);
if (!(bool)($meta['auto-load'] ?? false)) {
$skipped[] = $meta['name'] ?? basename($filepath, '.md');
continue;
}
$loaded[] = ['name' => $meta['name'] ?? basename($filepath, '.md'), 'content' => self::stripFrontMatter($raw)];
}
$combined = '';
foreach ($loaded as $skill) {
$combined .= "\n\n" . str_repeat('=', 60) . "\n";
$combined .= "SKILL: {$skill['name']}\n";
$combined .= str_repeat('=', 60) . "\n\n";
$combined .= $skill['content'];
}
return [
'loaded_skills' => array_column($loaded, 'name'),
'skipped_skills' => $skipped,
'total_loaded' => count($loaded),
'context' => trim($combined),
'char_count' => strlen($combined),
'usage_note' => 'This context contains your landing page build instructions. Follow them for every page you build. For motion/CSS patterns call get_skill("motion-and-css") separately.',
];
}
private static function parseFrontMatter(string $filepath): ?array {
$content = file_get_contents($filepath);
if (!$content) return null;
return self::parseFrontMatterFromContent($content);
}
private static function parseFrontMatterFromContent(string $content): array {
if (!preg_match('/^---\s*\n(.*?)\n---/s', $content, $m)) return [];
$meta = [];
$lines = explode("\n", trim($m[1]));
foreach ($lines as $line) {
if (!preg_match('/^([a-zA-Z0-9_\-]+)\s*:\s*(.*)$/', $line, $lm)) continue;
$key = trim($lm[1]);
$val = trim($lm[2]);
$val = preg_replace('/^["\'](.*)["\']$/', '$1', $val);
if (strtolower($val) === 'true') $val = true;
elseif (strtolower($val) === 'false') $val = false;
elseif (is_numeric($val) && !str_starts_with($val, '0') && !str_contains($val, '.')) $val = (int)$val;
if (is_string($val) && str_starts_with($val, '[') && str_ends_with($val, ']')) {
$val = array_map(fn($v) => trim(trim($v), '"\''), explode(',', substr($val, 1, -1)));
}
$meta[$key] = $val;
}
return $meta;
}
private static function stripFrontMatter(string $content): string {
return trim(preg_replace('/^---\s*\n.*?\n---\s*\n/s', '', $content));
}
}