*/ 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); } } }