Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ app/cache/*
*.cache
*.tmp
.phpactor.json

# generated by php sdf/cli agent install
.agents/
73 changes: 73 additions & 0 deletions sdf/cli
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ class CLI
case "swagger":
$this->handleSwagger();
break;
case "agent":
$this->handleAgent();
break;
default:
$this->printUsage();
break;
Expand Down Expand Up @@ -1448,6 +1451,74 @@ class CLI
return !empty(shell_exec("lsof -i:$port"));
}

/** Install agent skill to local or global agents directory. */
private function handleAgent(): void
{
$sub = $this->argv[2] ?? null;

if ($sub !== 'install') {
echo "Usage: php sdf/cli agent install [local|global]\n";
echo " install local (default) Copy skill to .agents/skills/<name>/SKILL.md\n";
echo " install global Copy skill to ~/.agents/skills/<name>/SKILL.md\n";
exit(1);
}

$scope = $this->argv[3] ?? 'local';

if ($scope !== 'local' && $scope !== 'global') {
echo "Usage: php sdf/cli agent install [local|global]\n";
echo " install local (default) Copy skill to .agents/skills/<name>/SKILL.md\n";
echo " install global Copy skill to ~/.agents/skills/<name>/SKILL.md\n";
exit(1);
}

// Determine source (project's wiki/agentic_workflow/skills/)
$projectRoot = dirname(__DIR__);
$skillDir = ($scope === 'global')
? getenv('HOME') . '/.agents/skills'
: getcwd() . '/.agents/skills';

// Find all skill sources in the project wiki
$sourceDir = $projectRoot . '/wiki/agentic_workflow/skills';
if (!is_dir($sourceDir)) {
echo "No skills found at $sourceDir. Create a .agents/skills/<name>/SKILL.md first.\n";
exit(1);
}

$installed = 0;
$items = scandir($sourceDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$skillPath = $sourceDir . '/' . $item . '/SKILL.md';
if (!is_file($skillPath)) continue;

$destDir = $skillDir . '/' . $item;
$destFile = $destDir . '/SKILL.md';

// Skip if copying to the same path
if (realpath($skillPath) === realpath($destFile)) {
echo "Skill '$item' already in place at $destFile\n";
$installed++;
continue;
}

@mkdir($destDir, 0755, true);
if (copy($skillPath, $destFile)) {
echo "Installed skill '$item' to $destFile\n";
$installed++;
} else {
echo "Failed to install skill '$item' to $destFile\n";
}
}

if ($installed === 0) {
echo "No SKILL.md files found under $sourceDir\n";
exit(1);
}

echo "\nDone. $installed skill(s) installed.\n";
}

/** Print banner. */
private function showBanner(): void
{
Expand Down Expand Up @@ -1494,6 +1565,8 @@ class CLI
echo "\n";
echo " swagger [action] OpenAPI / Swagger documentation\n";
echo " generate [output-path] Generate openapi.json from controllers\n";
echo "\n";
echo " agent install [local|global] Install agent skill (default: local)\n";
}
}

Expand Down
32 changes: 32 additions & 0 deletions tests/CLIGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,38 @@ protected function tearDown(): void
if (is_dir($this->tmpDir)) rmdir($this->tmpDir);
}

public function test_agent_install_local(): void
{
$script = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(getcwd() . '/sdf/cli');
$cmd = "cd " . escapeshellarg($this->tmpDir) . " && $script agent install";
exec($cmd, $out, $exit);
$this->assertSame(0, $exit, "CLI exited non-zero: " . implode("\n", $out));
$this->assertFileExists($this->tmpDir . '/.agents/skills/project-sdf/SKILL.md');
$content = file_get_contents($this->tmpDir . '/.agents/skills/project-sdf/SKILL.md');
$this->assertStringContainsString('name: project-sdf', $content);
}

public function test_agent_install_global(): void
{
$script = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(getcwd() . '/sdf/cli');
$globalDir = sys_get_temp_dir() . '/sdf_agent_global_' . uniqid();
putenv('HOME=' . $globalDir);
$cmd = "$script agent install global";
exec($cmd, $out, $exit);
$this->assertSame(0, $exit, "CLI exited non-zero: " . implode("\n", $out));
$this->assertFileExists($globalDir . '/.agents/skills/project-sdf/SKILL.md');
putenv('HOME');
}

public function test_agent_install_requires_subcommand(): void
{
$script = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(getcwd() . '/sdf/cli');
$cmd = "cd " . escapeshellarg($this->tmpDir) . " && $script agent";
exec($cmd, $out, $exit);
$this->assertNotSame(0, $exit);
$this->assertStringContainsString('Usage', implode('', $out));
}

public function test_generate_unit_test_file(): void
{
$script = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(getcwd() . '/sdf/cli');
Expand Down
208 changes: 0 additions & 208 deletions wiki/agentic_workflow/skills/project-sdf.md

This file was deleted.

Loading
Loading