@relvehq/agent-ready 0.0.0-stage → 0.2.1

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -3
  3. package/SETUP.md +337 -0
  4. package/dist/bin/agent-ready.d.ts +23 -0
  5. package/dist/bin/agent-ready.js +350 -0
  6. package/dist/cli/init.d.ts +70 -0
  7. package/dist/cli/init.js +366 -0
  8. package/dist/core/accept.d.ts +8 -0
  9. package/dist/core/accept.js +76 -0
  10. package/dist/core/config.d.ts +145 -0
  11. package/dist/core/config.js +103 -0
  12. package/dist/core/convert.d.ts +22 -0
  13. package/dist/core/convert.js +334 -0
  14. package/dist/core/detect.d.ts +30 -0
  15. package/dist/core/detect.js +82 -0
  16. package/dist/core/easy.d.ts +19 -0
  17. package/dist/core/easy.js +117 -0
  18. package/dist/core/enrich.d.ts +30 -0
  19. package/dist/core/enrich.js +155 -0
  20. package/dist/core/llms.d.ts +11 -0
  21. package/dist/core/llms.js +71 -0
  22. package/dist/core/report.d.ts +15 -0
  23. package/dist/core/report.js +46 -0
  24. package/dist/core/resolve.d.ts +43 -0
  25. package/dist/core/resolve.js +204 -0
  26. package/dist/core/respond.d.ts +18 -0
  27. package/dist/core/respond.js +59 -0
  28. package/dist/core/sitemap.d.ts +31 -0
  29. package/dist/core/sitemap.js +140 -0
  30. package/dist/index.d.ts +14 -0
  31. package/dist/index.js +76 -0
  32. package/dist/next/dashboard.d.ts +14 -0
  33. package/dist/next/dashboard.js +56 -0
  34. package/dist/next/index.d.ts +42 -0
  35. package/dist/next/index.js +100 -0
  36. package/dist/next/route.d.ts +26 -0
  37. package/dist/next/route.js +167 -0
  38. package/dist/node/index.d.ts +8 -0
  39. package/dist/node/index.js +191 -0
  40. package/dist/proof/audit.d.ts +47 -0
  41. package/dist/proof/audit.js +253 -0
  42. package/dist/proof/bots.d.ts +24 -0
  43. package/dist/proof/bots.js +60 -0
  44. package/dist/proof/dashboard.d.ts +8 -0
  45. package/dist/proof/dashboard.js +77 -0
  46. package/dist/proof/hits.d.ts +18 -0
  47. package/dist/proof/hits.js +51 -0
  48. package/dist/proof/jsonl.d.ts +5 -0
  49. package/dist/proof/jsonl.js +80 -0
  50. package/dist/proof/stats.d.ts +39 -0
  51. package/dist/proof/stats.js +127 -0
  52. package/dist/refine/check.d.ts +17 -0
  53. package/dist/refine/check.js +134 -0
  54. package/dist/refine/extract.d.ts +7 -0
  55. package/dist/refine/extract.js +120 -0
  56. package/dist/refine/file-store.d.ts +6 -0
  57. package/dist/refine/file-store.js +87 -0
  58. package/dist/refine/model.d.ts +30 -0
  59. package/dist/refine/model.js +105 -0
  60. package/dist/refine/prompts.d.ts +49 -0
  61. package/dist/refine/prompts.js +178 -0
  62. package/dist/refine/refine.d.ts +38 -0
  63. package/dist/refine/refine.js +75 -0
  64. package/dist/refine/run.d.ts +52 -0
  65. package/dist/refine/run.js +66 -0
  66. package/dist/refine/types.d.ts +17 -0
  67. package/dist/refine/types.js +8 -0
  68. package/dist/wp/relve-agent-ready/includes/class-relve-ar-admin.php +363 -0
  69. package/dist/wp/relve-agent-ready/includes/class-relve-ar-converter.php +762 -0
  70. package/dist/wp/relve-agent-ready/includes/class-relve-ar-llms.php +192 -0
  71. package/dist/wp/relve-agent-ready/includes/class-relve-ar-markdown-builder.php +447 -0
  72. package/dist/wp/relve-agent-ready/includes/class-relve-ar-negotiation.php +334 -0
  73. package/dist/wp/relve-agent-ready/includes/class-relve-ar-response.php +226 -0
  74. package/dist/wp/relve-agent-ready/includes/class-relve-ar-router.php +421 -0
  75. package/dist/wp/relve-agent-ready/includes/class-relve-ar-settings.php +115 -0
  76. package/dist/wp/relve-agent-ready/includes/class-relve-ar-store.php +543 -0
  77. package/dist/wp/relve-agent-ready/includes/index.php +2 -0
  78. package/dist/wp/relve-agent-ready/index.php +2 -0
  79. package/dist/wp/relve-agent-ready/readme.txt +75 -0
  80. package/dist/wp/relve-agent-ready/relve-agent-ready.php +70 -0
  81. package/dist/wp/relve-agent-ready/uninstall.php +24 -0
  82. package/package.json +64 -6
