Changeset 3763701
- Timestamp:
- 10/24/2026 07:54:55 PM (12 hours ago)
- Location:
- addsynapse-store-assistant
- Files:
-
- 2 added
- 24 edited
- 1 copied
-
tags/1.3.5 (copied) (copied from addsynapse-store-assistant/trunk )
-
tags/1.3.5/addsynapse-store-assistant.php (modified) (3 diffs)
-
tags/1.3.5/assets/home.css (modified) (1 diff)
-
tags/1.3.5/assets/home.js (modified) (5 diffs)
-
tags/1.3.5/includes/Account/Api.php (modified) (3 diffs)
-
tags/1.3.5/includes/Account/PendingOnboarding.php (added)
-
tags/1.3.5/includes/Admin/HomePage.php (modified) (17 diffs)
-
tags/1.3.5/includes/Install/Lifecycle.php (modified) (1 diff)
-
tags/1.3.5/includes/Remote/RelayClaim.php (modified) (6 diffs)
-
tags/1.3.5/includes/Remote/RelayKeys.php (modified) (1 diff)
-
tags/1.3.5/includes/Remote/SetupStatus.php (modified) (1 diff)
-
tags/1.3.5/package.json (modified) (1 diff)
-
tags/1.3.5/readme.txt (modified) (5 diffs)
-
tags/1.3.5/uninstall.php (modified) (1 diff)
-
trunk/addsynapse-store-assistant.php (modified) (3 diffs)
-
trunk/assets/home.css (modified) (1 diff)
-
trunk/assets/home.js (modified) (5 diffs)
-
trunk/includes/Account/Api.php (modified) (3 diffs)
-
trunk/includes/Account/PendingOnboarding.php (added)
-
trunk/includes/Admin/HomePage.php (modified) (17 diffs)
-
trunk/includes/Install/Lifecycle.php (modified) (1 diff)
-
trunk/includes/Remote/RelayClaim.php (modified) (6 diffs)
-
trunk/includes/Remote/RelayKeys.php (modified) (1 diff)
-
trunk/includes/Remote/SetupStatus.php (modified) (1 diff)
-
trunk/package.json (modified) (1 diff)
-
trunk/readme.txt (modified) (5 diffs)
-
trunk/uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
addsynapse-store-assistant/tags/1.3.5/addsynapse-store-assistant.php
r3759648 r3763701 4 4 * Plugin URI: https://addsynapse.com/store-assistant 5 5 * Description: Admin chat where shop managers ask, in their own language, about their store and for changes to one product at a time — with your own Anthropic or OpenAI API key. Every change is a preview a person approves, with Undo. 6 * Version: 1.3. 46 * Version: 1.3.5 7 7 * Requires at least: 6.9 8 8 * Requires PHP: 7.4 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3. 4' );22 define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3.5' ); 23 23 define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_FILE', __FILE__ ); 24 24 define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); … … 55 55 56 56 register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Schema::class, 'install' ) ); 57 // 1.3.5: remember the activation for one minute (continues a connection started in Claude, HomePage::maybe_start_from_claude). 58 register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Admin\HomePage::class, 'activated' ) ); 57 59 register_deactivation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Lifecycle::class, 'deactivate' ) ); 58 60 -
addsynapse-store-assistant/tags/1.3.5/assets/home.css
r3759648 r3763701 313 313 .adsa-apppw { margin: 12px 0 16px; } 314 314 .adsa-apppw .adsa-buttons { margin-bottom: 4px; } 315 316 /* 1.3.5: step 2 = one «Connect to AddSynapse» button; step 3 collapsed. */ 317 .adsa-connect__sub { margin: -4px 0 8px; } 318 .adsa-addai { margin-top: 8px; } 319 320 /* 1.3.5: «Done — go back to Claude» (from Claude, right after «Yes, connect»). */ 321 .adsa-done { max-width: 720px; margin: 24px 0; padding: 32px; background: #fff; border: 1px solid #b8e6bf; border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); } 322 .adsa-done__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; border-radius: 50%; background: var(--adsa-ok); color: #fff; } 323 .adsa-done__icon .adsa-icon { display: block; width: 26px; height: 26px; } 324 .adsa-done__title { margin: 16px 0 8px; font-size: 24px; line-height: 1.3; } 325 .adsa-done__lead { margin: 0 0 12px; font-size: 15px; } 326 .adsa-done__links { margin: 16px 0 0; } 327 @media (max-width: 600px) { .adsa-done { padding: 20px; } .adsa-done__title { font-size: 20px; } } 328 329 /* 1.3.5: «You're all set!» (Home) and «Done — go back to Claude». */ 330 .adsa-allset { margin: 20px 0 24px; padding: 28px 32px; background: #fff; border: 1px solid #b8e6bf; border-left: 4px solid var(--adsa-ok); border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); max-width: 860px; } 331 .adsa-allset__head { display: flex; align-items: center; gap: 14px; } 332 .adsa-allset__title { margin: 0; font-size: 26px; line-height: 1.25; } 333 .adsa-allset__lead { margin: 12px 0 0; font-size: 15px; } 334 .adsa-allset__try { margin: 18px 0 8px; font-size: 15px; } 335 .adsa-allset__prompts { margin: 0; padding: 0; list-style: none; } 336 .adsa-allset__prompts li { margin: 0 0 8px; } 337 .adsa-allset__open { margin: 18px 0 8px; display: flex; align-items: center; flex-wrap: wrap; gap: 16px; } 338 .adsa-allset__hide { margin: 8px 0 0; text-align: right; } 339 .adsa-allset-collapsed { max-width: 860px; } 340 .adsa-allset-collapsed .adsa-allset { box-shadow: none; margin: 12px 0 0; } 341 .adsa-recheck { margin: 8px 0 0; } 342 @media (max-width: 600px) { .adsa-allset { padding: 20px 16px; } .adsa-allset__title { font-size: 21px; } } -
addsynapse-store-assistant/tags/1.3.5/assets/home.js
r3759648 r3763701 86 86 } 87 87 88 /* «Mark» actions: «I've added it» (step 2), «It worked» (step 4) — explicit buttons only. */88 /* «Mark» actions: «I've added it» (step 3) — explicit buttons only. */ 89 89 root.querySelectorAll( '[data-mark]' ).forEach( function ( n ) { 90 90 n.addEventListener( 'click', function () { … … 132 132 var checkBtn = root.querySelector( '.adsa-check' ); 133 133 if ( checkBtn && apiFetch ) { 134 checkBtn.addEventListener( 'click',function () {134 var runCheck = function () { 135 135 var out = root.querySelector( '.adsa-check-result' ); 136 136 out.textContent = t.checking || '…'; … … 163 163 checkBtn.disabled = false; 164 164 } ); 165 } ); 166 } 167 168 /* Steps 3–4: poll this site's own state (an approved AddSynapse application password, its first use). */ 165 }; 166 checkBtn.addEventListener( 'click', runCheck ); 167 /* 1.3.5: the check runs by itself when the page opens; «Re-check» runs it again. */ 168 if ( root.querySelector( '[data-adsa-autocheck]' ) ) { 169 runCheck(); 170 } 171 } 172 173 /* Steps 2–3: poll this site's own state (an approved AddSynapse key, its first use). */ 169 174 function poll() { 170 175 apiFetch( { path: cfg.status } ) … … 174 179 } 175 180 if ( s.ready || s.connected ) { 176 /* Step 2 turns green on its own once the site is connected (step 3 done implies it was added). */ 177 setDone( 'add', true ); 181 /* 1.3.5: step 3 («Use it from Claude or ChatGPT») shows once the site is connected. */ 182 root.querySelectorAll( '[data-adsa-after-connect]' ).forEach( function ( n ) { 183 n.hidden = false; 184 } ); 185 root.querySelectorAll( '[data-adsa-before-connect]' ).forEach( function ( n ) { 186 n.hidden = true; 187 } ); 178 188 } 179 189 if ( s.connected ) { … … 181 191 state( 'connect', ( t.approved || '%s' ).replace( '%s', s.detail.user ), true ); 182 192 } 183 if ( s.ready ) { 193 /* A connected site passed what step 1 checks, unless a real check failed. */ 194 if ( s.ready || ( s.connected && ! s.check_failed ) ) { 184 195 setDone( 'check', true ); 185 196 } 186 197 if ( s.used || s.ready ) { 198 setDone( 'add', true ); 187 199 setDone( 'confirm', true ); 188 200 state( 'confirm', s.used ? t.read : ( t.checked || t.read ), true ); -
addsynapse-store-assistant/tags/1.3.5/includes/Account/Api.php
r3759269 r3763701 13 13 14 14 /** 15 * Four calls exist, and nothing else ever contacts AddSynapse: 15 * These calls exist, and nothing else ever contacts AddSynapse: 16 * - PendingOnboarding::from_claude(): `GET /api/onboarding/pending?site=<home URL>` — only for an administrator while 17 * nothing is set up (after activation / the first setup screen), at most once per 10 minutes, the site address only; 16 18 * - OptIn::handle_create(): `POST /api/v1/assistant/optin` — only when an administrator clicks «Create account»; 17 19 * - Announcements::maybe_fetch(): `POST /api/v1/assistant/announcements` — at most once a day, only after that, only while … … 130 132 131 133 /** 134 * Whether a failed request surely never reached the server (no name lookup / no connection). 135 * 136 * @param WP_Error $error wp_remote_post() error. 137 */ 138 private static function not_sent( WP_Error $error ): bool { 139 return (bool) preg_match( '/cURL error (6|7):|Could not resolve|Failed to connect|Connection refused/i', $error->get_error_message() ); 140 } 141 142 /** 132 143 * POST JSON. 133 144 * … … 142 153 return new WP_Error( 'addsynapse_assistant_api_url', 'The AddSynapse address is not allowed on this site.' ); 143 154 } 144 $response = wp_remote_post( 145 $base . $path, 146 array( 147 'timeout' => $timeout, 148 'redirection' => 0, 149 'headers' => array( 150 'Content-Type' => 'application/json', 151 'Accept' => 'application/json', 152 ), 153 'body' => (string) wp_json_encode( $body ), 154 ) 155 $args = array( 156 'timeout' => $timeout, 157 'redirection' => 0, 158 'headers' => array( 159 'Content-Type' => 'application/json', 160 'Accept' => 'application/json', 161 ), 162 'body' => (string) wp_json_encode( $body ), 155 163 ); 164 $response = wp_remote_post( $base . $path, $args ); 165 // 1.3.5 (satest 24/10: a connect POST failed on this server and never reached AddSynapse): one retry, only when 166 // the request surely never left (name lookup / connect failed) — a request that may have arrived is never sent twice. 167 if ( is_wp_error( $response ) && self::not_sent( $response ) ) { 168 $response = wp_remote_post( $base . $path, $args ); 169 } 156 170 if ( is_wp_error( $response ) ) { 157 return new WP_Error( 'addsynapse_assistant_api_unreachable', 'AddSynapse could not be reached. Try again in a few minutes.' ); 171 $detail = sanitize_text_field( mb_substr( $response->get_error_message(), 0, 160 ) ); 172 return new WP_Error( 173 'addsynapse_assistant_api_unreachable', 174 /* translators: %s: technical detail from WordPress, e.g. «cURL error 28: Operation timed out». */ 175 sprintf( __( 'This site could not reach AddSynapse (%s). Nothing was connected. Try again in a minute; if it keeps failing, ask your host whether outgoing HTTPS requests to api.addsynapse.com are allowed.', 'addsynapse-store-assistant' ), '' !== $detail ? $detail : 'no detail' ) 176 ); 158 177 } 159 178 $decoded = json_decode( (string) wp_remote_retrieve_body( $response ), true ); -
addsynapse-store-assistant/tags/1.3.5/includes/Admin/HomePage.php
r3759648 r3763701 16 16 use Addsynapse\StoreAssistant\Account\Api; 17 17 use Addsynapse\StoreAssistant\Account\OptIn; 18 use Addsynapse\StoreAssistant\Account\PendingOnboarding; 18 19 use Addsynapse\StoreAssistant\Account\Usage; 19 20 use Addsynapse\StoreAssistant\Brain\Models; … … 41 42 public const ACTION_FINISH = 'addsynapse_assistant_onboarding_finish'; 42 43 44 /** The admin-post action of «Hide» on the «You're all set!» panel (1.3.5). */ 45 public const ACTION_ALLSET_HIDE = 'addsynapse_assistant_allset_hide'; 46 47 /** Option: array{since:int,hidden:bool} — when Home first showed «You're all set!», and whether it was hidden. */ 48 public const ALLSET_OPTION = 'addsynapse_assistant_allset'; 49 50 /** Days the «You're all set!» panel stays open on Home. */ 51 public const ALLSET_DAYS = 7; 52 43 53 /** The admin-post action of «Disconnect AddSynapse» (1.3.4). */ 44 54 public const ACTION_DISCONNECT = 'addsynapse_assistant_relay_disconnect'; … … 46 56 /** Script/style handle. */ 47 57 public const HANDLE = 'addsynapse-assistant-home'; 58 59 /** Transient: the administrator (user id) who just activated the plugin (one minute). */ 60 public const JUST_ACTIVATED = 'addsynapse_assistant_just_activated'; 61 62 /** Transient: the Claude connection was continued automatically once (no loop; 10 minutes). */ 63 public const AUTO_STARTED = 'addsynapse_assistant_onboarding_auto'; 48 64 49 65 /** The wizard views (query arg `setup`). */ … … 56 72 add_action( 'admin_post_' . self::ACTION_FINISH, array( $this, 'handle_finish' ) ); 57 73 add_action( 'admin_post_' . self::ACTION_DISCONNECT, array( self::class, 'handle_disconnect' ) ); 74 add_action( 'admin_post_' . self::ACTION_ALLSET_HIDE, array( self::class, 'handle_allset_hide' ) ); 58 75 // Before WordPress checks access to the requested page: old addresses keep working. 59 76 add_action( 'admin_menu', array( self::class, 'legacy_redirects' ), 1 ); 77 // After RelayClaim::maybe_handle (admin_init:1), before any output. 78 add_action( 'admin_init', array( self::class, 'maybe_start_from_claude' ), 5 ); 79 } 80 81 /** 82 * Activation (a second activation callback, next to Schema::install): remember for one minute that an administrator 83 * just activated the plugin, so the next admin page can continue the Claude connection (maybe_start_from_claude). 84 * Not on bulk activation, WP-CLI or network activation. 85 * 86 * @param bool $network_wide Network activation. 87 */ 88 public static function activated( $network_wide = false ): void { 89 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only: only tells a bulk activation apart; core checked its own nonce. 90 if ( $network_wide || ( defined( 'WP_CLI' ) && WP_CLI ) || isset( $_REQUEST['checked'] ) ) { 91 return; 92 } 93 // A new activation asks AddSynapse afresh and may continue the connection again (satest 24/10: a re-activation 94 // within 10 minutes found the old «already continued» guard and did nothing). 95 PendingOnboarding::reset(); 96 set_transient( self::JUST_ACTIVATED, get_current_user_id(), MINUTE_IN_SECONDS ); 97 } 98 99 /** 100 * 1.3.5 (owner 24/10, «6 clicks from Claude»): the manager started in Claude and AddSynapse has a connection pending for 101 * this site → right after activation (or on the first visit of the wizard) continue that connection: the browser goes 102 * to AddSynapse's connect page for this site, exactly as the «Connect to AddSynapse» button does, and AddSynapse sends 103 * it straight back to this plugin's «Connect <host> to your AddSynapse account? [Yes, connect] [Cancel]» screen. 104 * Nothing is connected without «Yes, connect». Once per 10 minutes at most (no loop); otherwise nothing happens and the 105 * usual screens show. 106 */ 107 public static function maybe_start_from_claude(): void { 108 if ( wp_doing_ajax() || is_network_admin() || ! current_user_can( 'manage_options' ) ) { 109 return; 110 } 111 $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET'; 112 if ( 'GET' !== $method ) { 113 return; 114 } 115 global $pagenow; 116 $just = (int) get_transient( self::JUST_ACTIVATED ); 117 if ( $just > 0 && 'plugins.php' === $pagenow ) { 118 delete_transient( self::JUST_ACTIVATED ); 119 } else { 120 $just = 0; 121 } 122 $wizard = 'admin.php' === $pagenow && AddsynapseMenu::PARENT_SLUG === self::flag( 'page' ) 123 && '' === self::flag( 'as_relay' ) && '' === self::flag( 'setup' ) && '' === self::flag( 'view' ) && '' === self::flag( 'welcome' ); 124 if ( ( get_current_user_id() !== $just && ! $wizard ) || get_transient( self::AUTO_STARTED ) || self::is_set_up() || ! RelayKeys::transport_ok() ) { 125 // Not «needs_setup()»: a wizard finished on an earlier install (the option outlives deactivation) must not stop 126 // a new connection started in Claude (satest 24/10). Only a working connection does. 127 return; 128 } 129 if ( ! PendingOnboarding::from_claude() ) { 130 return; 131 } 132 $url = Api::claude_connect_url( home_url( '/' ) ); 133 if ( ! Api::link_allowed( $url ) ) { 134 return; 135 } 136 set_transient( self::AUTO_STARTED, 1, PendingOnboarding::TTL ); 137 // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- AddSynapse's own connect page (Api::link_allowed: https on an AddSynapse host only). 138 wp_redirect( $url, 302 ); 139 exit; 60 140 } 61 141 … … 133 213 return 'home'; 134 214 } 135 return 'choose'; 215 return self::from_claude() ? 'mcp' : 'choose'; 216 } 217 218 /** 219 * 1.3.5 (owner 24/10): the manager started in Claude (AddSynapse says a connection for this site address is pending), 220 * so the first screen skips «Choose how you want to connect» and opens the Claude path with one button. Only on the 221 * wizard's default screen (no `setup`/`view` asked for) of a site with nothing set up; «Other options» (setup=choose) 222 * always shows both ways. Any failure of the question = the usual first screen. 223 */ 224 public static function from_claude(): bool { 225 if ( '' !== self::flag( 'setup' ) || 'home' === self::flag( 'view' ) || ! current_user_can( Config::capability() ) || self::is_set_up() ) { 226 return false; 227 } 228 return PendingOnboarding::from_claude(); 136 229 } 137 230 … … 376 469 */ 377 470 private static function back_link(): void { 471 if ( self::from_claude() ) { 472 // Not a lock (wp.org guideline 5): both ways stay one click away. 473 echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">'; 474 self::print_icon( 'back' ); 475 echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>'; 476 return; 477 } 378 478 $target = self::state()['done'] || self::is_set_up() ? self::url() : self::setup_url( 'choose' ); 379 479 echo '<a class="adsa-back" href="' . esc_url( $target ) . '">'; … … 385 485 * The «Finish» form of a path. 386 486 * 387 * @param string $path mcp | key. 388 * @param string $note Text next to the button. 389 */ 390 private static function finish( string $path, string $note ): void { 487 * @param string $path mcp | key. 488 * @param string $note Text next to the button. 489 * @param bool $primary The screen's main button (false: a plain one, when another button is the main one). 490 */ 491 private static function finish( string $path, string $note, bool $primary = true ): void { 391 492 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="adsa-finish">'; 392 493 wp_nonce_field( self::ACTION_FINISH ); 393 494 echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_FINISH ) . '">'; 394 495 echo '<input type="hidden" name="addsynapse_assistant_path" value="' . esc_attr( $path ) . '">'; 395 echo '<button type="submit" class="button button-primary button-heroadsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>';496 echo '<button type="submit" class="button ' . ( $primary ? 'button-primary button-hero ' : '' ) . 'adsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>'; 396 497 echo '<p class="adsa-finish__note">' . esc_html( $note ) . '</p>'; 397 498 echo '</form>'; … … 497 598 */ 498 599 private static function render_mcp(): void { 499 $status = SetupStatus::connection(); 500 $confirm = self::is_site() 501 ? __( 'Using AddSynapse, tell me my site name and how many pages it has. Change nothing.', 'addsynapse-store-assistant' ) 502 : __( 'Using AddSynapse, tell me my store name and how many products it has. Change nothing.', 'addsynapse-store-assistant' ); 600 $status = SetupStatus::connection(); 601 // Came from Claude and just pressed «Yes, connect» (AddSynapse sent the browser back): a clean «Done» screen, not 602 // the three steps (owner 24/10). The steps stay one click away. 603 if ( ( ! empty( $status['ready'] ) || ! empty( $status['connected'] ) ) && 'connected' === self::flag( 'as_relay' ) && PendingOnboarding::cached_from_claude() ) { 604 self::render_arrival_done(); 605 return; 606 } 503 607 // Connected for sure (AddSynapse confirmed the password, or used it): every step is done. 504 608 $ready = ! empty( $status['ready'] ); 505 609 // 1.3.4: AddSynapse connects with this plugin's own key, so Application Passwords being off (Wordfence's default) no 506 610 // longer blocks anything; only HTTPS is required (RelayKeys::transport_ok). 507 $steps = array( 508 'check' => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] ), 509 // Done once the site is connected (step 3 done: the AI already sent the connect link), or the manager says so 510 // («I've added it») — never on a click of «Add to Claude» or «Copy». Owner 22/10: step 3 green, step 2 not. 511 'add' => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), $ready || (bool) $status['connected'] ), 512 'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), (bool) $status['connected'] ), 513 'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), $ready || (bool) $status['used'] ), 514 ); 611 // 1.3.5 (owner 24/10): step 2 is ONE button that connects this site to the AddSynapse account (the account usually 612 // already uses Claude); adding AddSynapse to an AI app moves to step 3, shown once the site is connected. 613 $connected = $ready || (bool) $status['connected']; 614 // Came from Claude and not connected yet: «Connect to AddSynapse» is the only primary button on the screen. 615 $arrival = self::from_claude() && ! $connected; 616 $steps = array( 617 // A connected site has passed what the check checks (AddSynapse reached it): step 1 is done unless a real 618 // check ran and failed (owner 24/10: never «not done» next to two green steps). 619 'check' => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] || ( $connected && empty( $status['check_failed'] ) ) ), 620 'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), $connected ), 621 ); 622 // 1.3.5 (owner 24/10): two steps — the check (runs by itself) and Connect — then «Finish». How to use it from 623 // Claude or ChatGPT is on Home («You're all set!»). 515 624 self::back_link(); 516 625 echo '<div class="adsa-pathhead">'; 517 626 echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>'; 518 echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Four short steps, about five minutes. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>'; 627 if ( $arrival ) { 628 echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'Press «Connect to AddSynapse» below.', 'addsynapse-store-assistant' ) . '</p>'; 629 } else { 630 echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Two short steps. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>'; 631 } 519 632 echo '</div>'; 520 633 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: a success line when AddSynapse sent the browser back. 521 if ( $ ready&& isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) {634 if ( $connected && isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) { 522 635 // Back from AddSynapse right after «Connect» (1.3.3, hub protocol 3). 523 echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>'; 636 if ( PendingOnboarding::cached_from_claude() ) { 637 // Came from Claude: the AddSynapse page in the other tab moves on by itself. 638 echo '<div class="notice notice-success adsa-connected-notice adsa-arrival-done" data-adsa-relay-connected data-adsa-from="claude"><p><strong>' . esc_html__( 'Done — go back to Claude.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ) . '</p></div>'; 639 } else { 640 echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>'; 641 } 524 642 } 525 643 if ( ! $ready && ! RelayKeys::transport_ok() ) { … … 533 651 self::step_open( 'check', 1, __( 'Check the connection', 'addsynapse-store-assistant' ), $steps['check'][1] ); 534 652 echo '<p>' . esc_html__( 'Checks HTTPS, the REST API and the sign-in header of this site. Runs on your server only.', 'addsynapse-store-assistant' ) . '</p>'; 535 echo '<button type="button" class="button button-primary adsa-check">' . esc_html__( 'Check site connectivity', 'addsynapse-store-assistant' ) . '</button>'; 536 echo '<div class="adsa-check-result" aria-live="polite"></div>'; 653 // Runs by itself when the page opens (home.js); «Re-check» runs it again. 654 echo '<div class="adsa-check-result" aria-live="polite" data-adsa-autocheck></div>'; 655 echo '<p class="adsa-recheck"><button type="button" class="button-link adsa-check">' . esc_html__( 'Re-check', 'addsynapse-store-assistant' ) . '</button></p>'; 537 656 self::step_close(); 538 657 539 self::step_open( 'add', 2, __( 'Add AddSynapse to your AI', 'addsynapse-store-assistant' ), $steps['add'][1] ); 540 self::render_add_ways(); 541 self::step_close(); 542 543 self::step_open( 'connect', 3, __( 'Connect this site from the chat', 'addsynapse-store-assistant' ), $steps['connect'][1] ); 544 if ( $ready ) { 545 // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect prompt here. 546 echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓ — try asking your AI:', 'addsynapse-store-assistant' ) . '</span></p>'; 547 self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) ); 658 self::step_open( 'connect', 2, __( 'Connect this site to AddSynapse', 'addsynapse-store-assistant' ), $steps['connect'][1] ); 659 if ( $connected ) { 660 // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect button here. 661 // 1.3.5: also while AddSynapse has not used the new key yet (pending): a second Connect would make a second key. 662 echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</span></p>'; 548 663 } else { 549 echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>'; 550 self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) ); 551 self::authorize_note(); 664 self::render_connect_button(); 552 665 $waiting = $status['connected'] 553 666 /* translators: %s: user login */ … … 555 668 : __( 'Waiting for your approval…', 'addsynapse-store-assistant' ); 556 669 echo '<p class="adsa-state' . ( $status['connected'] ? ' is-ok' : ' is-waiting' ) . '" data-state="connect" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $waiting ) . '</span></p>'; 670 // The older way, from the chat (the AI answers with the same confirm link) — kept for whoever starts there. 671 echo '<details class="adsa-more" data-adsa-chat-connect><summary>' . esc_html__( 'Prefer to connect from your AI chat?', 'addsynapse-store-assistant' ) . '</summary>'; 672 echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>'; 673 self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) ); 674 echo '</details>'; 557 675 } 558 676 self::step_close(); 559 677 560 self::step_open( 'confirm', 4, self::is_site() ? __( 'Confirm your AI can read the site', 'addsynapse-store-assistant' ) : __( 'Confirm your AI can read the store', 'addsynapse-store-assistant' ), $steps['confirm'][1] );561 echo '<p>' . esc_html__( 'Ask one question that changes nothing. This step turns green when your AI reads the store.', 'addsynapse-store-assistant' ) . '</p>';562 self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );563 $confirmed = $status['used']564 ? __( 'Your AI read the store. You are all set.', 'addsynapse-store-assistant' )565 : ( $ready ? __( 'AddSynapse checked the connection. You are all set.', 'addsynapse-store-assistant' ) : __( 'Waiting for the first answer…', 'addsynapse-store-assistant' ) );566 echo '<p class="adsa-state' . ( $status['used'] || $ready ? ' is-ok' : '' ) . '" data-state="confirm" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $confirmed ) . '</span></p>';567 echo '<p><button type="button" class="button-link adsa-mark" data-mark="confirm">' . esc_html__( 'It worked — mark as done', 'addsynapse-store-assistant' ) . '</button></p>';568 self::step_close();569 678 echo '</ol>'; 570 679 571 self::finish( 'mcp', __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ) ); 680 // «Finish» is the main action once connected; before that «Connect to AddSynapse» is. 681 self::finish( 'mcp', $connected ? __( 'Next: Home shows how to use it from Claude or ChatGPT.', 'addsynapse-store-assistant' ) : __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ), $connected ); 572 682 573 683 echo '<details class="adsa-more adsa-more--box"><summary>' . esc_html__( 'More details: what AddSynapse can do on this site', 'addsynapse-store-assistant' ) . '</summary><ul>'; … … 581 691 582 692 /** 583 * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (steps 1–2 done, step 3 693 * «Done — go back to Claude» (from=claude, right after «Yes, connect»). Nothing to press here: the AddSynapse page in 694 * the other tab moves on by itself. A prompt to try, and links to Home and to the setup steps. 695 */ 696 private static function render_arrival_done(): void { 697 self::render_allset( __( 'Done — go back to Claude.', 'addsynapse-store-assistant' ), __( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ), false ); 698 echo '<p class="adsa-done__links"><a href="' . esc_url( self::url( array( 'view' => 'home' ) ) ) . '">' . esc_html__( 'Go to Home', 'addsynapse-store-assistant' ) . '</a> · <a href="' . esc_url( self::setup_url( 'mcp' ) ) . '">' . esc_html__( 'See the setup steps', 'addsynapse-store-assistant' ) . '</a></p>'; 699 } 700 701 /** 702 * Three example requests for Claude or ChatGPT (store or plain site). 703 * 704 * @return string[] 705 */ 706 private static function try_prompts(): array { 707 return self::is_site() 708 ? array( 709 __( 'Show me my 3 latest pages', 'addsynapse-store-assistant' ), 710 __( 'Which pages have no SEO description?', 'addsynapse-store-assistant' ), 711 __( 'What is in my main menu?', 'addsynapse-store-assistant' ), 712 ) 713 : array( 714 __( 'Show me 3 products from my store', 'addsynapse-store-assistant' ), 715 __( 'Which products have no image?', 'addsynapse-store-assistant' ), 716 __( 'What were my best sellers last month?', 'addsynapse-store-assistant' ), 717 ); 718 } 719 720 /** 721 * «You're all set!» — open Claude (primary) or ChatGPT, three requests to copy, and (collapsed) how to add AddSynapse 722 * to an AI app. Ordinary links the browser opens; nothing is sent by the server. 723 * 724 * @param string $title Heading. 725 * @param string $lead One line under it ('' = none). 726 * @param bool $dismiss Show «Hide» (Home only). 727 */ 728 private static function render_allset( string $title, string $lead, bool $dismiss ): void { 729 echo '<section class="adsa-allset" data-adsa-allset role="status" aria-labelledby="adsa-allset-title">'; 730 echo '<div class="adsa-allset__head"><span class="adsa-done__icon" aria-hidden="true">'; 731 self::print_icon( 'check' ); 732 echo '</span><h2 class="adsa-allset__title" id="adsa-allset-title">' . esc_html( $title ) . '</h2></div>'; 733 if ( '' !== $lead ) { 734 echo '<p class="adsa-allset__lead">' . esc_html( $lead ) . '</p>'; 735 } 736 echo '<p class="adsa-allset__try"><strong>' . esc_html__( 'Open Claude and try:', 'addsynapse-store-assistant' ) . '</strong></p>'; 737 echo '<ul class="adsa-allset__prompts">'; 738 foreach ( self::try_prompts() as $prompt ) { 739 echo '<li>'; 740 self::copy_field( $prompt, __( 'Request to send', 'addsynapse-store-assistant' ), __( 'Copy', 'addsynapse-store-assistant' ) ); 741 echo '</li>'; 742 } 743 echo '</ul>'; 744 echo '<p class="adsa-buttons adsa-allset__open"><a class="button button-primary button-hero" href="https://claude.ai/new" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open Claude ↗', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a> '; 745 echo '<a class="adsa-allset__gpt" href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a></p>'; 746 echo '<details class="adsa-more adsa-addai" data-adsa-add-ways><summary>' . esc_html__( 'AddSynapse not in your Claude or ChatGPT yet?', 'addsynapse-store-assistant' ) . '</summary>'; 747 self::render_add_ways(); 748 echo '</details>'; 749 if ( $dismiss ) { 750 echo '<form method="post" class="adsa-allset__hide" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">'; 751 wp_nonce_field( self::ACTION_ALLSET_HIDE ); 752 echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_ALLSET_HIDE ) . '">'; 753 echo '<button type="submit" class="button-link">' . esc_html__( 'Hide', 'addsynapse-store-assistant' ) . '</button></form>'; 754 } 755 echo '</section>'; 756 } 757 758 /** 759 * Home: the «You're all set!» panel once connected — prominent for the first days (ALLSET_DAYS) or until hidden, 760 * then collapsed. 761 * 762 * @param array<string,mixed> $status SetupStatus::connection(). 763 */ 764 private static function render_home_allset( array $status ): void { 765 if ( empty( $status['connected'] ) ) { 766 return; 767 } 768 $state = get_option( self::ALLSET_OPTION, array() ); 769 $state = is_array( $state ) ? $state : array(); 770 if ( empty( $state['since'] ) ) { 771 $state = array( 772 'since' => time(), 773 'hidden' => false, 774 ); 775 update_option( self::ALLSET_OPTION, $state, false ); 776 } 777 $fresh = empty( $state['hidden'] ) && ( time() - (int) $state['since'] ) < self::ALLSET_DAYS * DAY_IN_SECONDS; 778 if ( $fresh ) { 779 self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', true ); 780 return; 781 } 782 echo '<details class="adsa-more adsa-allset-collapsed" data-adsa-allset-collapsed><summary>' . esc_html__( 'Try it in Claude or ChatGPT', 'addsynapse-store-assistant' ) . '</summary>'; 783 self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', false ); 784 echo '</details>'; 785 } 786 787 /** 788 * «Hide» of the «You're all set!» panel (it stays, collapsed). 789 */ 790 public static function handle_allset_hide(): void { 791 if ( ! current_user_can( Config::capability() ) ) { 792 wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 ); 793 } 794 check_admin_referer( self::ACTION_ALLSET_HIDE ); 795 $state = get_option( self::ALLSET_OPTION, array() ); 796 $state = is_array( $state ) ? $state : array(); 797 $state['since'] = (int) ( $state['since'] ?? time() ); 798 $state['hidden'] = true; 799 update_option( self::ALLSET_OPTION, $state, false ); 800 wp_safe_redirect( self::url( array( 'view' => 'home' ) ) ); 801 exit; 802 } 803 804 805 /** 806 * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (step 1 done, step 2 584 807 * current) with the «confirm the account» card of RelayClaim. 585 808 */ 586 809 private static function render_relay_confirm(): void { 810 if ( PendingOnboarding::cached_from_claude() ) { 811 // Keep the answer for the «Done» screen after «Yes, connect» (the confirm may take a while). 812 PendingOnboarding::keep(); 813 } 814 if ( PendingOnboarding::cached_from_claude() && ! self::is_set_up() ) { 815 // Not a lock (wp.org guideline 5): Cancel and both ways stay one click away. 816 echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">'; 817 self::print_icon( 'back' ); 818 echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>'; 819 } 587 820 echo '<div class="adsa-pathhead">'; 588 821 echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>'; 822 if ( PendingOnboarding::cached_from_claude() ) { 823 echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong></p>'; 824 } 589 825 echo '</div>'; 590 826 self::progress( 591 827 array( 592 828 'check' => array( __( 'Check', 'addsynapse-store-assistant' ), true ), 593 'add' => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), true ),594 829 'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), false ), 595 'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), false ),596 830 ) 597 831 ); … … 606 840 */ 607 841 public static function authorize_note(): void { 608 echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>'; 609 } 610 611 /** 612 * Step 2: two clear ways to add AddSynapse — Claude's connector directory (one click), or a custom connector by address 842 echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Yes, connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>'; 843 } 844 845 /** 846 * Step 2: the one button — AddSynapse's connect page for this site (sign in or create a free account, approve the 847 * access, then confirm it on this plugin's page). An ordinary link the administrator's browser opens; nothing is sent 848 * by the server. 849 */ 850 private static function render_connect_button(): void { 851 echo '<p>' . esc_html( self::is_site() ? __( 'Links this site to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) : __( 'Links this store to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) ) . '</p>'; 852 printf( '<p class="adsa-buttons"><a class="button button-primary button-hero adsa-connect" href="%s">%s</a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) ) ), esc_html__( 'Connect to AddSynapse', 'addsynapse-store-assistant' ) ); 853 echo '<p class="description adsa-connect__sub">' . esc_html__( 'Free account — sign in or create one in a minute.', 'addsynapse-store-assistant' ) . '</p>'; 854 self::authorize_note(); 855 } 856 857 /** 858 * Step 3 (collapsed): two clear ways to add AddSynapse to an AI app — Claude's directory (one click), or by address 613 859 * (ChatGPT beta, Claude, other AI apps). 614 860 */ … … 620 866 echo '<h3 class="adsa-addway__title" id="adsa-addway-claude">' . esc_html__( 'Claude — one click from the connector directory', 'addsynapse-store-assistant' ) . '</h3>'; 621 867 echo '<p>' . esc_html__( 'Opens AddSynapse in Claude\'s connector directory. Press Connect there and sign in to AddSynapse (or create a free account).', 'addsynapse-store-assistant' ) . '</p>'; 622 printf( '<p class="adsa-buttons"><a class="button button-primary adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) )), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );868 printf( '<p class="adsa-buttons"><a class="button adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CLAUDE_URL ), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) ); 623 869 echo '</section>'; 624 870 … … 642 888 echo '<li>' . esc_html__( 'Authentication: OAuth. Tick «I trust this application» and press Create.', 'addsynapse-store-assistant' ) . '</li>'; 643 889 echo '<li>' . esc_html__( 'Sign in to AddSynapse (or create a free account) and approve the access.', 'addsynapse-store-assistant' ) . '</li>'; 644 echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then continue with step 3 below.', 'addsynapse-store-assistant' ) . '</li>';890 echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then ask it a question.', 'addsynapse-store-assistant' ) . '</li>'; 645 891 echo '</ol>'; 646 892 printf( '<p><a class="button" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CHATGPT_URL ), esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) ); … … 652 898 echo '</section>'; 653 899 echo '</div>'; 654 655 echo '<p class="adsa-added"><button type="button" class="button adsa-mark" data-mark="add">' . esc_html__( 'I\'ve added it', 'addsynapse-store-assistant' ) . '</button> <span class="description">' . esc_html__( 'This step also turns green on its own once the site is connected.', 'addsynapse-store-assistant' ) . '</span></p>';656 900 } 657 901 … … 792 1036 echo '<div><strong>' . esc_html__( 'Already connected ✓', 'addsynapse-store-assistant' ) . '</strong> '; 793 1037 echo esc_html__( 'This site is connected to AddSynapse. Go back to Claude or ChatGPT and continue there.', 'addsynapse-store-assistant' ) . '</div></div>'; 794 } elseif ( in_array( $welcome, array( 'mcp', 'key' ), true) ) {1038 } elseif ( 'key' === $welcome || ( 'mcp' === $welcome && empty( $status['connected'] ) ) ) { 795 1039 echo '<div class="adsa-welcome" role="status">'; 796 1040 self::print_icon( 'check' ); … … 799 1043 } 800 1044 1045 self::render_home_allset( $status ); 801 1046 Announcements::render( 'home' ); 802 1047 ReviewRequest::render( 'home' ); -
addsynapse-store-assistant/tags/1.3.5/includes/Install/Lifecycle.php
r3758975 r3763701 57 57 Usage::deactivate(); 58 58 RemoteAccess::revoke_all(); 59 // 1.3.5: a later activation asks AddSynapse afresh («did you start in Claude?»). 60 \Addsynapse\StoreAssistant\Account\PendingOnboarding::reset(); 59 61 } 60 62 -
addsynapse-store-assistant/tags/1.3.5/includes/Remote/RelayClaim.php
r3759648 r3763701 437 437 438 438 /** 439 * « Last step: confirm the account» — step 3of the wizard, with the AddSynapse account (name + masked email) and440 * Connect/ Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect.439 * «Connect <host> to your AddSynapse account» — step 2 of the wizard, with the AddSynapse account (name + masked email) and 440 * «Yes, connect» / Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect. 441 441 */ 442 442 public static function render_confirm_card(): void { … … 450 450 $who = trim( $account . ( '' !== $email ? ' (' . $email . ')' : '' ) ); 451 451 echo '<li class="adsa-step is-current adsa-relayconfirm" id="adsa-step-connect" data-step="connect" data-adsa-relay-confirm>'; 452 echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">3</span></span>'; 453 echo '<h2 class="adsa-step__title">' . esc_html__( 'Last step: confirm the account', 'addsynapse-store-assistant' ) . '</h2></div>'; 452 echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">2</span></span>'; 453 // The whole site address, with its folder (owner 24/10: «demo.addsynapse.com/satest», not just the host). 454 $host = untrailingslashit( (string) preg_replace( '#^https?://#i', '', home_url() ) ); 455 /* translators: %s: this site's host name, e.g. shop.example.com */ 456 echo '<h2 class="adsa-step__title">' . esc_html( sprintf( __( 'Connect %s to your AddSynapse account', 'addsynapse-store-assistant' ), $host ) ) . '</h2></div>'; 454 457 echo '<div class="adsa-step__body">'; 455 echo '<p>' . ( 'key' === ( $stash['mode'] ?? '' ) 456 ? esc_html__( 'AddSynapse asks to connect this site. Connect it to this AddSynapse account?', 'addsynapse-store-assistant' ) 457 : esc_html__( 'WordPress approved the connection. Connect this site to this AddSynapse account?', 'addsynapse-store-assistant' ) ) . '</p>'; 458 // 1.3.5 (owner 24/10): plain words — what happens, whose account, nothing changes until approved. 459 echo '<p class="adsa-relayconfirm__lead">' . esc_html( 460 'site' === \Addsynapse\StoreAssistant\Environment::site_type() 461 ? __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your site and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' ) 462 : __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your store and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' ) 463 ) . '</p>'; 458 464 echo '<div class="adsa-relayconfirm__account">'; 459 465 if ( '' !== $account || '' !== $email ) { … … 476 482 ) . '</p>'; 477 483 } 478 echo '<p class="adsa-relayconfirm__why ">' . esc_html__( 'We ask so nobody can link your site to their own account. Continue only if this is your account and you started this from your AI chat a moment ago.', 'addsynapse-store-assistant' ) . '</p>';484 echo '<p class="adsa-relayconfirm__why description">' . esc_html__( 'Only continue if this is your account. Didn\'t start this? Press Cancel.', 'addsynapse-store-assistant' ) . '</p>'; 479 485 if ( 'key' === ( $stash['mode'] ?? '' ) ) { 480 echo '<p class="adsa-relayconfirm__scope " data-adsa-relay-key>' . esc_html__( 'Connect creates a key for AddSynapse that works only for this plugin\'s tools, acting as you. Changes still wait for your Approve. You can disconnect at any time from this page.', 'addsynapse-store-assistant' ) . '</p>';486 echo '<p class="adsa-relayconfirm__scope description" data-adsa-relay-key>' . esc_html__( 'This creates a key that only works for this plugin\'s tools. You can disconnect any time here.', 'addsynapse-store-assistant' ) . '</p>'; 481 487 } 482 488 echo '<form method="post" class="adsa-finish adsa-relayconfirm__form" action="' . esc_url( … … 491 497 ) . '">'; 492 498 wp_nonce_field( 'addsynapse_assistant_relay_confirm_' . $state ); 493 echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( ' Connect', 'addsynapse-store-assistant' ) . '</button>';499 echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Yes, connect', 'addsynapse-store-assistant' ) . '</button>'; 494 500 echo '<button type="submit" name="as_cancel" value="1" class="button button-secondary">' . esc_html__( 'Cancel', 'addsynapse-store-assistant' ) . '</button>'; 495 501 echo '</form>'; … … 689 695 'protocol' => self::PROTOCOL, 690 696 ), 691 15697 40 692 698 ); 693 699 unset( $password ); … … 824 830 'protocol' => self::PROTOCOL, 825 831 ), 826 15 832 // AddSynapse checks the new key against this site before it answers (its own call can take ~15 s). 833 40 827 834 ); 828 835 unset( $key['secret'] ); -
addsynapse-store-assistant/tags/1.3.5/includes/Remote/RelayKeys.php
r3759648 r3763701 86 86 /** 87 87 * Whether keys may be created/used here: HTTPS, or a `local` development environment (same rule as Application Passwords). 88 * 1.3.5: HTTPS also when PHP sits behind a TLS-terminating proxy that does not set $_SERVER['HTTPS'] (Varnish/nginx on 89 * CloudPanel): the site's own address is https://, or a proxy on this same machine says so (X-Forwarded-Proto / 90 * X-Forwarded-Ssl, trusted only from a loopback address). 88 91 */ 89 92 public static function transport_ok(): bool { 90 return is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ); 93 if ( is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) ) { 94 return true; 95 } 96 if ( 'https' === strtolower( (string) wp_parse_url( home_url(), PHP_URL_SCHEME ) ) ) { 97 return true; 98 } 99 $remote = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; 100 if ( ! in_array( $remote, array( '127.0.0.1', '::1' ), true ) ) { 101 return false; 102 } 103 $proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) ) : ''; 104 $ssl = isset( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ) ) : ''; 105 return 'https' === $proto || 'on' === $ssl; 91 106 } 92 107 -
addsynapse-store-assistant/tags/1.3.5/includes/Remote/SetupStatus.php
r3759648 r3763701 166 166 $check = get_option( self::CHECK_OPTION, array() ); 167 167 return array( 168 'checked' => is_array( $check ) && ! empty( $check['ok'] ), 169 'connected' => null !== $found, 170 'used' => null !== $found && null !== $found['last_used'], 168 'checked' => is_array( $check ) && ! empty( $check['ok'] ), 169 // A real check ran and failed (the only case step 1 may stay open on a connected site). 170 'check_failed' => is_array( $check ) && array_key_exists( 'ok', $check ) && empty( $check['ok'] ), 171 'connected' => null !== $found, 172 'used' => null !== $found && null !== $found['last_used'], 171 173 // Connected for sure: AddSynapse confirmed this password (claim) or already used it. The wizard then shows every step done. 172 'claimed' => $claimed,173 'ready' => null !== $found && ( $claimed || null !== $found['last_used'] ),174 'detail' => $found,175 'log_count' => count( ApprovalLog::all() ),174 'claimed' => $claimed, 175 'ready' => null !== $found && ( $claimed || null !== $found['last_used'] ), 176 'detail' => $found, 177 'log_count' => count( ApprovalLog::all() ), 176 178 ); 177 179 } -
addsynapse-store-assistant/tags/1.3.5/package.json
r3759648 r3763701 1 1 { 2 2 "name": "addsynapse-store-assistant", 3 "version": "1.3. 4",3 "version": "1.3.5", 4 4 "private": true, 5 5 "description": "Admin chat UI of the AddSynapse Store Assistant WordPress plugin.", -
addsynapse-store-assistant/tags/1.3.5/readme.txt
r3759648 r3763701 7 7 WC requires at least: 8.0 8 8 WC tested up to: 11.1 9 Stable tag: 1.3. 49 Stable tag: 1.3.5 10 10 License: GPLv2 or later 11 11 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 172 172 == External services == 173 173 174 The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them.174 The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** (except the section 5 setup question) and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them. 175 175 176 176 = 1. Anthropic API — api.anthropic.com (only with your own Anthropic key) = … … 205 205 * AddSynapse privacy policy: https://addsynapse.com/privacy 206 206 207 = 5. AddSynapse for Claude and other AI apps — addsynapse.com ( only after you approve the connection) =208 209 Optional: use your site from Claude or ChatGPT (beta) (AddSynapse → Home → "Use your store from Claude or ChatGPT"). The setup steps only check your own site and show links and text to copy; they send nothing to AddSynapse. The "Add to Claude" link opens app.addsynapse.com with this site's address in the link (so AddSynapse knows which site to check when you sign up), which then opens Claude's connector page; your browser opens it only when you click it.207 = 5. AddSynapse for Claude and other AI apps — addsynapse.com (after you approve the connection; one setup question before) = 208 209 Optional: use your site from Claude or ChatGPT. "Connect to AddSynapse" opens app.addsynapse.com with this site's address in the link. While nothing is set up, for an administrator (after activation or on the setup screen, at most every 10 minutes), the plugin asks whether you started in Claude: `GET https://api.addsynapse.com/api/onboarding/pending?site=<site address>` (site address only). If yes, the browser continues there; nothing connects without Yes, connect. 210 210 211 211 You start the connection from your AI chat. It opens this plugin's page on your site. For a logged-in administrator only, that page asks AddSynapse which account started the connection (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim/preview`: the one-time code and site address; answer: account name and masked email) and asks you to confirm it. Only if you press Connect does the plugin create a random connection key for AddSynapse, acting as you, and send it once from your server over HTTPS (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim`: key, user name, site address, one-time code — never in a web address); Cancel creates nothing. The site keeps only a keyed hash of the key. A key AddSynapse does not use within 15 minutes is revoked. Sites connected with an earlier version keep their WordPress Application Password until they reconnect. … … 226 226 227 227 == Changelog == 228 229 = 1.3.5 = 230 * Improved: simpler setup. Step 2 is one «Connect to AddSynapse» button that links this site to your AddSynapse account; adding AddSynapse to Claude or ChatGPT moves to step 3, shown after the site is connected. 231 * Improved: started in Claude? Right after activation the plugin asks AddSynapse once (site address only, see External services) and goes straight to «Connect <site> to your AddSynapse account? Yes, connect», then «Done — go back to Claude». «Other options» keeps both ways one click away. 232 * Improved: setup has two steps (the connection check runs by itself) and Home opens with «You're all set!»: open Claude and three requests to try. 233 * Fixed: sites behind an HTTPS proxy (PHP sees plain HTTP) can connect; a failed request to AddSynapse now says what failed. 228 234 229 235 = 1.3.4 = … … 307 313 == Upgrade Notice == 308 314 315 = 1.3.5 = 316 Simpler setup: one «Connect to AddSynapse» button links the site to your account; started in Claude, you land right on «Yes, connect». 317 309 318 = 1.3.4 = 310 319 Connect without WordPress Application Passwords, with a key only this plugin accepts. Coupons above 50% off are approved only in wp-admin. Includes 1.3.3: change your site's appearance from Claude or ChatGPT, with Approve and Undo. -
addsynapse-store-assistant/tags/1.3.5/uninstall.php
r3759648 r3763701 88 88 // 1.3.0: the welcome wizard (finished / skipped, chosen way). 89 89 'addsynapse_assistant_onboarding', 90 // 1.3.5: Home's «You're all set!» panel (first shown, hidden). 91 'addsynapse_assistant_allset', 90 92 // 1.3.0: the AddSynapse connection AddSynapse confirmed (uuid of the password, user, time — never the password). 91 93 'addsynapse_assistant_relay_claimed', -
addsynapse-store-assistant/trunk/addsynapse-store-assistant.php
r3759648 r3763701 4 4 * Plugin URI: https://addsynapse.com/store-assistant 5 5 * Description: Admin chat where shop managers ask, in their own language, about their store and for changes to one product at a time — with your own Anthropic or OpenAI API key. Every change is a preview a person approves, with Undo. 6 * Version: 1.3. 46 * Version: 1.3.5 7 7 * Requires at least: 6.9 8 8 * Requires PHP: 7.4 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3. 4' );22 define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3.5' ); 23 23 define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_FILE', __FILE__ ); 24 24 define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); … … 55 55 56 56 register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Schema::class, 'install' ) ); 57 // 1.3.5: remember the activation for one minute (continues a connection started in Claude, HomePage::maybe_start_from_claude). 58 register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Admin\HomePage::class, 'activated' ) ); 57 59 register_deactivation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Lifecycle::class, 'deactivate' ) ); 58 60 -
addsynapse-store-assistant/trunk/assets/home.css
r3759648 r3763701 313 313 .adsa-apppw { margin: 12px 0 16px; } 314 314 .adsa-apppw .adsa-buttons { margin-bottom: 4px; } 315 316 /* 1.3.5: step 2 = one «Connect to AddSynapse» button; step 3 collapsed. */ 317 .adsa-connect__sub { margin: -4px 0 8px; } 318 .adsa-addai { margin-top: 8px; } 319 320 /* 1.3.5: «Done — go back to Claude» (from Claude, right after «Yes, connect»). */ 321 .adsa-done { max-width: 720px; margin: 24px 0; padding: 32px; background: #fff; border: 1px solid #b8e6bf; border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); } 322 .adsa-done__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; border-radius: 50%; background: var(--adsa-ok); color: #fff; } 323 .adsa-done__icon .adsa-icon { display: block; width: 26px; height: 26px; } 324 .adsa-done__title { margin: 16px 0 8px; font-size: 24px; line-height: 1.3; } 325 .adsa-done__lead { margin: 0 0 12px; font-size: 15px; } 326 .adsa-done__links { margin: 16px 0 0; } 327 @media (max-width: 600px) { .adsa-done { padding: 20px; } .adsa-done__title { font-size: 20px; } } 328 329 /* 1.3.5: «You're all set!» (Home) and «Done — go back to Claude». */ 330 .adsa-allset { margin: 20px 0 24px; padding: 28px 32px; background: #fff; border: 1px solid #b8e6bf; border-left: 4px solid var(--adsa-ok); border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); max-width: 860px; } 331 .adsa-allset__head { display: flex; align-items: center; gap: 14px; } 332 .adsa-allset__title { margin: 0; font-size: 26px; line-height: 1.25; } 333 .adsa-allset__lead { margin: 12px 0 0; font-size: 15px; } 334 .adsa-allset__try { margin: 18px 0 8px; font-size: 15px; } 335 .adsa-allset__prompts { margin: 0; padding: 0; list-style: none; } 336 .adsa-allset__prompts li { margin: 0 0 8px; } 337 .adsa-allset__open { margin: 18px 0 8px; display: flex; align-items: center; flex-wrap: wrap; gap: 16px; } 338 .adsa-allset__hide { margin: 8px 0 0; text-align: right; } 339 .adsa-allset-collapsed { max-width: 860px; } 340 .adsa-allset-collapsed .adsa-allset { box-shadow: none; margin: 12px 0 0; } 341 .adsa-recheck { margin: 8px 0 0; } 342 @media (max-width: 600px) { .adsa-allset { padding: 20px 16px; } .adsa-allset__title { font-size: 21px; } } -
addsynapse-store-assistant/trunk/assets/home.js
r3759648 r3763701 86 86 } 87 87 88 /* «Mark» actions: «I've added it» (step 2), «It worked» (step 4) — explicit buttons only. */88 /* «Mark» actions: «I've added it» (step 3) — explicit buttons only. */ 89 89 root.querySelectorAll( '[data-mark]' ).forEach( function ( n ) { 90 90 n.addEventListener( 'click', function () { … … 132 132 var checkBtn = root.querySelector( '.adsa-check' ); 133 133 if ( checkBtn && apiFetch ) { 134 checkBtn.addEventListener( 'click',function () {134 var runCheck = function () { 135 135 var out = root.querySelector( '.adsa-check-result' ); 136 136 out.textContent = t.checking || '…'; … … 163 163 checkBtn.disabled = false; 164 164 } ); 165 } ); 166 } 167 168 /* Steps 3–4: poll this site's own state (an approved AddSynapse application password, its first use). */ 165 }; 166 checkBtn.addEventListener( 'click', runCheck ); 167 /* 1.3.5: the check runs by itself when the page opens; «Re-check» runs it again. */ 168 if ( root.querySelector( '[data-adsa-autocheck]' ) ) { 169 runCheck(); 170 } 171 } 172 173 /* Steps 2–3: poll this site's own state (an approved AddSynapse key, its first use). */ 169 174 function poll() { 170 175 apiFetch( { path: cfg.status } ) … … 174 179 } 175 180 if ( s.ready || s.connected ) { 176 /* Step 2 turns green on its own once the site is connected (step 3 done implies it was added). */ 177 setDone( 'add', true ); 181 /* 1.3.5: step 3 («Use it from Claude or ChatGPT») shows once the site is connected. */ 182 root.querySelectorAll( '[data-adsa-after-connect]' ).forEach( function ( n ) { 183 n.hidden = false; 184 } ); 185 root.querySelectorAll( '[data-adsa-before-connect]' ).forEach( function ( n ) { 186 n.hidden = true; 187 } ); 178 188 } 179 189 if ( s.connected ) { … … 181 191 state( 'connect', ( t.approved || '%s' ).replace( '%s', s.detail.user ), true ); 182 192 } 183 if ( s.ready ) { 193 /* A connected site passed what step 1 checks, unless a real check failed. */ 194 if ( s.ready || ( s.connected && ! s.check_failed ) ) { 184 195 setDone( 'check', true ); 185 196 } 186 197 if ( s.used || s.ready ) { 198 setDone( 'add', true ); 187 199 setDone( 'confirm', true ); 188 200 state( 'confirm', s.used ? t.read : ( t.checked || t.read ), true ); -
addsynapse-store-assistant/trunk/includes/Account/Api.php
r3759269 r3763701 13 13 14 14 /** 15 * Four calls exist, and nothing else ever contacts AddSynapse: 15 * These calls exist, and nothing else ever contacts AddSynapse: 16 * - PendingOnboarding::from_claude(): `GET /api/onboarding/pending?site=<home URL>` — only for an administrator while 17 * nothing is set up (after activation / the first setup screen), at most once per 10 minutes, the site address only; 16 18 * - OptIn::handle_create(): `POST /api/v1/assistant/optin` — only when an administrator clicks «Create account»; 17 19 * - Announcements::maybe_fetch(): `POST /api/v1/assistant/announcements` — at most once a day, only after that, only while … … 130 132 131 133 /** 134 * Whether a failed request surely never reached the server (no name lookup / no connection). 135 * 136 * @param WP_Error $error wp_remote_post() error. 137 */ 138 private static function not_sent( WP_Error $error ): bool { 139 return (bool) preg_match( '/cURL error (6|7):|Could not resolve|Failed to connect|Connection refused/i', $error->get_error_message() ); 140 } 141 142 /** 132 143 * POST JSON. 133 144 * … … 142 153 return new WP_Error( 'addsynapse_assistant_api_url', 'The AddSynapse address is not allowed on this site.' ); 143 154 } 144 $response = wp_remote_post( 145 $base . $path, 146 array( 147 'timeout' => $timeout, 148 'redirection' => 0, 149 'headers' => array( 150 'Content-Type' => 'application/json', 151 'Accept' => 'application/json', 152 ), 153 'body' => (string) wp_json_encode( $body ), 154 ) 155 $args = array( 156 'timeout' => $timeout, 157 'redirection' => 0, 158 'headers' => array( 159 'Content-Type' => 'application/json', 160 'Accept' => 'application/json', 161 ), 162 'body' => (string) wp_json_encode( $body ), 155 163 ); 164 $response = wp_remote_post( $base . $path, $args ); 165 // 1.3.5 (satest 24/10: a connect POST failed on this server and never reached AddSynapse): one retry, only when 166 // the request surely never left (name lookup / connect failed) — a request that may have arrived is never sent twice. 167 if ( is_wp_error( $response ) && self::not_sent( $response ) ) { 168 $response = wp_remote_post( $base . $path, $args ); 169 } 156 170 if ( is_wp_error( $response ) ) { 157 return new WP_Error( 'addsynapse_assistant_api_unreachable', 'AddSynapse could not be reached. Try again in a few minutes.' ); 171 $detail = sanitize_text_field( mb_substr( $response->get_error_message(), 0, 160 ) ); 172 return new WP_Error( 173 'addsynapse_assistant_api_unreachable', 174 /* translators: %s: technical detail from WordPress, e.g. «cURL error 28: Operation timed out». */ 175 sprintf( __( 'This site could not reach AddSynapse (%s). Nothing was connected. Try again in a minute; if it keeps failing, ask your host whether outgoing HTTPS requests to api.addsynapse.com are allowed.', 'addsynapse-store-assistant' ), '' !== $detail ? $detail : 'no detail' ) 176 ); 158 177 } 159 178 $decoded = json_decode( (string) wp_remote_retrieve_body( $response ), true ); -
addsynapse-store-assistant/trunk/includes/Admin/HomePage.php
r3759648 r3763701 16 16 use Addsynapse\StoreAssistant\Account\Api; 17 17 use Addsynapse\StoreAssistant\Account\OptIn; 18 use Addsynapse\StoreAssistant\Account\PendingOnboarding; 18 19 use Addsynapse\StoreAssistant\Account\Usage; 19 20 use Addsynapse\StoreAssistant\Brain\Models; … … 41 42 public const ACTION_FINISH = 'addsynapse_assistant_onboarding_finish'; 42 43 44 /** The admin-post action of «Hide» on the «You're all set!» panel (1.3.5). */ 45 public const ACTION_ALLSET_HIDE = 'addsynapse_assistant_allset_hide'; 46 47 /** Option: array{since:int,hidden:bool} — when Home first showed «You're all set!», and whether it was hidden. */ 48 public const ALLSET_OPTION = 'addsynapse_assistant_allset'; 49 50 /** Days the «You're all set!» panel stays open on Home. */ 51 public const ALLSET_DAYS = 7; 52 43 53 /** The admin-post action of «Disconnect AddSynapse» (1.3.4). */ 44 54 public const ACTION_DISCONNECT = 'addsynapse_assistant_relay_disconnect'; … … 46 56 /** Script/style handle. */ 47 57 public const HANDLE = 'addsynapse-assistant-home'; 58 59 /** Transient: the administrator (user id) who just activated the plugin (one minute). */ 60 public const JUST_ACTIVATED = 'addsynapse_assistant_just_activated'; 61 62 /** Transient: the Claude connection was continued automatically once (no loop; 10 minutes). */ 63 public const AUTO_STARTED = 'addsynapse_assistant_onboarding_auto'; 48 64 49 65 /** The wizard views (query arg `setup`). */ … … 56 72 add_action( 'admin_post_' . self::ACTION_FINISH, array( $this, 'handle_finish' ) ); 57 73 add_action( 'admin_post_' . self::ACTION_DISCONNECT, array( self::class, 'handle_disconnect' ) ); 74 add_action( 'admin_post_' . self::ACTION_ALLSET_HIDE, array( self::class, 'handle_allset_hide' ) ); 58 75 // Before WordPress checks access to the requested page: old addresses keep working. 59 76 add_action( 'admin_menu', array( self::class, 'legacy_redirects' ), 1 ); 77 // After RelayClaim::maybe_handle (admin_init:1), before any output. 78 add_action( 'admin_init', array( self::class, 'maybe_start_from_claude' ), 5 ); 79 } 80 81 /** 82 * Activation (a second activation callback, next to Schema::install): remember for one minute that an administrator 83 * just activated the plugin, so the next admin page can continue the Claude connection (maybe_start_from_claude). 84 * Not on bulk activation, WP-CLI or network activation. 85 * 86 * @param bool $network_wide Network activation. 87 */ 88 public static function activated( $network_wide = false ): void { 89 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only: only tells a bulk activation apart; core checked its own nonce. 90 if ( $network_wide || ( defined( 'WP_CLI' ) && WP_CLI ) || isset( $_REQUEST['checked'] ) ) { 91 return; 92 } 93 // A new activation asks AddSynapse afresh and may continue the connection again (satest 24/10: a re-activation 94 // within 10 minutes found the old «already continued» guard and did nothing). 95 PendingOnboarding::reset(); 96 set_transient( self::JUST_ACTIVATED, get_current_user_id(), MINUTE_IN_SECONDS ); 97 } 98 99 /** 100 * 1.3.5 (owner 24/10, «6 clicks from Claude»): the manager started in Claude and AddSynapse has a connection pending for 101 * this site → right after activation (or on the first visit of the wizard) continue that connection: the browser goes 102 * to AddSynapse's connect page for this site, exactly as the «Connect to AddSynapse» button does, and AddSynapse sends 103 * it straight back to this plugin's «Connect <host> to your AddSynapse account? [Yes, connect] [Cancel]» screen. 104 * Nothing is connected without «Yes, connect». Once per 10 minutes at most (no loop); otherwise nothing happens and the 105 * usual screens show. 106 */ 107 public static function maybe_start_from_claude(): void { 108 if ( wp_doing_ajax() || is_network_admin() || ! current_user_can( 'manage_options' ) ) { 109 return; 110 } 111 $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET'; 112 if ( 'GET' !== $method ) { 113 return; 114 } 115 global $pagenow; 116 $just = (int) get_transient( self::JUST_ACTIVATED ); 117 if ( $just > 0 && 'plugins.php' === $pagenow ) { 118 delete_transient( self::JUST_ACTIVATED ); 119 } else { 120 $just = 0; 121 } 122 $wizard = 'admin.php' === $pagenow && AddsynapseMenu::PARENT_SLUG === self::flag( 'page' ) 123 && '' === self::flag( 'as_relay' ) && '' === self::flag( 'setup' ) && '' === self::flag( 'view' ) && '' === self::flag( 'welcome' ); 124 if ( ( get_current_user_id() !== $just && ! $wizard ) || get_transient( self::AUTO_STARTED ) || self::is_set_up() || ! RelayKeys::transport_ok() ) { 125 // Not «needs_setup()»: a wizard finished on an earlier install (the option outlives deactivation) must not stop 126 // a new connection started in Claude (satest 24/10). Only a working connection does. 127 return; 128 } 129 if ( ! PendingOnboarding::from_claude() ) { 130 return; 131 } 132 $url = Api::claude_connect_url( home_url( '/' ) ); 133 if ( ! Api::link_allowed( $url ) ) { 134 return; 135 } 136 set_transient( self::AUTO_STARTED, 1, PendingOnboarding::TTL ); 137 // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- AddSynapse's own connect page (Api::link_allowed: https on an AddSynapse host only). 138 wp_redirect( $url, 302 ); 139 exit; 60 140 } 61 141 … … 133 213 return 'home'; 134 214 } 135 return 'choose'; 215 return self::from_claude() ? 'mcp' : 'choose'; 216 } 217 218 /** 219 * 1.3.5 (owner 24/10): the manager started in Claude (AddSynapse says a connection for this site address is pending), 220 * so the first screen skips «Choose how you want to connect» and opens the Claude path with one button. Only on the 221 * wizard's default screen (no `setup`/`view` asked for) of a site with nothing set up; «Other options» (setup=choose) 222 * always shows both ways. Any failure of the question = the usual first screen. 223 */ 224 public static function from_claude(): bool { 225 if ( '' !== self::flag( 'setup' ) || 'home' === self::flag( 'view' ) || ! current_user_can( Config::capability() ) || self::is_set_up() ) { 226 return false; 227 } 228 return PendingOnboarding::from_claude(); 136 229 } 137 230 … … 376 469 */ 377 470 private static function back_link(): void { 471 if ( self::from_claude() ) { 472 // Not a lock (wp.org guideline 5): both ways stay one click away. 473 echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">'; 474 self::print_icon( 'back' ); 475 echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>'; 476 return; 477 } 378 478 $target = self::state()['done'] || self::is_set_up() ? self::url() : self::setup_url( 'choose' ); 379 479 echo '<a class="adsa-back" href="' . esc_url( $target ) . '">'; … … 385 485 * The «Finish» form of a path. 386 486 * 387 * @param string $path mcp | key. 388 * @param string $note Text next to the button. 389 */ 390 private static function finish( string $path, string $note ): void { 487 * @param string $path mcp | key. 488 * @param string $note Text next to the button. 489 * @param bool $primary The screen's main button (false: a plain one, when another button is the main one). 490 */ 491 private static function finish( string $path, string $note, bool $primary = true ): void { 391 492 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="adsa-finish">'; 392 493 wp_nonce_field( self::ACTION_FINISH ); 393 494 echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_FINISH ) . '">'; 394 495 echo '<input type="hidden" name="addsynapse_assistant_path" value="' . esc_attr( $path ) . '">'; 395 echo '<button type="submit" class="button button-primary button-heroadsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>';496 echo '<button type="submit" class="button ' . ( $primary ? 'button-primary button-hero ' : '' ) . 'adsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>'; 396 497 echo '<p class="adsa-finish__note">' . esc_html( $note ) . '</p>'; 397 498 echo '</form>'; … … 497 598 */ 498 599 private static function render_mcp(): void { 499 $status = SetupStatus::connection(); 500 $confirm = self::is_site() 501 ? __( 'Using AddSynapse, tell me my site name and how many pages it has. Change nothing.', 'addsynapse-store-assistant' ) 502 : __( 'Using AddSynapse, tell me my store name and how many products it has. Change nothing.', 'addsynapse-store-assistant' ); 600 $status = SetupStatus::connection(); 601 // Came from Claude and just pressed «Yes, connect» (AddSynapse sent the browser back): a clean «Done» screen, not 602 // the three steps (owner 24/10). The steps stay one click away. 603 if ( ( ! empty( $status['ready'] ) || ! empty( $status['connected'] ) ) && 'connected' === self::flag( 'as_relay' ) && PendingOnboarding::cached_from_claude() ) { 604 self::render_arrival_done(); 605 return; 606 } 503 607 // Connected for sure (AddSynapse confirmed the password, or used it): every step is done. 504 608 $ready = ! empty( $status['ready'] ); 505 609 // 1.3.4: AddSynapse connects with this plugin's own key, so Application Passwords being off (Wordfence's default) no 506 610 // longer blocks anything; only HTTPS is required (RelayKeys::transport_ok). 507 $steps = array( 508 'check' => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] ), 509 // Done once the site is connected (step 3 done: the AI already sent the connect link), or the manager says so 510 // («I've added it») — never on a click of «Add to Claude» or «Copy». Owner 22/10: step 3 green, step 2 not. 511 'add' => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), $ready || (bool) $status['connected'] ), 512 'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), (bool) $status['connected'] ), 513 'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), $ready || (bool) $status['used'] ), 514 ); 611 // 1.3.5 (owner 24/10): step 2 is ONE button that connects this site to the AddSynapse account (the account usually 612 // already uses Claude); adding AddSynapse to an AI app moves to step 3, shown once the site is connected. 613 $connected = $ready || (bool) $status['connected']; 614 // Came from Claude and not connected yet: «Connect to AddSynapse» is the only primary button on the screen. 615 $arrival = self::from_claude() && ! $connected; 616 $steps = array( 617 // A connected site has passed what the check checks (AddSynapse reached it): step 1 is done unless a real 618 // check ran and failed (owner 24/10: never «not done» next to two green steps). 619 'check' => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] || ( $connected && empty( $status['check_failed'] ) ) ), 620 'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), $connected ), 621 ); 622 // 1.3.5 (owner 24/10): two steps — the check (runs by itself) and Connect — then «Finish». How to use it from 623 // Claude or ChatGPT is on Home («You're all set!»). 515 624 self::back_link(); 516 625 echo '<div class="adsa-pathhead">'; 517 626 echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>'; 518 echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Four short steps, about five minutes. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>'; 627 if ( $arrival ) { 628 echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'Press «Connect to AddSynapse» below.', 'addsynapse-store-assistant' ) . '</p>'; 629 } else { 630 echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Two short steps. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>'; 631 } 519 632 echo '</div>'; 520 633 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: a success line when AddSynapse sent the browser back. 521 if ( $ ready&& isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) {634 if ( $connected && isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) { 522 635 // Back from AddSynapse right after «Connect» (1.3.3, hub protocol 3). 523 echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>'; 636 if ( PendingOnboarding::cached_from_claude() ) { 637 // Came from Claude: the AddSynapse page in the other tab moves on by itself. 638 echo '<div class="notice notice-success adsa-connected-notice adsa-arrival-done" data-adsa-relay-connected data-adsa-from="claude"><p><strong>' . esc_html__( 'Done — go back to Claude.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ) . '</p></div>'; 639 } else { 640 echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>'; 641 } 524 642 } 525 643 if ( ! $ready && ! RelayKeys::transport_ok() ) { … … 533 651 self::step_open( 'check', 1, __( 'Check the connection', 'addsynapse-store-assistant' ), $steps['check'][1] ); 534 652 echo '<p>' . esc_html__( 'Checks HTTPS, the REST API and the sign-in header of this site. Runs on your server only.', 'addsynapse-store-assistant' ) . '</p>'; 535 echo '<button type="button" class="button button-primary adsa-check">' . esc_html__( 'Check site connectivity', 'addsynapse-store-assistant' ) . '</button>'; 536 echo '<div class="adsa-check-result" aria-live="polite"></div>'; 653 // Runs by itself when the page opens (home.js); «Re-check» runs it again. 654 echo '<div class="adsa-check-result" aria-live="polite" data-adsa-autocheck></div>'; 655 echo '<p class="adsa-recheck"><button type="button" class="button-link adsa-check">' . esc_html__( 'Re-check', 'addsynapse-store-assistant' ) . '</button></p>'; 537 656 self::step_close(); 538 657 539 self::step_open( 'add', 2, __( 'Add AddSynapse to your AI', 'addsynapse-store-assistant' ), $steps['add'][1] ); 540 self::render_add_ways(); 541 self::step_close(); 542 543 self::step_open( 'connect', 3, __( 'Connect this site from the chat', 'addsynapse-store-assistant' ), $steps['connect'][1] ); 544 if ( $ready ) { 545 // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect prompt here. 546 echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓ — try asking your AI:', 'addsynapse-store-assistant' ) . '</span></p>'; 547 self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) ); 658 self::step_open( 'connect', 2, __( 'Connect this site to AddSynapse', 'addsynapse-store-assistant' ), $steps['connect'][1] ); 659 if ( $connected ) { 660 // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect button here. 661 // 1.3.5: also while AddSynapse has not used the new key yet (pending): a second Connect would make a second key. 662 echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</span></p>'; 548 663 } else { 549 echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>'; 550 self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) ); 551 self::authorize_note(); 664 self::render_connect_button(); 552 665 $waiting = $status['connected'] 553 666 /* translators: %s: user login */ … … 555 668 : __( 'Waiting for your approval…', 'addsynapse-store-assistant' ); 556 669 echo '<p class="adsa-state' . ( $status['connected'] ? ' is-ok' : ' is-waiting' ) . '" data-state="connect" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $waiting ) . '</span></p>'; 670 // The older way, from the chat (the AI answers with the same confirm link) — kept for whoever starts there. 671 echo '<details class="adsa-more" data-adsa-chat-connect><summary>' . esc_html__( 'Prefer to connect from your AI chat?', 'addsynapse-store-assistant' ) . '</summary>'; 672 echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>'; 673 self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) ); 674 echo '</details>'; 557 675 } 558 676 self::step_close(); 559 677 560 self::step_open( 'confirm', 4, self::is_site() ? __( 'Confirm your AI can read the site', 'addsynapse-store-assistant' ) : __( 'Confirm your AI can read the store', 'addsynapse-store-assistant' ), $steps['confirm'][1] );561 echo '<p>' . esc_html__( 'Ask one question that changes nothing. This step turns green when your AI reads the store.', 'addsynapse-store-assistant' ) . '</p>';562 self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );563 $confirmed = $status['used']564 ? __( 'Your AI read the store. You are all set.', 'addsynapse-store-assistant' )565 : ( $ready ? __( 'AddSynapse checked the connection. You are all set.', 'addsynapse-store-assistant' ) : __( 'Waiting for the first answer…', 'addsynapse-store-assistant' ) );566 echo '<p class="adsa-state' . ( $status['used'] || $ready ? ' is-ok' : '' ) . '" data-state="confirm" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $confirmed ) . '</span></p>';567 echo '<p><button type="button" class="button-link adsa-mark" data-mark="confirm">' . esc_html__( 'It worked — mark as done', 'addsynapse-store-assistant' ) . '</button></p>';568 self::step_close();569 678 echo '</ol>'; 570 679 571 self::finish( 'mcp', __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ) ); 680 // «Finish» is the main action once connected; before that «Connect to AddSynapse» is. 681 self::finish( 'mcp', $connected ? __( 'Next: Home shows how to use it from Claude or ChatGPT.', 'addsynapse-store-assistant' ) : __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ), $connected ); 572 682 573 683 echo '<details class="adsa-more adsa-more--box"><summary>' . esc_html__( 'More details: what AddSynapse can do on this site', 'addsynapse-store-assistant' ) . '</summary><ul>'; … … 581 691 582 692 /** 583 * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (steps 1–2 done, step 3 693 * «Done — go back to Claude» (from=claude, right after «Yes, connect»). Nothing to press here: the AddSynapse page in 694 * the other tab moves on by itself. A prompt to try, and links to Home and to the setup steps. 695 */ 696 private static function render_arrival_done(): void { 697 self::render_allset( __( 'Done — go back to Claude.', 'addsynapse-store-assistant' ), __( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ), false ); 698 echo '<p class="adsa-done__links"><a href="' . esc_url( self::url( array( 'view' => 'home' ) ) ) . '">' . esc_html__( 'Go to Home', 'addsynapse-store-assistant' ) . '</a> · <a href="' . esc_url( self::setup_url( 'mcp' ) ) . '">' . esc_html__( 'See the setup steps', 'addsynapse-store-assistant' ) . '</a></p>'; 699 } 700 701 /** 702 * Three example requests for Claude or ChatGPT (store or plain site). 703 * 704 * @return string[] 705 */ 706 private static function try_prompts(): array { 707 return self::is_site() 708 ? array( 709 __( 'Show me my 3 latest pages', 'addsynapse-store-assistant' ), 710 __( 'Which pages have no SEO description?', 'addsynapse-store-assistant' ), 711 __( 'What is in my main menu?', 'addsynapse-store-assistant' ), 712 ) 713 : array( 714 __( 'Show me 3 products from my store', 'addsynapse-store-assistant' ), 715 __( 'Which products have no image?', 'addsynapse-store-assistant' ), 716 __( 'What were my best sellers last month?', 'addsynapse-store-assistant' ), 717 ); 718 } 719 720 /** 721 * «You're all set!» — open Claude (primary) or ChatGPT, three requests to copy, and (collapsed) how to add AddSynapse 722 * to an AI app. Ordinary links the browser opens; nothing is sent by the server. 723 * 724 * @param string $title Heading. 725 * @param string $lead One line under it ('' = none). 726 * @param bool $dismiss Show «Hide» (Home only). 727 */ 728 private static function render_allset( string $title, string $lead, bool $dismiss ): void { 729 echo '<section class="adsa-allset" data-adsa-allset role="status" aria-labelledby="adsa-allset-title">'; 730 echo '<div class="adsa-allset__head"><span class="adsa-done__icon" aria-hidden="true">'; 731 self::print_icon( 'check' ); 732 echo '</span><h2 class="adsa-allset__title" id="adsa-allset-title">' . esc_html( $title ) . '</h2></div>'; 733 if ( '' !== $lead ) { 734 echo '<p class="adsa-allset__lead">' . esc_html( $lead ) . '</p>'; 735 } 736 echo '<p class="adsa-allset__try"><strong>' . esc_html__( 'Open Claude and try:', 'addsynapse-store-assistant' ) . '</strong></p>'; 737 echo '<ul class="adsa-allset__prompts">'; 738 foreach ( self::try_prompts() as $prompt ) { 739 echo '<li>'; 740 self::copy_field( $prompt, __( 'Request to send', 'addsynapse-store-assistant' ), __( 'Copy', 'addsynapse-store-assistant' ) ); 741 echo '</li>'; 742 } 743 echo '</ul>'; 744 echo '<p class="adsa-buttons adsa-allset__open"><a class="button button-primary button-hero" href="https://claude.ai/new" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open Claude ↗', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a> '; 745 echo '<a class="adsa-allset__gpt" href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a></p>'; 746 echo '<details class="adsa-more adsa-addai" data-adsa-add-ways><summary>' . esc_html__( 'AddSynapse not in your Claude or ChatGPT yet?', 'addsynapse-store-assistant' ) . '</summary>'; 747 self::render_add_ways(); 748 echo '</details>'; 749 if ( $dismiss ) { 750 echo '<form method="post" class="adsa-allset__hide" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">'; 751 wp_nonce_field( self::ACTION_ALLSET_HIDE ); 752 echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_ALLSET_HIDE ) . '">'; 753 echo '<button type="submit" class="button-link">' . esc_html__( 'Hide', 'addsynapse-store-assistant' ) . '</button></form>'; 754 } 755 echo '</section>'; 756 } 757 758 /** 759 * Home: the «You're all set!» panel once connected — prominent for the first days (ALLSET_DAYS) or until hidden, 760 * then collapsed. 761 * 762 * @param array<string,mixed> $status SetupStatus::connection(). 763 */ 764 private static function render_home_allset( array $status ): void { 765 if ( empty( $status['connected'] ) ) { 766 return; 767 } 768 $state = get_option( self::ALLSET_OPTION, array() ); 769 $state = is_array( $state ) ? $state : array(); 770 if ( empty( $state['since'] ) ) { 771 $state = array( 772 'since' => time(), 773 'hidden' => false, 774 ); 775 update_option( self::ALLSET_OPTION, $state, false ); 776 } 777 $fresh = empty( $state['hidden'] ) && ( time() - (int) $state['since'] ) < self::ALLSET_DAYS * DAY_IN_SECONDS; 778 if ( $fresh ) { 779 self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', true ); 780 return; 781 } 782 echo '<details class="adsa-more adsa-allset-collapsed" data-adsa-allset-collapsed><summary>' . esc_html__( 'Try it in Claude or ChatGPT', 'addsynapse-store-assistant' ) . '</summary>'; 783 self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', false ); 784 echo '</details>'; 785 } 786 787 /** 788 * «Hide» of the «You're all set!» panel (it stays, collapsed). 789 */ 790 public static function handle_allset_hide(): void { 791 if ( ! current_user_can( Config::capability() ) ) { 792 wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 ); 793 } 794 check_admin_referer( self::ACTION_ALLSET_HIDE ); 795 $state = get_option( self::ALLSET_OPTION, array() ); 796 $state = is_array( $state ) ? $state : array(); 797 $state['since'] = (int) ( $state['since'] ?? time() ); 798 $state['hidden'] = true; 799 update_option( self::ALLSET_OPTION, $state, false ); 800 wp_safe_redirect( self::url( array( 'view' => 'home' ) ) ); 801 exit; 802 } 803 804 805 /** 806 * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (step 1 done, step 2 584 807 * current) with the «confirm the account» card of RelayClaim. 585 808 */ 586 809 private static function render_relay_confirm(): void { 810 if ( PendingOnboarding::cached_from_claude() ) { 811 // Keep the answer for the «Done» screen after «Yes, connect» (the confirm may take a while). 812 PendingOnboarding::keep(); 813 } 814 if ( PendingOnboarding::cached_from_claude() && ! self::is_set_up() ) { 815 // Not a lock (wp.org guideline 5): Cancel and both ways stay one click away. 816 echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">'; 817 self::print_icon( 'back' ); 818 echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>'; 819 } 587 820 echo '<div class="adsa-pathhead">'; 588 821 echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>'; 822 if ( PendingOnboarding::cached_from_claude() ) { 823 echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong></p>'; 824 } 589 825 echo '</div>'; 590 826 self::progress( 591 827 array( 592 828 'check' => array( __( 'Check', 'addsynapse-store-assistant' ), true ), 593 'add' => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), true ),594 829 'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), false ), 595 'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), false ),596 830 ) 597 831 ); … … 606 840 */ 607 841 public static function authorize_note(): void { 608 echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>'; 609 } 610 611 /** 612 * Step 2: two clear ways to add AddSynapse — Claude's connector directory (one click), or a custom connector by address 842 echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Yes, connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>'; 843 } 844 845 /** 846 * Step 2: the one button — AddSynapse's connect page for this site (sign in or create a free account, approve the 847 * access, then confirm it on this plugin's page). An ordinary link the administrator's browser opens; nothing is sent 848 * by the server. 849 */ 850 private static function render_connect_button(): void { 851 echo '<p>' . esc_html( self::is_site() ? __( 'Links this site to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) : __( 'Links this store to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) ) . '</p>'; 852 printf( '<p class="adsa-buttons"><a class="button button-primary button-hero adsa-connect" href="%s">%s</a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) ) ), esc_html__( 'Connect to AddSynapse', 'addsynapse-store-assistant' ) ); 853 echo '<p class="description adsa-connect__sub">' . esc_html__( 'Free account — sign in or create one in a minute.', 'addsynapse-store-assistant' ) . '</p>'; 854 self::authorize_note(); 855 } 856 857 /** 858 * Step 3 (collapsed): two clear ways to add AddSynapse to an AI app — Claude's directory (one click), or by address 613 859 * (ChatGPT beta, Claude, other AI apps). 614 860 */ … … 620 866 echo '<h3 class="adsa-addway__title" id="adsa-addway-claude">' . esc_html__( 'Claude — one click from the connector directory', 'addsynapse-store-assistant' ) . '</h3>'; 621 867 echo '<p>' . esc_html__( 'Opens AddSynapse in Claude\'s connector directory. Press Connect there and sign in to AddSynapse (or create a free account).', 'addsynapse-store-assistant' ) . '</p>'; 622 printf( '<p class="adsa-buttons"><a class="button button-primary adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) )), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );868 printf( '<p class="adsa-buttons"><a class="button adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CLAUDE_URL ), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) ); 623 869 echo '</section>'; 624 870 … … 642 888 echo '<li>' . esc_html__( 'Authentication: OAuth. Tick «I trust this application» and press Create.', 'addsynapse-store-assistant' ) . '</li>'; 643 889 echo '<li>' . esc_html__( 'Sign in to AddSynapse (or create a free account) and approve the access.', 'addsynapse-store-assistant' ) . '</li>'; 644 echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then continue with step 3 below.', 'addsynapse-store-assistant' ) . '</li>';890 echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then ask it a question.', 'addsynapse-store-assistant' ) . '</li>'; 645 891 echo '</ol>'; 646 892 printf( '<p><a class="button" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CHATGPT_URL ), esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) ); … … 652 898 echo '</section>'; 653 899 echo '</div>'; 654 655 echo '<p class="adsa-added"><button type="button" class="button adsa-mark" data-mark="add">' . esc_html__( 'I\'ve added it', 'addsynapse-store-assistant' ) . '</button> <span class="description">' . esc_html__( 'This step also turns green on its own once the site is connected.', 'addsynapse-store-assistant' ) . '</span></p>';656 900 } 657 901 … … 792 1036 echo '<div><strong>' . esc_html__( 'Already connected ✓', 'addsynapse-store-assistant' ) . '</strong> '; 793 1037 echo esc_html__( 'This site is connected to AddSynapse. Go back to Claude or ChatGPT and continue there.', 'addsynapse-store-assistant' ) . '</div></div>'; 794 } elseif ( in_array( $welcome, array( 'mcp', 'key' ), true) ) {1038 } elseif ( 'key' === $welcome || ( 'mcp' === $welcome && empty( $status['connected'] ) ) ) { 795 1039 echo '<div class="adsa-welcome" role="status">'; 796 1040 self::print_icon( 'check' ); … … 799 1043 } 800 1044 1045 self::render_home_allset( $status ); 801 1046 Announcements::render( 'home' ); 802 1047 ReviewRequest::render( 'home' ); -
addsynapse-store-assistant/trunk/includes/Install/Lifecycle.php
r3758975 r3763701 57 57 Usage::deactivate(); 58 58 RemoteAccess::revoke_all(); 59 // 1.3.5: a later activation asks AddSynapse afresh («did you start in Claude?»). 60 \Addsynapse\StoreAssistant\Account\PendingOnboarding::reset(); 59 61 } 60 62 -
addsynapse-store-assistant/trunk/includes/Remote/RelayClaim.php
r3759648 r3763701 437 437 438 438 /** 439 * « Last step: confirm the account» — step 3of the wizard, with the AddSynapse account (name + masked email) and440 * Connect/ Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect.439 * «Connect <host> to your AddSynapse account» — step 2 of the wizard, with the AddSynapse account (name + masked email) and 440 * «Yes, connect» / Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect. 441 441 */ 442 442 public static function render_confirm_card(): void { … … 450 450 $who = trim( $account . ( '' !== $email ? ' (' . $email . ')' : '' ) ); 451 451 echo '<li class="adsa-step is-current adsa-relayconfirm" id="adsa-step-connect" data-step="connect" data-adsa-relay-confirm>'; 452 echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">3</span></span>'; 453 echo '<h2 class="adsa-step__title">' . esc_html__( 'Last step: confirm the account', 'addsynapse-store-assistant' ) . '</h2></div>'; 452 echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">2</span></span>'; 453 // The whole site address, with its folder (owner 24/10: «demo.addsynapse.com/satest», not just the host). 454 $host = untrailingslashit( (string) preg_replace( '#^https?://#i', '', home_url() ) ); 455 /* translators: %s: this site's host name, e.g. shop.example.com */ 456 echo '<h2 class="adsa-step__title">' . esc_html( sprintf( __( 'Connect %s to your AddSynapse account', 'addsynapse-store-assistant' ), $host ) ) . '</h2></div>'; 454 457 echo '<div class="adsa-step__body">'; 455 echo '<p>' . ( 'key' === ( $stash['mode'] ?? '' ) 456 ? esc_html__( 'AddSynapse asks to connect this site. Connect it to this AddSynapse account?', 'addsynapse-store-assistant' ) 457 : esc_html__( 'WordPress approved the connection. Connect this site to this AddSynapse account?', 'addsynapse-store-assistant' ) ) . '</p>'; 458 // 1.3.5 (owner 24/10): plain words — what happens, whose account, nothing changes until approved. 459 echo '<p class="adsa-relayconfirm__lead">' . esc_html( 460 'site' === \Addsynapse\StoreAssistant\Environment::site_type() 461 ? __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your site and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' ) 462 : __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your store and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' ) 463 ) . '</p>'; 458 464 echo '<div class="adsa-relayconfirm__account">'; 459 465 if ( '' !== $account || '' !== $email ) { … … 476 482 ) . '</p>'; 477 483 } 478 echo '<p class="adsa-relayconfirm__why ">' . esc_html__( 'We ask so nobody can link your site to their own account. Continue only if this is your account and you started this from your AI chat a moment ago.', 'addsynapse-store-assistant' ) . '</p>';484 echo '<p class="adsa-relayconfirm__why description">' . esc_html__( 'Only continue if this is your account. Didn\'t start this? Press Cancel.', 'addsynapse-store-assistant' ) . '</p>'; 479 485 if ( 'key' === ( $stash['mode'] ?? '' ) ) { 480 echo '<p class="adsa-relayconfirm__scope " data-adsa-relay-key>' . esc_html__( 'Connect creates a key for AddSynapse that works only for this plugin\'s tools, acting as you. Changes still wait for your Approve. You can disconnect at any time from this page.', 'addsynapse-store-assistant' ) . '</p>';486 echo '<p class="adsa-relayconfirm__scope description" data-adsa-relay-key>' . esc_html__( 'This creates a key that only works for this plugin\'s tools. You can disconnect any time here.', 'addsynapse-store-assistant' ) . '</p>'; 481 487 } 482 488 echo '<form method="post" class="adsa-finish adsa-relayconfirm__form" action="' . esc_url( … … 491 497 ) . '">'; 492 498 wp_nonce_field( 'addsynapse_assistant_relay_confirm_' . $state ); 493 echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( ' Connect', 'addsynapse-store-assistant' ) . '</button>';499 echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Yes, connect', 'addsynapse-store-assistant' ) . '</button>'; 494 500 echo '<button type="submit" name="as_cancel" value="1" class="button button-secondary">' . esc_html__( 'Cancel', 'addsynapse-store-assistant' ) . '</button>'; 495 501 echo '</form>'; … … 689 695 'protocol' => self::PROTOCOL, 690 696 ), 691 15697 40 692 698 ); 693 699 unset( $password ); … … 824 830 'protocol' => self::PROTOCOL, 825 831 ), 826 15 832 // AddSynapse checks the new key against this site before it answers (its own call can take ~15 s). 833 40 827 834 ); 828 835 unset( $key['secret'] ); -
addsynapse-store-assistant/trunk/includes/Remote/RelayKeys.php
r3759648 r3763701 86 86 /** 87 87 * Whether keys may be created/used here: HTTPS, or a `local` development environment (same rule as Application Passwords). 88 * 1.3.5: HTTPS also when PHP sits behind a TLS-terminating proxy that does not set $_SERVER['HTTPS'] (Varnish/nginx on 89 * CloudPanel): the site's own address is https://, or a proxy on this same machine says so (X-Forwarded-Proto / 90 * X-Forwarded-Ssl, trusted only from a loopback address). 88 91 */ 89 92 public static function transport_ok(): bool { 90 return is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ); 93 if ( is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) ) { 94 return true; 95 } 96 if ( 'https' === strtolower( (string) wp_parse_url( home_url(), PHP_URL_SCHEME ) ) ) { 97 return true; 98 } 99 $remote = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; 100 if ( ! in_array( $remote, array( '127.0.0.1', '::1' ), true ) ) { 101 return false; 102 } 103 $proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) ) : ''; 104 $ssl = isset( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ) ) : ''; 105 return 'https' === $proto || 'on' === $ssl; 91 106 } 92 107 -
addsynapse-store-assistant/trunk/includes/Remote/SetupStatus.php
r3759648 r3763701 166 166 $check = get_option( self::CHECK_OPTION, array() ); 167 167 return array( 168 'checked' => is_array( $check ) && ! empty( $check['ok'] ), 169 'connected' => null !== $found, 170 'used' => null !== $found && null !== $found['last_used'], 168 'checked' => is_array( $check ) && ! empty( $check['ok'] ), 169 // A real check ran and failed (the only case step 1 may stay open on a connected site). 170 'check_failed' => is_array( $check ) && array_key_exists( 'ok', $check ) && empty( $check['ok'] ), 171 'connected' => null !== $found, 172 'used' => null !== $found && null !== $found['last_used'], 171 173 // Connected for sure: AddSynapse confirmed this password (claim) or already used it. The wizard then shows every step done. 172 'claimed' => $claimed,173 'ready' => null !== $found && ( $claimed || null !== $found['last_used'] ),174 'detail' => $found,175 'log_count' => count( ApprovalLog::all() ),174 'claimed' => $claimed, 175 'ready' => null !== $found && ( $claimed || null !== $found['last_used'] ), 176 'detail' => $found, 177 'log_count' => count( ApprovalLog::all() ), 176 178 ); 177 179 } -
addsynapse-store-assistant/trunk/package.json
r3759648 r3763701 1 1 { 2 2 "name": "addsynapse-store-assistant", 3 "version": "1.3. 4",3 "version": "1.3.5", 4 4 "private": true, 5 5 "description": "Admin chat UI of the AddSynapse Store Assistant WordPress plugin.", -
addsynapse-store-assistant/trunk/readme.txt
r3759648 r3763701 7 7 WC requires at least: 8.0 8 8 WC tested up to: 11.1 9 Stable tag: 1.3. 49 Stable tag: 1.3.5 10 10 License: GPLv2 or later 11 11 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 172 172 == External services == 173 173 174 The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them.174 The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** (except the section 5 setup question) and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them. 175 175 176 176 = 1. Anthropic API — api.anthropic.com (only with your own Anthropic key) = … … 205 205 * AddSynapse privacy policy: https://addsynapse.com/privacy 206 206 207 = 5. AddSynapse for Claude and other AI apps — addsynapse.com ( only after you approve the connection) =208 209 Optional: use your site from Claude or ChatGPT (beta) (AddSynapse → Home → "Use your store from Claude or ChatGPT"). The setup steps only check your own site and show links and text to copy; they send nothing to AddSynapse. The "Add to Claude" link opens app.addsynapse.com with this site's address in the link (so AddSynapse knows which site to check when you sign up), which then opens Claude's connector page; your browser opens it only when you click it.207 = 5. AddSynapse for Claude and other AI apps — addsynapse.com (after you approve the connection; one setup question before) = 208 209 Optional: use your site from Claude or ChatGPT. "Connect to AddSynapse" opens app.addsynapse.com with this site's address in the link. While nothing is set up, for an administrator (after activation or on the setup screen, at most every 10 minutes), the plugin asks whether you started in Claude: `GET https://api.addsynapse.com/api/onboarding/pending?site=<site address>` (site address only). If yes, the browser continues there; nothing connects without Yes, connect. 210 210 211 211 You start the connection from your AI chat. It opens this plugin's page on your site. For a logged-in administrator only, that page asks AddSynapse which account started the connection (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim/preview`: the one-time code and site address; answer: account name and masked email) and asks you to confirm it. Only if you press Connect does the plugin create a random connection key for AddSynapse, acting as you, and send it once from your server over HTTPS (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim`: key, user name, site address, one-time code — never in a web address); Cancel creates nothing. The site keeps only a keyed hash of the key. A key AddSynapse does not use within 15 minutes is revoked. Sites connected with an earlier version keep their WordPress Application Password until they reconnect. … … 226 226 227 227 == Changelog == 228 229 = 1.3.5 = 230 * Improved: simpler setup. Step 2 is one «Connect to AddSynapse» button that links this site to your AddSynapse account; adding AddSynapse to Claude or ChatGPT moves to step 3, shown after the site is connected. 231 * Improved: started in Claude? Right after activation the plugin asks AddSynapse once (site address only, see External services) and goes straight to «Connect <site> to your AddSynapse account? Yes, connect», then «Done — go back to Claude». «Other options» keeps both ways one click away. 232 * Improved: setup has two steps (the connection check runs by itself) and Home opens with «You're all set!»: open Claude and three requests to try. 233 * Fixed: sites behind an HTTPS proxy (PHP sees plain HTTP) can connect; a failed request to AddSynapse now says what failed. 228 234 229 235 = 1.3.4 = … … 307 313 == Upgrade Notice == 308 314 315 = 1.3.5 = 316 Simpler setup: one «Connect to AddSynapse» button links the site to your account; started in Claude, you land right on «Yes, connect». 317 309 318 = 1.3.4 = 310 319 Connect without WordPress Application Passwords, with a key only this plugin accepts. Coupons above 50% off are approved only in wp-admin. Includes 1.3.3: change your site's appearance from Claude or ChatGPT, with Approve and Undo. -
addsynapse-store-assistant/trunk/uninstall.php
r3759648 r3763701 88 88 // 1.3.0: the welcome wizard (finished / skipped, chosen way). 89 89 'addsynapse_assistant_onboarding', 90 // 1.3.5: Home's «You're all set!» panel (first shown, hidden). 91 'addsynapse_assistant_allset', 90 92 // 1.3.0: the AddSynapse connection AddSynapse confirmed (uuid of the password, user, time — never the password). 91 93 'addsynapse_assistant_relay_claimed',
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)