@relvehq/agent-ready 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +24 -3
  2. package/SETUP.md +354 -354
  3. package/dist/core/report.d.ts +9 -3
  4. package/dist/core/report.js +30 -10
  5. package/dist/core/version.d.ts +1 -0
  6. package/dist/core/version.js +8 -0
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.js +5 -1
  9. package/dist/refine/check.d.ts +3 -1
  10. package/dist/refine/check.js +43 -0
  11. package/dist/refine/remote.d.ts +3 -2
  12. package/dist/refine/remote.js +3 -2
  13. package/dist/wp/relve-agent-ready/includes/class-relve-ar-admin.php +560 -550
  14. package/dist/wp/relve-agent-ready/includes/class-relve-ar-converter.php +762 -762
  15. package/dist/wp/relve-agent-ready/includes/class-relve-ar-llms.php +192 -192
  16. package/dist/wp/relve-agent-ready/includes/class-relve-ar-markdown-builder.php +455 -455
  17. package/dist/wp/relve-agent-ready/includes/class-relve-ar-negotiation.php +334 -334
  18. package/dist/wp/relve-agent-ready/includes/class-relve-ar-refiner.php +894 -894
  19. package/dist/wp/relve-agent-ready/includes/class-relve-ar-reporter.php +147 -0
  20. package/dist/wp/relve-agent-ready/includes/class-relve-ar-response.php +226 -226
  21. package/dist/wp/relve-agent-ready/includes/class-relve-ar-router.php +422 -421
  22. package/dist/wp/relve-agent-ready/includes/class-relve-ar-settings.php +210 -201
  23. package/dist/wp/relve-agent-ready/includes/class-relve-ar-store.php +583 -583
  24. package/dist/wp/relve-agent-ready/includes/index.php +2 -2
  25. package/dist/wp/relve-agent-ready/index.php +2 -2
  26. package/dist/wp/relve-agent-ready/readme.txt +119 -108
  27. package/dist/wp/relve-agent-ready/relve-agent-ready.php +82 -81
  28. package/dist/wp/relve-agent-ready/uninstall.php +35 -35
  29. package/package.json +1 -1
