diff --git a/templates/plugin/commands/deploy-function.md.twig b/templates/plugin/commands/deploy-function.md.twig index 2b7912ebf2..9319093cdd 100644 --- a/templates/plugin/commands/deploy-function.md.twig +++ b/templates/plugin/commands/deploy-function.md.twig @@ -1,5 +1,5 @@ --- -name: deploy-function +name: {{ language.name == 'CodexPlugin' ? 'deploy-function' | skillName(spec) : 'deploy-function' }} description: Deploy a function using the {{ spec.info.title }} CLI {% if language.name == 'ClaudePlugin' %} disable-model-invocation: true diff --git a/templates/plugin/commands/deploy-site.md.twig b/templates/plugin/commands/deploy-site.md.twig index 91403720ea..19e251c112 100644 --- a/templates/plugin/commands/deploy-site.md.twig +++ b/templates/plugin/commands/deploy-site.md.twig @@ -1,5 +1,5 @@ --- -name: deploy-site +name: {{ language.name == 'CodexPlugin' ? 'deploy-site' | skillName(spec) : 'deploy-site' }} description: Deploy a site using the {{ spec.info.title }} CLI {% if language.name == 'ClaudePlugin' %} disable-model-invocation: true diff --git a/tests/generation/GenerationTest.php b/tests/generation/GenerationTest.php index be0daad2c1..f1f7ce5882 100644 --- a/tests/generation/GenerationTest.php +++ b/tests/generation/GenerationTest.php @@ -9,7 +9,10 @@ use Appwrite\SDK\Language; use Appwrite\SDK\Language\Android; use Appwrite\SDK\Language\Apple; +use Appwrite\SDK\Language\ClaudePlugin; use Appwrite\SDK\Language\CLI; +use Appwrite\SDK\Language\CodexPlugin; +use Appwrite\SDK\Language\CursorPlugin; use Appwrite\SDK\Language\Dart; use Appwrite\SDK\Language\DotNet; use Appwrite\SDK\Language\Flutter; @@ -24,6 +27,7 @@ use Appwrite\SDK\Language\REST; use Appwrite\SDK\Language\Ruby; use Appwrite\SDK\Language\Rust; +use Appwrite\SDK\Language\Skills; use Appwrite\SDK\Language\Swift; use Appwrite\SDK\Language\Unity; use Appwrite\SDK\Language\Web; @@ -108,6 +112,19 @@ final class GenerationTest extends TestCase 'rust' => ['src/enums/webhook_event.rs', 'pub const UserCreated', 'src/enums/localized_status.rs', 'Value1,', 'src/enums/province_type.rs', 'Capital,'], ]; + /** + * Targets that ship Agent Skills, whose `SKILL.md` frontmatter name must + * match the directory holding it. + * + * @var array> + */ + private const array SKILL_LANGUAGES = [ + 'skills' => Skills::class, + 'claude-plugin' => ClaudePlugin::class, + 'cursor-plugin' => CursorPlugin::class, + 'codex-plugin' => CodexPlugin::class, + ]; + /** * Package metadata each language must declare, by generated path: the * runtime floor its code and dependencies need, and every interpreter @@ -180,7 +197,7 @@ private static function languageClasses(): array private function language(string $name): Language { - $language = new (self::languageClasses()[$name])(); + $language = new ((self::languageClasses() + self::SKILL_LANGUAGES)[$name])(); if ($language instanceof CLI) { $language->setExecutableName('appwrite'); } @@ -1254,6 +1271,26 @@ public function testSwiftModelDefaultsAreLiterals(): void $this->assertStringContainsString('self.zzsettings = try container.contains(.zzsettings) ? container.decode([String: AnyCodable].self, forKey: .zzsettings) : ([String: Any]()).mapValues { AnyCodable($0) }', $model); } + /** @return iterable */ + public static function skillLanguages(): iterable + { + foreach (\array_keys(self::SKILL_LANGUAGES) as $name) { + yield $name => [$name]; + } + } + + #[DataProvider('skillLanguages')] + public function testSkillNamesMatchTheirDirectories(string $name): void + { + $skills = \array_filter($this->generate($name, 'console'), static fn(string $path): bool => \basename($path) === 'SKILL.md', ARRAY_FILTER_USE_KEY); + $this->assertNotEmpty($skills); + + foreach ($skills as $path => $contents) { + $this->assertSame(1, \preg_match('/\\A---\\nname: (\\S+)\\n/', $contents, $match), "{$name}: {$path} has no frontmatter name"); + $this->assertSame(\basename(\dirname($path)), $match[1], "{$name}: {$path}"); + } + } + public function testGoModelCommentsAreNotHtmlEscaped(): void { $models = \array_filter($this->generate('go', 'server'), static fn(string $path): bool => \str_starts_with($path, 'models/') && \str_ends_with($path, '.go'), ARRAY_FILTER_USE_KEY);