*/
protected array $createdOperations = [];
/**
* Execute the console command.
*/
public function handle(): int
{
$name = Str::studly($this->argument('name'));
$category = Str::studly($this->option('category'));
if (! in_array($category, self::CATEGORIES)) {
$this->newLine();
$this->components->error("Invalid category [{$category}].");
$this->newLine();
$this->components->bulletList(self::CATEGORIES);
$this->newLine();
return self::FAILURE;
}
$providerPath = $this->getProviderPath($category, $name);
if (File::isDirectory($providerPath) && ! $this->option('force')) {
$this->newLine();
$this->components->error("Provider [{$name}] already exists in [{$category}]!");
$this->newLine();
$this->components->warn('Use --force to overwrite the existing provider.');
$this->newLine();
return self::FAILURE;
}
$this->newLine();
$this->components->info("Creating Plug provider: {$category}/{$name}");
$this->newLine();
// Create directory structure
File::ensureDirectoryExists($providerPath);
$this->components->task('Creating provider directory', fn () => true);
// Create operations based on flags
$this->createOperations($providerPath, $category, $name);
// Show summary table of created operations
$this->newLine();
$this->components->twoColumnDetail('Created Operations>', 'Description>');
foreach ($this->createdOperations as $op) {
$this->components->twoColumnDetail(
"{$op['operation']}>",
"{$op['description']}>"
);
}
$this->newLine();
$this->components->info("Plug provider [{$category}/{$name}] created successfully!");
$this->newLine();
$this->components->twoColumnDetail('Location', "{$providerPath}>");
$this->newLine();
$this->components->info('Usage example:');
$this->line(" use> Plug\\{$category}\\{$name}\\Auth;");
$this->newLine();
$this->line(' $auth> = new> Auth(\$clientId>, \$clientSecret>, \$redirectUrl>);');
$this->line(' $authUrl> = \$auth>->getAuthUrl>();');
$this->newLine();
return self::SUCCESS;
}
/**
* Get the path for the provider.
*/
protected function getProviderPath(string $category, string $name): string
{
// Check for packages structure first (monorepo)
$packagesPath = base_path("packages/core-php/src/Plug/{$category}/{$name}");
if (File::isDirectory(dirname(dirname($packagesPath)))) {
return $packagesPath;
}
// Fall back to app/Plug for consuming applications
return base_path("app/Plug/{$category}/{$name}");
}
/**
* Resolve the namespace for the provider.
*/
protected function resolveNamespace(string $providerPath, string $category, string $name): string
{
if (str_contains($providerPath, 'packages/core-php/src/Plug')) {
return "Core\\Plug\\{$category}\\{$name}";
}
return "Plug\\{$category}\\{$name}";
}
/**
* Create operations based on flags.
*/
protected function createOperations(string $providerPath, string $category, string $name): void
{
$namespace = $this->resolveNamespace($providerPath, $category, $name);
// Always create Auth if --auth or --all or no specific options
if ($this->option('auth') || $this->option('all') || ! $this->hasAnyOperation()) {
$this->createAuthOperation($providerPath, $namespace, $name);
}
if ($this->option('post') || $this->option('all')) {
$this->createPostOperation($providerPath, $namespace, $name);
}
if ($this->option('delete') || $this->option('all')) {
$this->createDeleteOperation($providerPath, $namespace, $name);
}
if ($this->option('media') || $this->option('all')) {
$this->createMediaOperation($providerPath, $namespace, $name);
}
}
/**
* Check if any operation option was provided.
*/
protected function hasAnyOperation(): bool
{
return $this->option('auth')
|| $this->option('post')
|| $this->option('delete')
|| $this->option('media')
|| $this->option('all');
}
/**
* Create the Auth operation.
*/
protected function createAuthOperation(string $providerPath, string $namespace, string $name): void
{
$content = <<clientId = \$clientId;
\$this->clientSecret = \$clientSecret;
\$this->redirectUrl = \$redirectUrl;
}
/**
* Get the provider display name.
*/
public static function name(): string
{
return '{$name}';
}
/**
* Set OAuth scopes.
*
* @param string[] \$scopes
*/
public function withScopes(array \$scopes): static
{
\$this->scopes = \$scopes;
return \$this;
}
/**
* Get the authorization URL for user redirect.
*/
public function getAuthUrl(?string \$state = null): string
{
\$params = [
'client_id' => \$this->clientId,
'redirect_uri' => \$this->redirectUrl,
'response_type' => 'code',
'scope' => implode(' ', \$this->scopes),
];
if (\$state) {
\$params['state'] = \$state;
}
// TODO: [USER] Replace with your provider's OAuth authorization URL
// Example: return 'https://api.twitter.com/oauth/authorize?' . http_build_query(\$params);
return 'https://example.com/oauth/authorize?'.http_build_query(\$params);
}
/**
* Exchange authorization code for access token.
*/
public function exchangeCode(string \$code): Response
{
// TODO: [USER] Implement token exchange with your provider's API
// Make a POST request to the provider's token endpoint with the authorization code
return \$this->ok([
'access_token' => '',
'refresh_token' => '',
'expires_in' => 0,
]);
}
/**
* Refresh an expired access token.
*/
public function refreshToken(string \$refreshToken): Response
{
// TODO: [USER] Implement token refresh with your provider's API
// Use the refresh token to obtain a new access token
return \$this->ok([
'access_token' => '',
'refresh_token' => '',
'expires_in' => 0,
]);
}
/**
* Revoke an access token.
*/
public function revokeToken(string \$accessToken): Response
{
// TODO: [USER] Implement token revocation with your provider's API
// Call the provider's revocation endpoint to invalidate the token
return \$this->ok(['revoked' => true]);
}
/**
* Get an HTTP client instance.
*/
protected function http(): PendingRequest
{
return Http::acceptJson()
->timeout(30);
}
}
PHP;
File::put("{$providerPath}/Auth.php", $content);
$this->createdOperations[] = ['operation' => 'Auth.php', 'description' => 'OAuth 2.0 authentication'];
$this->components->task('Creating Auth.php', fn () => true);
}
/**
* Create the Post operation.
*/
protected function createPostOperation(string $providerPath, string $namespace, string $name): void
{
$content = <<http()
// ->withToken(\$this->accessToken())
// ->post('https://api.example.com/posts', [
// 'text' => \$content,
// ...\$options,
// ]);
//
// return \$this->fromResponse(\$response);
return \$this->ok([
'id' => '',
'url' => '',
'created_at' => now()->toIso8601String(),
]);
}
/**
* Schedule a post for later.
*/
public function schedule(string \$content, \DateTimeInterface \$publishAt, array \$options = []): Response
{
// TODO: [USER] Implement scheduled posting with your provider's API
return \$this->ok([
'id' => '',
'scheduled_at' => \$publishAt->format('c'),
]);
}
/**
* Get an HTTP client instance.
*/
protected function http(): PendingRequest
{
return Http::acceptJson()
->timeout(30);
}
}
PHP;
File::put("{$providerPath}/Post.php", $content);
$this->createdOperations[] = ['operation' => 'Post.php', 'description' => 'Content creation/publishing'];
$this->components->task('Creating Post.php', fn () => true);
}
/**
* Create the Delete operation.
*/
protected function createDeleteOperation(string $providerPath, string $namespace, string $name): void
{
$content = <<http()
// ->withToken(\$this->accessToken())
// ->delete("https://api.example.com/posts/{\$postId}");
//
// return \$this->fromResponse(\$response);
return \$this->ok(['deleted' => true]);
}
/**
* Get an HTTP client instance.
*/
protected function http(): PendingRequest
{
return Http::acceptJson()
->timeout(30);
}
}
PHP;
File::put("{$providerPath}/Delete.php", $content);
$this->createdOperations[] = ['operation' => 'Delete.php', 'description' => 'Content deletion'];
$this->components->task('Creating Delete.php', fn () => true);
}
/**
* Create the Media operation.
*/
protected function createMediaOperation(string $providerPath, string $namespace, string $name): void
{
$content = <<http()
// ->withToken(\$this->accessToken())
// ->attach('media', file_get_contents(\$filePath), basename(\$filePath))
// ->post('https://api.example.com/media/upload', \$options);
//
// return \$this->fromResponse(\$response);
return \$this->ok([
'media_id' => '',
'url' => '',
]);
}
/**
* Upload media from a URL.
*/
public function uploadFromUrl(string \$url, array \$options = []): Response
{
// TODO: [USER] Implement URL-based media upload with your provider's API
return \$this->ok([
'media_id' => '',
'url' => '',
]);
}
/**
* Get an HTTP client instance.
*/
protected function http(): PendingRequest
{
return Http::acceptJson()
->timeout(60); // Longer timeout for uploads
}
}
PHP;
File::put("{$providerPath}/Media.php", $content);
$this->createdOperations[] = ['operation' => 'Media.php', 'description' => 'Media file uploads'];
$this->components->task('Creating Media.php', fn () => true);
}
/**
* Get shell completion suggestions for arguments and options.
*/
public function complete(
CompletionInput $input,
CompletionSuggestions $suggestions
): void {
if ($input->mustSuggestArgumentValuesFor('name')) {
// Suggest common social platform names
$suggestions->suggestValues([
'Twitter',
'Instagram',
'Facebook',
'LinkedIn',
'TikTok',
'YouTube',
'Mastodon',
'Threads',
'Bluesky',
]);
}
if ($input->mustSuggestOptionValuesFor('category')) {
$suggestions->suggestValues(self::CATEGORIES);
}
}
}