@@ -1,201 +1,210 @@
1
- <?php
2
- /**
3
- * Settings: one option array, `relve_agent_ready`.
4
- *
5
- * enabled bool default false (installing changes nothing)
6
- * post_types string[] default post, page
7
- * excluded_paths string one path prefix per line
8
- * llms_mode string auto|on|off, default auto
9
- * openai_key string the site's own OpenAI key (stored here only, never
10
- * shown in full; sent only to the Relve refine endpoint)
11
- * refine_model string gpt-4o-mini (default), gpt-4o, gpt-4.1, gpt-4.1-mini
12
- * refine_on_save bool default true (only acts once a key is saved)
13
- *
14
- * The settings form never posts `openai_key`; it posts `openai_key_new` (a
15
- * replacement, empty = keep) and `openai_key_remove`. A submission without
16
- * either keeps the saved key.
17
- *
18
- * @package Relve_Agent_Ready
19
- */
20
-
21
- if ( ! defined( 'ABSPATH' ) ) {
22
- exit;
23
- }
24
-
25
- /**
26
- * Settings access and sanitising.
27
- */
28
- class Relve_AR_Settings {
29
-
30
- const OPTION = 'relve_agent_ready';
31
-
32
- /** Paths never worth serving as Markdown (segment-aware prefixes). */
33
- const DEFAULT_EXCLUDED = "/wp-admin\n/wp-login.php\n/wp-json\n/wp-content\n/wp-includes\n/feed\n/cart\n/checkout\n/my-account\n/account";
34
-
35
- /** Models the Relve refine endpoint accepts (contract.md). */
36
- const MODELS = array( 'gpt-4o-mini', 'gpt-4o', 'gpt-4.1', 'gpt-4.1-mini' );
37
-
38
- /** Default model: fast enough for the endpoint's ~30 s request limit. */
39
- const DEFAULT_MODEL = 'gpt-4o-mini';
40
-
41
- /**
42
- * Defaults.
43
- *
44
- * @return array
45
- */
46
- public static function defaults() {
47
- return array(
48
- 'enabled' => false,
49
- 'post_types' => array( 'post', 'page' ),
50
- 'excluded_paths' => self::DEFAULT_EXCLUDED,
51
- 'llms_mode' => 'auto',
52
- 'openai_key' => '',
53
- 'refine_model' => self::DEFAULT_MODEL,
54
- 'refine_on_save' => true,
55
- );
56
- }
57
-
58
- /**
59
- * Masked key for display: "sk-...abcd". Never the full key.
60
- *
61
- * @param string $key Key.
62
- * @return string
63
- */
64
- public static function mask_key( $key ) {
65
- $key = (string) $key;
66
- if ( '' === $key ) {
67
- return '';
68
- }
69
- if ( strlen( $key ) < 12 ) {
70
- return '****';
71
- }
72
- return substr( $key, 0, 3 ) . '...' . substr( $key, -4 );
73
- }
74
-
75
- /**
76
- * A cleaned key, or null when it does not look like an OpenAI key.
77
- *
78
- * @param string $raw Raw input.
79
- * @return string|null
80
- */
81
- public static function clean_key( $raw ) {
82
- $key = preg_replace( '/\s+/', '', (string) $raw );
83
- if ( null === $key || ! preg_match( '/^[A-Za-z0-9_\-]{20,300}$/', $key ) ) {
84
- return null;
85
- }
86
- return $key;
87
- }
88
-
89
- /**
90
- * Current settings merged over the defaults, plus "excluded" as a list.
91
- *
92
- * @return array
93
- */
94
- public static function get() {
95
- $stored = get_option( self::OPTION, array() );
96
- $s = array_merge( self::defaults(), is_array( $stored ) ? $stored : array() );
97
- $s['enabled'] = (bool) $s['enabled'];
98
- $s['post_types'] = array_values( array_filter( (array) $s['post_types'], 'is_string' ) );
99
- $s['excluded'] = self::lines( $s['excluded_paths'] );
100
- $s['openai_key'] = is_string( $s['openai_key'] ) ? $s['openai_key'] : '';
101
- $s['refine_model'] = in_array( $s['refine_model'], self::MODELS, true ) ? $s['refine_model'] : self::DEFAULT_MODEL;
102
- $s['refine_on_save'] = (bool) $s['refine_on_save'];
103
- return $s;
104
- }
105
-
106
- /**
107
- * Split the excluded-paths text into prefixes.
108
- *
109
- * @param string $text Text.
110
- * @return string[]
111
- */
112
- public static function lines( $text ) {
113
- $out = array();
114
- foreach ( preg_split( '/\r\n|\r|\n/', (string) $text ) as $line ) {
115
- $line = trim( $line );
116
- if ( '' !== $line ) {
117
- $out[] = $line;
118
- }
119
- }
120
- return $out;
121
- }
122
-
123
- /**
124
- * Sanitise a submitted settings array (register_setting callback).
125
- *
126
- * @param mixed $input Raw input.
127
- * @return array
128
- */
129
- public static function sanitize( $input ) {
130
- $input = is_array( $input ) ? $input : array();
131
- $out = self::defaults();
132
-
133
- $out['enabled'] = ! empty( $input['enabled'] );
134
-
135
- $public = function_exists( 'get_post_types' ) ? get_post_types( array( 'public' => true ) ) : array( 'post' => 'post', 'page' => 'page' );
136
- $types = array();
137
- foreach ( (array) ( isset( $input['post_types'] ) ? $input['post_types'] : array() ) as $t ) {
138
- $t = sanitize_key( (string) $t );
139
- if ( 'attachment' !== $t && isset( $public[ $t ] ) ) {
140
- $types[] = $t;
141
- }
142
- }
143
- $out['post_types'] = array_values( array_unique( $types ) );
144
-
145
- $paths = array();
146
- foreach ( self::lines( isset( $input['excluded_paths'] ) ? (string) $input['excluded_paths'] : '' ) as $line ) {
147
- $line = sanitize_text_field( $line );
148
- $line = preg_replace( '#[^A-Za-z0-9/_.~%@!$&\'()*+,;=:-]#', '', $line );
149
- if ( '' === $line ) {
150
- continue;
151
- }
152
- if ( '/' !== $line[0] ) {
153
- $line = '/' . $line;
154
- }
155
- $paths[] = $line;
156
- if ( count( $paths ) >= 200 ) {
157
- break;
158
- }
159
- }
160
- $out['excluded_paths'] = implode( "\n", array_unique( $paths ) );
161
-
162
- $mode = isset( $input['llms_mode'] ) ? (string) $input['llms_mode'] : 'auto';
163
- $out['llms_mode'] = in_array( $mode, array( 'auto', 'on', 'off' ), true ) ? $mode : 'auto';
164
-
165
- // OpenAI key: replace, remove, or keep. WordPress may run this callback
166
- // a second time on its own output (first save), which carries
167
- // `openai_key` itself; that value is re-validated and kept.
168
- $stored = get_option( self::OPTION, array() );
169
- $current = ( is_array( $stored ) && isset( $stored['openai_key'] ) && is_string( $stored['openai_key'] ) ) ? $stored['openai_key'] : '';
170
- $new = isset( $input['openai_key_new'] ) ? trim( (string) $input['openai_key_new'] ) : '';
171
- $changed = false;
172
- if ( '' !== $new ) {
173
- $clean = self::clean_key( $new );
174
- if ( null === $clean ) {
175
- $out['openai_key'] = $current;
176
- if ( function_exists( 'add_settings_error' ) ) {
177
- add_settings_error( self::OPTION, 'relve_ar_bad_key', __( 'That does not look like an OpenAI API key. The saved key was kept.', 'relve-agent-ready' ) );
178
- }
179
- } else {
180
- $out['openai_key'] = $clean;
181
- $changed = $clean !== $current;
182
- }
183
- } elseif ( ! empty( $input['openai_key_remove'] ) ) {
184
- $out['openai_key'] = '';
185
- $changed = '' !== $current;
186
- } elseif ( isset( $input['openai_key'] ) ) {
187
- $clean = self::clean_key( (string) $input['openai_key'] );
188
- $out['openai_key'] = null === $clean ? $current : $clean;
189
- } else {
190
- $out['openai_key'] = $current;
191
- }
192
- if ( $changed && class_exists( 'Relve_AR_Refiner' ) ) {
193
- Relve_AR_Refiner::key_changed( '' !== $out['openai_key'] );
194
- }
195
-
196
- $model = isset( $input['refine_model'] ) ? (string) $input['refine_model'] : self::DEFAULT_MODEL;
197
- $out['refine_model'] = in_array( $model, self::MODELS, true ) ? $model : self::DEFAULT_MODEL;
198
- $out['refine_on_save'] = ! empty( $input['refine_on_save'] );
199
- return $out;
200
- }
201
- }
1
+ <?php
2
+ /**
3
+ * Settings: one option array, `relve_agent_ready`.
4
+ *
5
+ * enabled bool default false (installing changes nothing)
6
+ * post_types string[] default post, page
7
+ * excluded_paths string one path prefix per line
8
+ * llms_mode string auto|on|off, default auto
9
+ * openai_key string the site's own OpenAI key (stored here only, never
10
+ * shown in full; sent only to the Relve refine endpoint)
11
+ * refine_model string gpt-4o (default), gpt-4.1, gpt-4.1-mini, gpt-4o-mini
12
+ * refine_on_save bool default true (only acts once a key is saved)
13
+ * share_stats bool default false: send each agent visit's stats
14
+ * (path, bot, tokens, bytes; never the Markdown or
15
+ * the visitor's IP) to Relve (Relve_AR_Reporter)
16
+ *
17
+ * The settings form never posts `openai_key`; it posts `openai_key_new` (a
18
+ * replacement, empty = keep) and `openai_key_remove`. A submission without
19
+ * either keeps the saved key.
20
+ *
21
+ * @package Relve_Agent_Ready
22
+ */
23
+
24
+ if ( ! defined( 'ABSPATH' ) ) {
25
+ exit;
26
+ }
27
+
28
+ /**
29
+ * Settings access and sanitising.
30
+ */
31
+ class Relve_AR_Settings {
32
+
33
+ const OPTION = 'relve_agent_ready';
34
+
35
+ /** Paths never worth serving as Markdown (segment-aware prefixes). */
36
+ const DEFAULT_EXCLUDED = "/wp-admin\n/wp-login.php\n/wp-json\n/wp-content\n/wp-includes\n/feed\n/cart\n/checkout\n/my-account\n/account";
37
+
38
+ /** Models the Relve refine endpoint accepts (contract.md). */
39
+ const MODELS = array( 'gpt-4o', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4o-mini' );
40
+
41
+ /**
42
+ * Default model. Was gpt-4o-mini; on 2026-10-29 it gave example cards'
43
+ * prices to the page itself, gpt-4o did not and was as fast.
44
+ */
45
+ const DEFAULT_MODEL = 'gpt-4o';
46
+
47
+ /**
48
+ * Defaults.
49
+ *
50
+ * @return array
51
+ */
52
+ public static function defaults() {
53
+ return array(
54
+ 'enabled' => false,
55
+ 'post_types' => array( 'post', 'page' ),
56
+ 'excluded_paths' => self::DEFAULT_EXCLUDED,
57
+ 'llms_mode' => 'auto',
58
+ 'openai_key' => '',
59
+ 'refine_model' => self::DEFAULT_MODEL,
60
+ 'refine_on_save' => true,
61
+ 'share_stats' => false,
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Masked key for display: "sk-...abcd". Never the full key.
67
+ *
68
+ * @param string $key Key.
69
+ * @return string
70
+ */
71
+ public static function mask_key( $key ) {
72
+ $key = (string) $key;
73
+ if ( '' === $key ) {
74
+ return '';
75
+ }
76
+ if ( strlen( $key ) < 12 ) {
77
+ return '****';
78
+ }
79
+ return substr( $key, 0, 3 ) . '...' . substr( $key, -4 );
80
+ }
81
+
82
+ /**
83
+ * A cleaned key, or null when it does not look like an OpenAI key.
84
+ *
85
+ * @param string $raw Raw input.
86
+ * @return string|null
87
+ */
88
+ public static function clean_key( $raw ) {
89
+ $key = preg_replace( '/\s+/', '', (string) $raw );
90
+ if ( null === $key || ! preg_match( '/^[A-Za-z0-9_\-]{20,300}$/', $key ) ) {
91
+ return null;
92
+ }
93
+ return $key;
94
+ }
95
+
96
+ /**
97
+ * Current settings merged over the defaults, plus "excluded" as a list.
98
+ *
99
+ * @return array
100
+ */
101
+ public static function get() {
102
+ $stored = get_option( self::OPTION, array() );
103
+ $s = array_merge( self::defaults(), is_array( $stored ) ? $stored : array() );
104
+ $s['enabled'] = (bool) $s['enabled'];
105
+ $s['post_types'] = array_values( array_filter( (array) $s['post_types'], 'is_string' ) );
106
+ $s['excluded'] = self::lines( $s['excluded_paths'] );
107
+ $s['openai_key'] = is_string( $s['openai_key'] ) ? $s['openai_key'] : '';
108
+ $s['refine_model'] = in_array( $s['refine_model'], self::MODELS, true ) ? $s['refine_model'] : self::DEFAULT_MODEL;
109
+ $s['refine_on_save'] = (bool) $s['refine_on_save'];
110
+ $s['share_stats'] = (bool) $s['share_stats'];
111
+ return $s;
112
+ }
113
+
114
+ /**
115
+ * Split the excluded-paths text into prefixes.
116
+ *
117
+ * @param string $text Text.
118
+ * @return string[]
119
+ */
120
+ public static function lines( $text ) {
121
+ $out = array();
122
+ foreach ( preg_split( '/\r\n|\r|\n/', (string) $text ) as $line ) {
123
+ $line = trim( $line );
124
+ if ( '' !== $line ) {
125
+ $out[] = $line;
126
+ }
127
+ }
128
+ return $out;
129
+ }
130
+
131
+ /**
132
+ * Sanitise a submitted settings array (register_setting callback).
133
+ *
134
+ * @param mixed $input Raw input.
135
+ * @return array
136
+ */
137
+ public static function sanitize( $input ) {
138
+ $input = is_array( $input ) ? $input : array();
139
+ $out = self::defaults();
140
+
141
+ $out['enabled'] = ! empty( $input['enabled'] );
142
+
143
+ $public = function_exists( 'get_post_types' ) ? get_post_types( array( 'public' => true ) ) : array( 'post' => 'post', 'page' => 'page' );
144
+ $types = array();
145
+ foreach ( (array) ( isset( $input['post_types'] ) ? $input['post_types'] : array() ) as $t ) {
146
+ $t = sanitize_key( (string) $t );
147
+ if ( 'attachment' !== $t && isset( $public[ $t ] ) ) {
148
+ $types[] = $t;
149
+ }
150
+ }
151
+ $out['post_types'] = array_values( array_unique( $types ) );
152
+
153
+ $paths = array();
154
+ foreach ( self::lines( isset( $input['excluded_paths'] ) ? (string) $input['excluded_paths'] : '' ) as $line ) {
155
+ $line = sanitize_text_field( $line );
156
+ $line = preg_replace( '#[^A-Za-z0-9/_.~%@!$&\'()*+,;=:-]#', '', $line );
157
+ if ( '' === $line ) {
158
+ continue;
159
+ }
160
+ if ( '/' !== $line[0] ) {
161
+ $line = '/' . $line;
162
+ }
163
+ $paths[] = $line;
164
+ if ( count( $paths ) >= 200 ) {
165
+ break;
166
+ }
167
+ }
168
+ $out['excluded_paths'] = implode( "\n", array_unique( $paths ) );
169
+
170
+ $mode = isset( $input['llms_mode'] ) ? (string) $input['llms_mode'] : 'auto';
171
+ $out['llms_mode'] = in_array( $mode, array( 'auto', 'on', 'off' ), true ) ? $mode : 'auto';
172
+
173
+ // OpenAI key: replace, remove, or keep. WordPress may run this callback
174
+ // a second time on its own output (first save), which carries
175
+ // `openai_key` itself; that value is re-validated and kept.
176
+ $stored = get_option( self::OPTION, array() );
177
+ $current = ( is_array( $stored ) && isset( $stored['openai_key'] ) && is_string( $stored['openai_key'] ) ) ? $stored['openai_key'] : '';
178
+ $new = isset( $input['openai_key_new'] ) ? trim( (string) $input['openai_key_new'] ) : '';
179
+ $changed = false;
180
+ if ( '' !== $new ) {
181
+ $clean = self::clean_key( $new );
182
+ if ( null === $clean ) {
183
+ $out['openai_key'] = $current;
184
+ if ( function_exists( 'add_settings_error' ) ) {
185
+ add_settings_error( self::OPTION, 'relve_ar_bad_key', __( 'That does not look like an OpenAI API key. The saved key was kept.', 'relve-agent-ready' ) );
186
+ }
187
+ } else {
188
+ $out['openai_key'] = $clean;
189
+ $changed = $clean !== $current;
190
+ }
191
+ } elseif ( ! empty( $input['openai_key_remove'] ) ) {
192
+ $out['openai_key'] = '';
193
+ $changed = '' !== $current;
194
+ } elseif ( isset( $input['openai_key'] ) ) {
195
+ $clean = self::clean_key( (string) $input['openai_key'] );
196
+ $out['openai_key'] = null === $clean ? $current : $clean;
197
+ } else {
198
+ $out['openai_key'] = $current;
199
+ }
200
+ if ( $changed && class_exists( 'Relve_AR_Refiner' ) ) {
201
+ Relve_AR_Refiner::key_changed( '' !== $out['openai_key'] );
202
+ }
203
+
204
+ $model = isset( $input['refine_model'] ) ? (string) $input['refine_model'] : self::DEFAULT_MODEL;
205
+ $out['refine_model'] = in_array( $model, self::MODELS, true ) ? $model : self::DEFAULT_MODEL;
206
+ $out['refine_on_save'] = ! empty( $input['refine_on_save'] );
207
+ $out['share_stats'] = ! empty( $input['share_stats'] );
208
+ return $out;
209
+ }
210
+ }