PluginProbe
ZIP AI – AI Website Builder & AI Agent (Beta) / 0.0.6
ZIP AI – AI Website Builder & AI Agent (Beta) v0.0.6
0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / classes / cli / cli-commands.php

cli-commands.php in ZIP AI – AI Website Builder & AI Agent (Beta) 0.0.6, at classes/cli/cli-commands.php

171 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WP-CLI Commands for ZipWP MCP.
4 *
5 * @package zip-ai
6 * @since 1.0.0
7 */
8
9 namespace ZipAI\MCP\Classes\Cli;
10
11 // Exit if accessed directly.
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 use ZipAI\MCP\Classes\Core\Utils;
17 use ZipAI\MCP\Classes\Core\Helper;
18 use WP_CLI;
19
20 /**
21 * ZipWP MCP CLI Commands.
22 *
23 * @since 1.0.0
24 */
25 class CLI_Commands {
26
27 /**
28 * Setup the authentication token for ZipWP MCP.
29 *
30 * Encrypts the token using local Sodium key and saves to zip_mcp_settings.
31 *
32 * ## OPTIONS
33 *
34 * <token>
35 * : The plain text auth token to encrypt and store.
36 *
37 * [--email=<email>]
38 * : The user email to associate with the settings.
39 *
40 * ## EXAMPLES
41 *
42 * # Binds the WP Application Password to the token on the SaaS too, so MCP
43 * # tools work. App Passwords are SSL-gated + per-user — pass --url=https
44 * # and a user context (--user, or --email which is resolved to a user).
45 * wp zip-ai setup-token "your-auth-token" --user=admin --url="https://your-site"
46 * wp zip-ai setup-token "your-auth-token" --email="user@example.com" --url="https://your-site"
47 *
48 * @subcommand setup-token
49 * @when after_wp_load
50 *
51 * @param array $args Positional arguments.
52 * @param array $assoc_args Associative arguments.
53 * @return void
54 */
55 public function setup_token( $args, $assoc_args ) {
56 $token = $args[0] ?? '';
57 $email = $assoc_args['email'] ?? '';
58
59 if ( empty( $token ) ) {
60 WP_CLI::error( 'Token is required.' );
61 return;
62 }
63
64 // Encrypt the token using local Sodium key.
65 $encrypted_token = Utils::encrypt( $token );
66
67 if ( empty( $encrypted_token ) ) {
68 WP_CLI::error( 'Failed to encrypt token. Sodium may not be available.' );
69 return;
70 }
71
72 // Get existing settings or create new.
73 $mcp_settings = get_option( 'zip_mcp_settings', array() );
74
75 // Update with encrypted token.
76 $mcp_settings['auth_token'] = $encrypted_token;
77 $mcp_settings['auth_token_server'] = defined( 'ZIPAI_MCP_CREDIT_SERVER_API' ) ? untrailingslashit( ZIPAI_MCP_CREDIT_SERVER_API ) : '';
78 $mcp_settings['enabled'] = true;
79
80 if ( ! empty( $email ) ) {
81 $mcp_settings['user_email'] = $email;
82 }
83
84 // Save settings.
85 $result = update_option( 'zip_mcp_settings', $mcp_settings );
86 $current = get_option( 'zip_mcp_settings', array() );
87 $stored = $result || ( isset( $current['auth_token'] ) && $current['auth_token'] === $encrypted_token );
88
89 if ( ! $stored ) {
90 WP_CLI::error( 'Failed to save auth token.' );
91 return;
92 }
93
94 WP_CLI::success( $result ? 'Auth token has been encrypted and saved.' : 'Auth token is already set (unchanged).' );
95
96 // Bind the WP Application Password to THIS token on the SaaS. Without it
97 // the token is MCP-blind: SaaS->WP tool discovery 401s and zero MCP
98 // tools are packed into each turn. The OAuth "Connect" flow already
99 // provisions on connect; the CLI/dev path historically skipped this,
100 // which is how hand-set/dev tokens ended up unbound.
101 $this->provision_app_password( $email );
102 }
103
104 /**
105 * Provision the WP Application Password and bind it to the just-stored
106 * Sanctum token on the SaaS, reporting an HONEST landed/not-landed result
107 * (the underlying push is soft-fail, so re-push to read its status).
108 *
109 * @since 1.0.0
110 *
111 * @param string $email Optional user email to resolve a user context under WP-CLI.
112 * @return void
113 */
114 private function provision_app_password( $email ) {
115 // Application Passwords are per-user, so provisioning needs a user
116 // context. Under WP-CLI that comes from --user; fall back to --email.
117 if ( get_current_user_id() <= 0 ) {
118 $user = '' !== $email ? get_user_by( 'email', $email ) : null;
119 if ( $user instanceof \WP_User ) {
120 wp_set_current_user( $user->ID );
121 } else {
122 WP_CLI::warning( 'Token stored, but no user context — App Password NOT bound. Re-run with --user=<login> --url="https://your-site" (App Passwords are SSL-gated), or connect via the admin UI.' );
123 return;
124 }
125 }
126
127 $provision = Helper::ensure_app_password_provisioned();
128 if ( empty( $provision['success'] ) ) {
129 WP_CLI::warning( sprintf( 'Token stored, but App Password not provisioned (%s): %s', $provision['code'] ?? 'unknown', $provision['message'] ?? '' ) );
130 if ( 'app_passwords_disabled' === ( $provision['code'] ?? '' ) ) {
131 WP_CLI::warning( 'App Passwords are SSL-gated under WP-CLI — re-run with --url="https://your-site".' );
132 }
133 return;
134 }
135
136 // ensure_app_password_provisioned() pushes the bind soft-fail (it
137 // swallows the HTTP result). Re-push explicitly — the SaaS endpoint is
138 // idempotent — so we can report whether the bind actually landed.
139 $header = Helper::get_decrypted_app_password_authorization();
140 if ( '' !== $header && Helper::push_app_password_to_saas( $header ) ) {
141 WP_CLI::success( 'WP Application Password bound to this token on the SaaS — MCP tools are available.' );
142 } else {
143 WP_CLI::warning( 'App Password provisioned locally but the SaaS bind did NOT land — MCP tools will 401 until it succeeds. Check connectivity to the credit server.' );
144 }
145 }
146
147 /**
148 * Revoke/remove the authentication token.
149 *
150 * ## EXAMPLES
151 *
152 * wp zip-ai revoke-token
153 *
154 * @subcommand revoke-token
155 * @when after_wp_load
156 *
157 * @return void
158 */
159 public function revoke_token() {
160 $mcp_settings = get_option( 'zip_mcp_settings', array() );
161
162 unset( $mcp_settings['auth_token'] );
163 $mcp_settings['enabled'] = false;
164
165 update_option( 'zip_mcp_settings', $mcp_settings );
166
167 WP_CLI::success( 'Auth token has been revoked.' );
168 }
169
170 }
171