@@ -0,0 +1,363 @@
1
+ <?php
2
+ /**
3
+ * Admin: Settings -> Agent Ready.
4
+ *
5
+ * Settings form (Settings API, sanitised by Relve_AR_Settings::sanitize), a
6
+ * status panel with this site's three test commands, and the refined-files
7
+ * upload / clear actions (admin-post.php, each checking manage_options and
8
+ * its own nonce). All output is escaped.
9
+ *
10
+ * @package Relve_Agent_Ready
11
+ */
12
+
13
+ if ( ! defined( 'ABSPATH' ) ) {
14
+ exit;
15
+ }
16
+
17
+ /**
18
+ * Settings page.
19
+ */
20
+ class Relve_AR_Admin {
21
+
22
+ const PAGE = 'relve-agent-ready';
23
+ const GROUP = 'relve_agent_ready_group';
24
+ const NOTICE = 'relve_ar_notice_';
25
+
26
+ /**
27
+ * Register hooks.
28
+ *
29
+ * @return void
30
+ */
31
+ public static function init() {
32
+ add_action( 'admin_menu', array( __CLASS__, 'menu' ) );
33
+ add_action( 'admin_init', array( __CLASS__, 'register' ) );
34
+ add_action( 'admin_post_relve_ar_upload', array( __CLASS__, 'handle_upload' ) );
35
+ add_action( 'admin_post_relve_ar_clear', array( __CLASS__, 'handle_clear' ) );
36
+ add_filter( 'plugin_action_links_' . plugin_basename( RELVE_AR_FILE ), array( __CLASS__, 'action_links' ) );
37
+ }
38
+
39
+ /**
40
+ * Settings link on the Plugins screen.
41
+ *
42
+ * @param array $links Links.
43
+ * @return array
44
+ */
45
+ public static function action_links( $links ) {
46
+ $url = admin_url( 'options-general.php?page=' . self::PAGE );
47
+ array_unshift( $links, '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Settings', 'relve-agent-ready' ) . '</a>' );
48
+ return $links;
49
+ }
50
+
51
+ /**
52
+ * Menu entry.
53
+ *
54
+ * @return void
55
+ */
56
+ public static function menu() {
57
+ add_options_page(
58
+ __( 'Agent Ready', 'relve-agent-ready' ),
59
+ __( 'Agent Ready', 'relve-agent-ready' ),
60
+ 'manage_options',
61
+ self::PAGE,
62
+ array( __CLASS__, 'render' )
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Register the option.
68
+ *
69
+ * @return void
70
+ */
71
+ public static function register() {
72
+ register_setting(
73
+ self::GROUP,
74
+ Relve_AR_Settings::OPTION,
75
+ array(
76
+ 'type' => 'array',
77
+ 'sanitize_callback' => array( 'Relve_AR_Settings', 'sanitize' ),
78
+ 'default' => Relve_AR_Settings::defaults(),
79
+ 'show_in_rest' => false,
80
+ )
81
+ );
82
+ }
83
+
84
+ // ── actions ───────────────────────────────────────────────────────────
85
+
86
+ /**
87
+ * Refined-files zip upload.
88
+ *
89
+ * @return void
90
+ */
91
+ public static function handle_upload() {
92
+ if ( ! current_user_can( 'manage_options' ) ) {
93
+ wp_die( esc_html__( 'You are not allowed to do this.', 'relve-agent-ready' ), 403 );
94
+ }
95
+ check_admin_referer( 'relve_ar_upload' );
96
+
97
+ $notice = array(
98
+ 'type' => 'error',
99
+ 'message' => __( 'No file was uploaded.', 'relve-agent-ready' ),
100
+ 'reasons' => array(),
101
+ );
102
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- file array; tmp_name checked with is_uploaded_file().
103
+ $file = isset( $_FILES['relve_ar_zip'] ) && is_array( $_FILES['relve_ar_zip'] ) ? $_FILES['relve_ar_zip'] : null;
104
+ if ( $file && isset( $file['error'], $file['tmp_name'], $file['name'] ) && UPLOAD_ERR_OK === (int) $file['error'] ) {
105
+ $name = sanitize_file_name( (string) $file['name'] );
106
+ if ( '.zip' !== strtolower( substr( $name, -4 ) ) ) {
107
+ $notice['message'] = __( 'Upload a .zip file.', 'relve-agent-ready' );
108
+ } elseif ( ! is_uploaded_file( $file['tmp_name'] ) ) {
109
+ $notice['message'] = __( 'The upload could not be verified.', 'relve-agent-ready' );
110
+ } else {
111
+ $store = new Relve_AR_Store();
112
+ $store->ensure();
113
+ $r = $store->import_zip( $file['tmp_name'] );
114
+ if ( $r['ok'] ) {
115
+ $notice['type'] = 'success';
116
+ /* translators: 1: files stored, 2: files skipped, 3: entries rejected. */
117
+ $notice['message'] = sprintf( __( 'Stored %1$d refined files (skipped %2$d other files, rejected %3$d entries).', 'relve-agent-ready' ), $r['stored'], $r['skipped'], $r['rejected'] );
118
+ } else {
119
+ $notice['message'] = $r['error'];
120
+ }
121
+ $notice['reasons'] = array_slice( $r['reasons'], 0, 20 );
122
+ }
123
+ } elseif ( $file && isset( $file['error'] ) && UPLOAD_ERR_NO_FILE !== (int) $file['error'] ) {
124
+ $notice['message'] = __( 'The upload failed (it may be larger than the server allows).', 'relve-agent-ready' );
125
+ }
126
+ self::redirect_with( $notice );
127
+ }
128
+
129
+ /**
130
+ * Clear refined files.
131
+ *
132
+ * @return void
133
+ */
134
+ public static function handle_clear() {
135
+ if ( ! current_user_can( 'manage_options' ) ) {
136
+ wp_die( esc_html__( 'You are not allowed to do this.', 'relve-agent-ready' ), 403 );
137
+ }
138
+ check_admin_referer( 'relve_ar_clear' );
139
+ $store = new Relve_AR_Store();
140
+ $store->clear();
141
+ self::redirect_with(
142
+ array(
143
+ 'type' => 'success',
144
+ 'message' => __( 'Refined files cleared.', 'relve-agent-ready' ),
145
+ 'reasons' => array(),
146
+ )
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Keep a notice for this user and go back to the settings page.
152
+ *
153
+ * @param array $notice Notice.
154
+ * @return void
155
+ */
156
+ private static function redirect_with( $notice ) {
157
+ set_transient( self::NOTICE . get_current_user_id(), $notice, 120 );
158
+ wp_safe_redirect( admin_url( 'options-general.php?page=' . self::PAGE ) );
159
+ exit;
160
+ }
161
+
162
+ // ── page ──────────────────────────────────────────────────────────────
163
+
164
+ /**
165
+ * Render the page.
166
+ *
167
+ * @return void
168
+ */
169
+ public static function render() {
170
+ if ( ! current_user_can( 'manage_options' ) ) {
171
+ return;
172
+ }
173
+ $s = Relve_AR_Settings::get();
174
+ $store = new Relve_AR_Store();
175
+ $count = $store->count();
176
+ $key = self::NOTICE . get_current_user_id();
177
+ $note = get_transient( $key );
178
+ if ( $note ) {
179
+ delete_transient( $key );
180
+ }
181
+ $types = get_post_types( array( 'public' => true ), 'objects' );
182
+ $opt = Relve_AR_Settings::OPTION;
183
+ ?>
184
+ <div class="wrap">
185
+ <h1><?php esc_html_e( 'Agent Ready', 'relve-agent-ready' ); ?></h1>
186
+ <p><?php esc_html_e( 'Serves a Markdown version of your posts and pages to AI agents that ask for it (Accept: text/markdown, a .md URL or ?format=md), plus an llms.txt. Browsers always get the normal page.', 'relve-agent-ready' ); ?></p>
187
+
188
+ <?php if ( is_array( $note ) ) : ?>
189
+ <div class="notice notice-<?php echo esc_attr( 'success' === $note['type'] ? 'success' : 'error' ); ?> is-dismissible">
190
+ <p><?php echo esc_html( $note['message'] ); ?></p>
191
+ <?php if ( ! empty( $note['reasons'] ) ) : ?>
192
+ <ul style="list-style:disc;margin-left:2em">
193
+ <?php foreach ( $note['reasons'] as $reason ) : ?>
194
+ <li><code><?php echo esc_html( $reason ); ?></code></li>
195
+ <?php endforeach; ?>
196
+ </ul>
197
+ <?php endif; ?>
198
+ </div>
199
+ <?php endif; ?>
200
+
201
+ <?php self::render_status( $s, $count ); ?>
202
+
203
+ <h2><?php esc_html_e( 'Settings', 'relve-agent-ready' ); ?></h2>
204
+ <form method="post" action="<?php echo esc_url( admin_url( 'options.php' ) ); ?>">
205
+ <?php settings_fields( self::GROUP ); ?>
206
+ <table class="form-table" role="presentation">
207
+ <tr>
208
+ <th scope="row"><?php esc_html_e( 'Enabled', 'relve-agent-ready' ); ?></th>
209
+ <td>
210
+ <label>
211
+ <input type="checkbox" name="<?php echo esc_attr( $opt ); ?>[enabled]" value="1" <?php checked( $s['enabled'] ); ?> />
212
+ <?php esc_html_e( 'Serve Markdown and llms.txt. While unchecked, no request is changed.', 'relve-agent-ready' ); ?>
213
+ </label>
214
+ </td>
215
+ </tr>
216
+ <tr>
217
+ <th scope="row"><?php esc_html_e( 'Post types', 'relve-agent-ready' ); ?></th>
218
+ <td>
219
+ <?php foreach ( $types as $type ) : ?>
220
+ <?php
221
+ if ( 'attachment' === $type->name ) {
222
+ continue;
223
+ }
224
+ ?>
225
+ <label style="display:block">
226
+ <input type="checkbox" name="<?php echo esc_attr( $opt ); ?>[post_types][]" value="<?php echo esc_attr( $type->name ); ?>" <?php checked( in_array( $type->name, $s['post_types'], true ) ); ?> />
227
+ <?php echo esc_html( $type->labels->name ); ?> <code><?php echo esc_html( $type->name ); ?></code>
228
+ </label>
229
+ <?php endforeach; ?>
230
+ <p class="description"><?php esc_html_e( 'The front page and category, tag and author archives are served when Posts is enabled.', 'relve-agent-ready' ); ?></p>
231
+ </td>
232
+ </tr>
233
+ <tr>
234
+ <th scope="row"><label for="relve-ar-excluded"><?php esc_html_e( 'Excluded paths', 'relve-agent-ready' ); ?></label></th>
235
+ <td>
236
+ <textarea id="relve-ar-excluded" class="large-text code" rows="8" name="<?php echo esc_attr( $opt ); ?>[excluded_paths]"><?php echo esc_textarea( $s['excluded_paths'] ); ?></textarea>
237
+ <p class="description"><?php esc_html_e( 'One path per line. A path excludes itself and everything below it ("/cart" excludes /cart and /cart/x, not /cartoon). Matched against the full path and the path below your home URL.', 'relve-agent-ready' ); ?></p>
238
+ </td>
239
+ </tr>
240
+ <tr>
241
+ <th scope="row"><?php esc_html_e( 'llms.txt', 'relve-agent-ready' ); ?></th>
242
+ <td>
243
+ <?php
244
+ $modes = array(
245
+ 'auto' => __( 'Auto: serve it unless Yoast SEO (or a real llms.txt file) already does', 'relve-agent-ready' ),
246
+ 'on' => __( 'On: always serve it', 'relve-agent-ready' ),
247
+ 'off' => __( 'Off', 'relve-agent-ready' ),
248
+ );
249
+ foreach ( $modes as $value => $label ) :
250
+ ?>
251
+ <label style="display:block">
252
+ <input type="radio" name="<?php echo esc_attr( $opt ); ?>[llms_mode]" value="<?php echo esc_attr( $value ); ?>" <?php checked( $s['llms_mode'], $value ); ?> />
253
+ <?php echo esc_html( $label ); ?>
254
+ </label>
255
+ <?php endforeach; ?>
256
+ </td>
257
+ </tr>
258
+ </table>
259
+ <?php submit_button(); ?>
260
+ </form>
261
+
262
+ <h2><?php esc_html_e( 'Refined files from Relve', 'relve-agent-ready' ); ?></h2>
263
+ <p>
264
+ <?php
265
+ printf(
266
+ /* translators: %d: number of files. */
267
+ esc_html( _n( '%d refined file is stored. Refined files are served before the Markdown built from WordPress.', '%d refined files are stored. Refined files are served before the Markdown built from WordPress.', $count, 'relve-agent-ready' ) ),
268
+ (int) $count
269
+ );
270
+ ?>
271
+ </p>
272
+ <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data">
273
+ <input type="hidden" name="action" value="relve_ar_upload" />
274
+ <?php wp_nonce_field( 'relve_ar_upload' ); ?>
275
+ <input type="file" name="relve_ar_zip" accept=".zip,application/zip" required />
276
+ <?php submit_button( __( 'Upload zip', 'relve-agent-ready' ), 'secondary', 'submit', false ); ?>
277
+ <p class="description"><?php esc_html_e( 'The zip from Relve (refined-md.zip): a top-level md/ folder of .md files. Uploading replaces the stored set. Max 20 MB, 2 MB per file.', 'relve-agent-ready' ); ?></p>
278
+ </form>
279
+ <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin-top:1em">
280
+ <input type="hidden" name="action" value="relve_ar_clear" />
281
+ <?php wp_nonce_field( 'relve_ar_clear' ); ?>
282
+ <?php submit_button( __( 'Clear refined files', 'relve-agent-ready' ), 'delete', 'submit', false, array( 'onclick' => "return confirm('" . esc_js( __( 'Delete all refined files?', 'relve-agent-ready' ) ) . "');" ) ); ?>
283
+ </form>
284
+ </div>
285
+ <?php
286
+ }
287
+
288
+ /**
289
+ * Status panel.
290
+ *
291
+ * @param array $s Settings.
292
+ * @param int $count Refined file count.
293
+ * @return void
294
+ */
295
+ private static function render_status( $s, $count ) {
296
+ $yoast = Relve_AR_Llms::yoast_serves();
297
+ $physical = Relve_AR_Llms::physical_file_exists();
298
+ $serves = Relve_AR_Llms::should_serve( $s['llms_mode'], $yoast, $physical );
299
+ $origin = Relve_AR_Router::home_origin();
300
+ $llms = array();
301
+ foreach ( Relve_AR_Router::llms_paths( $s ) as $path ) {
302
+ $llms[] = $origin . $path;
303
+ }
304
+
305
+ $sample = get_posts(
306
+ array(
307
+ 'post_type' => in_array( 'post', $s['post_types'], true ) ? 'post' : ( $s['post_types'] ? $s['post_types'] : 'post' ),
308
+ 'post_status' => 'publish',
309
+ 'has_password' => false,
310
+ 'numberposts' => 1,
311
+ 'orderby' => 'date',
312
+ 'order' => 'DESC',
313
+ 'suppress_filters' => false,
314
+ )
315
+ );
316
+ $page = $sample ? get_permalink( $sample[0] ) : home_url( '/' );
317
+ $md = Relve_AR_Llms::md_url( $page, home_url( '/' ) );
318
+ ?>
319
+ <div class="card" style="max-width:none">
320
+ <h2 class="title"><?php esc_html_e( 'Status', 'relve-agent-ready' ); ?></h2>
321
+ <table class="widefat striped" style="max-width:60em">
322
+ <tbody>
323
+ <tr>
324
+ <td><?php esc_html_e( 'Serving Markdown', 'relve-agent-ready' ); ?></td>
325
+ <td><strong><?php echo $s['enabled'] ? esc_html__( 'On', 'relve-agent-ready' ) : esc_html__( 'Off (tick Enabled below)', 'relve-agent-ready' ); ?></strong></td>
326
+ </tr>
327
+ <tr>
328
+ <td><?php esc_html_e( 'Refined files', 'relve-agent-ready' ); ?></td>
329
+ <td><?php echo esc_html( (string) (int) $count ); ?></td>
330
+ </tr>
331
+ <tr>
332
+ <td><?php esc_html_e( 'llms.txt', 'relve-agent-ready' ); ?></td>
333
+ <td>
334
+ <?php
335
+ if ( ! $s['enabled'] ) {
336
+ esc_html_e( 'Off while the plugin is disabled.', 'relve-agent-ready' );
337
+ } elseif ( $serves ) {
338
+ echo esc_html( implode( ' ', $llms ) );
339
+ } elseif ( 'off' === $s['llms_mode'] ) {
340
+ esc_html_e( 'Off (setting).', 'relve-agent-ready' );
341
+ } elseif ( $yoast ) {
342
+ esc_html_e( 'Served by Yoast SEO; this plugin stays out of the way (choose "On" to take over).', 'relve-agent-ready' );
343
+ } else {
344
+ esc_html_e( 'A real llms.txt file exists in the WordPress folder; the web server serves that one.', 'relve-agent-ready' );
345
+ }
346
+ ?>
347
+ </td>
348
+ </tr>
349
+ </tbody>
350
+ </table>
351
+ <p><?php esc_html_e( 'Test from a terminal (expect Content-Type: text/markdown and x-agent-ready-source):', 'relve-agent-ready' ); ?></p>
352
+ <pre style="white-space:pre-wrap;background:#f6f7f7;padding:1em;max-width:60em"><?php
353
+ echo esc_html(
354
+ 'curl -sI -H "Accept: text/markdown" "' . $page . "\"\n"
355
+ . 'curl -s "' . $md . "\"\n"
356
+ . 'curl -s "' . ( $llms ? $llms[0] : $origin . Relve_AR_Router::home_path() . '/llms.txt' ) . '"'
357
+ );
358
+ ?></pre>
359
+ <p class="description"><?php esc_html_e( 'A page cache or CDN in front of WordPress may still answer an Accept request with cached HTML. That is safe; the .md URL always works. Behind a proxy or Worker, make sure it forwards the Accept header, .md paths and llms.txt to WordPress.', 'relve-agent-ready' ); ?></p>
360
+ </div>
361
+ <?php
362
+ }
363
+ }