aeo.js 0.0.7 → 0.0.9

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 (68) hide show
  1. package/README.md +119 -164
  2. package/dist/angular.d.mts +1 -1
  3. package/dist/angular.d.ts +1 -1
  4. package/dist/angular.js +69 -13
  5. package/dist/angular.js.map +1 -1
  6. package/dist/angular.mjs +69 -13
  7. package/dist/angular.mjs.map +1 -1
  8. package/dist/astro.d.mts +1 -1
  9. package/dist/astro.d.ts +1 -1
  10. package/dist/astro.js +78 -18
  11. package/dist/astro.js.map +1 -1
  12. package/dist/astro.mjs +78 -18
  13. package/dist/astro.mjs.map +1 -1
  14. package/dist/cli.js +84 -27
  15. package/dist/cli.js.map +1 -1
  16. package/dist/cli.mjs +84 -27
  17. package/dist/cli.mjs.map +1 -1
  18. package/dist/index.d.mts +2 -2
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +89 -29
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +89 -29
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/next.d.mts +1 -1
  25. package/dist/next.d.ts +1 -1
  26. package/dist/next.js +71 -15
  27. package/dist/next.js.map +1 -1
  28. package/dist/next.mjs +71 -15
  29. package/dist/next.mjs.map +1 -1
  30. package/dist/nuxt.d.mts +1 -1
  31. package/dist/nuxt.d.ts +1 -1
  32. package/dist/nuxt.js +69 -13
  33. package/dist/nuxt.js.map +1 -1
  34. package/dist/nuxt.mjs +69 -13
  35. package/dist/nuxt.mjs.map +1 -1
  36. package/dist/react.d.mts +1 -1
  37. package/dist/react.d.ts +1 -1
  38. package/dist/react.js +52 -7
  39. package/dist/react.js.map +1 -1
  40. package/dist/react.mjs +52 -7
  41. package/dist/react.mjs.map +1 -1
  42. package/dist/{types-Cn_Qbkmg.d.mts → types-BlLNcw-X.d.mts} +2 -0
  43. package/dist/{types-Cn_Qbkmg.d.ts → types-BlLNcw-X.d.ts} +2 -0
  44. package/dist/vite.d.mts +1 -1
  45. package/dist/vite.d.ts +1 -1
  46. package/dist/vite.js +94 -20
  47. package/dist/vite.js.map +1 -1
  48. package/dist/vite.mjs +94 -20
  49. package/dist/vite.mjs.map +1 -1
  50. package/dist/vue.d.mts +1 -1
  51. package/dist/vue.d.ts +1 -1
  52. package/dist/vue.js +52 -7
  53. package/dist/vue.js.map +1 -1
  54. package/dist/vue.mjs +52 -7
  55. package/dist/vue.mjs.map +1 -1
  56. package/dist/webpack.d.mts +1 -1
  57. package/dist/webpack.d.ts +1 -1
  58. package/dist/webpack.js +69 -13
  59. package/dist/webpack.js.map +1 -1
  60. package/dist/webpack.mjs +69 -13
  61. package/dist/webpack.mjs.map +1 -1
  62. package/dist/widget.d.mts +1 -1
  63. package/dist/widget.d.ts +1 -1
  64. package/dist/widget.js +52 -7
  65. package/dist/widget.js.map +1 -1
  66. package/dist/widget.mjs +52 -7
  67. package/dist/widget.mjs.map +1 -1
  68. package/package.json +1 -1
package/README.md CHANGED
@@ -1,39 +1,22 @@
1
- # aeo.js
2
-
3
- Answer Engine Optimization for the modern web. Make your site discoverable by AI crawlers and LLMs.
4
-
5
1
  <p align="center">
6
- <a href="https://ralphstarter.ai/badge"><img src="https://ralphstarter.ai/img/badge-built-with@2x.png" alt="built with ralph-starter" height="28"></a>
2
+ <h1 align="center">aeo.js</h1>
3
+ <p align="center">Answer Engine Optimization for the modern web.<br/>Make your site discoverable by ChatGPT, Claude, Perplexity, and every AI answer engine.</p>
7
4
  </p>
8
5
 
9
- ## What is AEO?
10
-
11
- Answer Engine Optimization (AEO) is the practice of making your website discoverable and citable by AI-powered answer engines like ChatGPT, Claude, Perplexity, and SearchGPT.
12
-
13
- aeo.js auto-generates the files these engines look for and provides a drop-in widget that shows visitors how your site appears to AI.
14
-
15
- ## Features
16
-
17
- - **`llms.txt` / `llms-full.txt`** -- LLM-readable summaries of your site
18
- - **`robots.txt`** -- AI-crawler-aware robots directives
19
- - **`sitemap.xml`** -- Standard sitemap generation
20
- - **`docs.json`** -- Structured documentation manifest
21
- - **`ai-index.json`** -- AI-optimized content index
22
- - **Raw Markdown** -- Per-page `.md` files extracted from your HTML
23
- - **Human/AI Widget** -- Drop-in toggle showing the AI-readable version of any page
24
- - **CLI** -- `npx aeo.js generate` to run standalone
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/aeo.js"><img src="https://img.shields.io/npm/v/aeo.js?style=flat&colorA=0d0d0d&colorB=1a1a1a" alt="npm version"></a>
8
+ <a href="https://www.npmjs.com/package/aeo.js"><img src="https://img.shields.io/npm/dm/aeo.js?style=flat&colorA=0d0d0d&colorB=1a1a1a" alt="npm downloads"></a>
9
+ <a href="https://github.com/multivmlabs/aeo.js"><img src="https://img.shields.io/github/stars/multivmlabs/aeo.js?style=flat&colorA=0d0d0d&colorB=1a1a1a" alt="GitHub stars"></a>
10
+ <a href="https://github.com/multivmlabs/aeo.js/blob/main/LICENSE"><img src="https://img.shields.io/github/license/multivmlabs/aeo.js?style=flat&colorA=0d0d0d&colorB=1a1a1a" alt="License"></a>
11
+ </p>
25
12
 
26
- ## Supported Frameworks
13
+ <p align="center">
14
+ <a href="https://aeojs.org">Documentation</a> · <a href="https://check.aeojs.org">AEO Checker</a> · <a href="https://www.npmjs.com/package/aeo.js">npm</a>
15
+ </p>
27
16
 
28
- | Framework | Status | Import |
29
- |-----------|--------|--------|
30
- | Astro | Stable | `aeo.js/astro` |
31
- | Next.js | Stable | `aeo.js/next` |
32
- | Vite / React | Stable | `aeo.js/vite` |
33
- | Nuxt | Stable | `aeo.js/nuxt` |
34
- | Angular | Stable | `aeo.js/angular` |
35
- | Webpack | Stable | `aeo.js/webpack` |
36
- | Any (CLI) | Stable | `npx aeo.js generate` |
17
+ <p align="center">
18
+ <img src="example.gif" alt="aeo.js in action" width="700">
19
+ </p>
37
20
 
38
21
  ## Install
39
22
 
@@ -62,12 +45,8 @@ export default defineConfig({
62
45
  });
63
46
  ```
64
47
 
65
- The widget is automatically injected and persists across View Transitions.
66
-
67
48
  ### Next.js
68
49
 
69
- Wrap your Next.js config with `withAeo`:
70
-
71
50
  ```js
72
51
  // next.config.mjs
73
52
  import { withAeo } from 'aeo.js/next';
@@ -81,7 +60,7 @@ export default withAeo({
81
60
  });
82
61
  ```
83
62
 
84
- After building, run the post-build step to extract content from pre-rendered pages:
63
+ Add the post-build step to `package.json`:
85
64
 
86
65
  ```json
87
66
  {
@@ -91,7 +70,7 @@ After building, run the post-build step to extract content from pre-rendered pag
91
70
  }
92
71
  ```
93
72
 
94
- ### Vite (React, Vue, Svelte, etc.)
73
+ ### Vite
95
74
 
96
75
  ```js
97
76
  // vite.config.ts
@@ -109,16 +88,8 @@ export default defineConfig({
109
88
  });
110
89
  ```
111
90
 
112
- The Vite plugin:
113
- - Generates AEO files on `vite dev` and `vite build`
114
- - Injects the widget automatically
115
- - Serves dynamic `.md` files in dev (extracts content from your running app)
116
- - Detects SPA shells and falls back to client-side DOM extraction
117
-
118
91
  ### Nuxt
119
92
 
120
- Add the module to your Nuxt config:
121
-
122
93
  ```ts
123
94
  // nuxt.config.ts
124
95
  export default defineNuxtConfig({
@@ -131,17 +102,8 @@ export default defineNuxtConfig({
131
102
  });
132
103
  ```
133
104
 
134
- The Nuxt module:
135
- - Scans your `pages/` directory for routes
136
- - Generates AEO files during dev and production builds
137
- - Scans pre-rendered HTML from `.output/public/` for full page content
138
- - Injects the widget as a client-side Nuxt plugin
139
- - Adds `<link>` and `<meta>` tags for AEO discoverability
140
-
141
105
  ### Angular
142
106
 
143
- Add a post-build step to your `package.json`:
144
-
145
107
  ```json
146
108
  {
147
109
  "scripts": {
@@ -150,20 +112,6 @@ Add a post-build step to your `package.json`:
150
112
  }
151
113
  ```
152
114
 
153
- The Angular plugin:
154
- - Reads `angular.json` to auto-detect the output directory (`dist/<project>/browser/`)
155
- - Scans route config files (`*.routes.ts`) and component directories for routes
156
- - Scans pre-rendered HTML from the build output for full page content
157
- - Injects the widget into `index.html` automatically
158
-
159
- You can also generate AEO files from source routes without building:
160
-
161
- ```ts
162
- import { generate } from 'aeo.js/angular';
163
-
164
- await generate({ title: 'My App', url: 'https://myapp.com' });
165
- ```
166
-
167
115
  ### Webpack
168
116
 
169
117
  ```js
@@ -181,101 +129,39 @@ module.exports = {
181
129
  };
182
130
  ```
183
131
 
184
- ## CLI
132
+ ### CLI
185
133
 
186
- Run aeo.js from the command line without any framework integration:
134
+ No framework needed run standalone:
187
135
 
188
136
  ```bash
189
- # Generate all AEO files
190
- npx aeo.js generate
191
-
192
- # Generate with options
193
- npx aeo.js generate --url https://mysite.com --title "My Site" --out public
194
-
195
- # Create a config file
137
+ npx aeo.js generate --url https://mysite.com --title "My Site"
196
138
  npx aeo.js init
197
-
198
- # Check your setup
199
139
  npx aeo.js check
200
140
  ```
201
141
 
202
- ### Commands
203
-
204
- | Command | Description |
205
- |---------|-------------|
206
- | `generate` | Generate all AEO files (robots.txt, llms.txt, sitemap.xml, etc.) |
207
- | `init` | Create an `aeo.config.ts` configuration file |
208
- | `check` | Validate your AEO setup and show what would be generated |
209
-
210
- ### Options
211
-
212
- | Flag | Description |
213
- |------|-------------|
214
- | `--out <dir>` | Output directory (default: auto-detected) |
215
- | `--url <url>` | Site URL |
216
- | `--title <title>` | Site title |
217
- | `--no-widget` | Disable widget generation |
218
- | `--help`, `-h` | Show help |
219
- | `--version`, `-v` | Show version |
220
-
221
- ## Configuration
222
-
223
- All framework plugins accept the same config object. You can also use `defineConfig` for standalone configs:
224
-
225
- ```js
226
- import { defineConfig } from 'aeo.js';
227
-
228
- export default defineConfig({
229
- // Required
230
- title: 'My Site',
231
- url: 'https://mysite.com',
232
-
233
- // Optional
234
- description: 'A description of your site',
235
- contentDir: 'docs', // Directory with handwritten .md files
236
- outDir: 'public', // Output directory for generated files
142
+ ## Supported Frameworks
237
143
 
238
- // Toggle individual generators
239
- generators: {
240
- robotsTxt: true, // robots.txt
241
- llmsTxt: true, // llms.txt
242
- llmsFullTxt: true, // llms-full.txt
243
- rawMarkdown: true, // Per-page .md files
244
- manifest: true, // docs.json
245
- sitemap: true, // sitemap.xml
246
- aiIndex: true, // ai-index.json
247
- },
144
+ | Framework | Import |
145
+ |-----------|--------|
146
+ | Astro | `aeo.js/astro` |
147
+ | Next.js | `aeo.js/next` |
148
+ | Vite | `aeo.js/vite` |
149
+ | Nuxt | `aeo.js/nuxt` |
150
+ | Angular | `aeo.js/angular` |
151
+ | Webpack | `aeo.js/webpack` |
152
+ | CLI | `npx aeo.js generate` |
248
153
 
249
- // Customize robots.txt
250
- robots: {
251
- allow: ['/'],
252
- disallow: ['/admin'],
253
- crawlDelay: 0,
254
- },
154
+ ## Widget
255
155
 
256
- // Widget configuration
257
- widget: {
258
- enabled: true,
259
- position: 'bottom-right', // 'bottom-left' | 'top-right' | 'top-left'
260
- humanLabel: 'Human',
261
- aiLabel: 'AI',
262
- showBadge: true,
263
- theme: {
264
- background: 'rgba(18, 18, 24, 0.9)',
265
- text: '#C0C0C5',
266
- accent: '#E8E8EA',
267
- badge: '#4ADE80',
268
- },
269
- },
270
- });
271
- ```
156
+ The Human/AI widget lets visitors toggle between the normal page and its AI-readable markdown version.
272
157
 
273
- ## Widget
158
+ | Default | Small | Icon |
159
+ |---------|-------|------|
160
+ | <img src="widget-default.gif" alt="Default widget" width="220"> | <img src="widget-small.gif" alt="Small widget" width="220"> | <img src="widget-icon.gif" alt="Icon widget" width="220"> |
274
161
 
275
- The Human/AI widget is a floating toggle that lets visitors switch between the normal page and its AI-readable markdown version. Framework plugins (Astro, Vite, Nuxt, Angular) inject it automatically. For Next.js or manual setups:
162
+ Framework plugins inject it automatically. For Next.js or manual setups:
276
163
 
277
164
  ```tsx
278
- // app/layout.tsx (or any client component)
279
165
  'use client';
280
166
  import { useEffect } from 'react';
281
167
 
@@ -295,29 +181,98 @@ export function AeoWidgetLoader() {
295
181
  }
296
182
  ```
297
183
 
298
- When a visitor clicks **AI**, the widget:
299
- 1. Fetches the `.md` file for the current page
300
- 2. Falls back to extracting markdown from the live DOM if no `.md` exists
301
- 3. Displays the markdown in a slide-out panel
302
- 4. Offers copy-to-clipboard and download actions
184
+ React and Vue wrapper components are also available:
185
+
186
+ ```tsx
187
+ import { AeoReactWidget } from 'aeo.js/react';
188
+
189
+ <AeoReactWidget config={{ title: 'My Site', url: 'https://mysite.com' }} />
190
+ ```
191
+
192
+ ```vue
193
+ <script setup>
194
+ import { AeoVueWidget } from 'aeo.js/vue';
195
+ </script>
196
+
197
+ <template>
198
+ <AeoVueWidget :config="{ title: 'My Site', url: 'https://mysite.com' }" />
199
+ </template>
200
+ ```
303
201
 
304
202
  ## Generated Files
305
203
 
306
- After building, your output directory will contain:
204
+ After building, your output directory contains:
307
205
 
308
206
  ```
309
207
  public/
310
- robots.txt # AI-crawler-aware directives
311
- llms.txt # Short LLM-readable summary
312
- llms-full.txt # Full content for LLMs
313
- sitemap.xml # Standard sitemap
314
- docs.json # Documentation manifest
315
- ai-index.json # AI-optimized content index
316
- index.md # Markdown for /
317
- about.md # Markdown for /about
318
- ... # One .md per discovered page
208
+ ├── robots.txt # AI-crawler directives
209
+ ├── llms.txt # Short LLM-readable summary
210
+ ├── llms-full.txt # Full content for LLMs
211
+ ├── sitemap.xml # Standard sitemap
212
+ ├── docs.json # Documentation manifest
213
+ ├── ai-index.json # AI content index
214
+ ├── index.md # Markdown for /
215
+ └── about.md # Markdown for /about
216
+ ```
217
+
218
+ ## Configuration
219
+
220
+ ```js
221
+ import { defineConfig } from 'aeo.js';
222
+
223
+ export default defineConfig({
224
+ title: 'My Site',
225
+ url: 'https://mysite.com',
226
+ description: 'A description of your site',
227
+
228
+ generators: {
229
+ robotsTxt: true,
230
+ llmsTxt: true,
231
+ llmsFullTxt: true,
232
+ rawMarkdown: true,
233
+ sitemap: true,
234
+ aiIndex: true,
235
+ schema: true,
236
+ },
237
+
238
+ schema: {
239
+ enabled: true,
240
+ organization: { name: 'My Company', url: 'https://mysite.com' },
241
+ defaultType: 'WebPage',
242
+ },
243
+
244
+ og: {
245
+ enabled: true,
246
+ image: 'https://mysite.com/og.png',
247
+ twitterHandle: '@mycompany',
248
+ },
249
+
250
+ widget: {
251
+ enabled: true,
252
+ position: 'bottom-right',
253
+ theme: { accent: '#4ADE80', badge: '#4ADE80' },
254
+ },
255
+ });
319
256
  ```
320
257
 
258
+ Full configuration reference → [aeojs.org/reference/configuration](https://aeojs.org/reference/configuration/)
259
+
260
+ ## Why AEO?
261
+
262
+ - **58% of searches** end without a click — AI gives the answer directly
263
+ - **40% of Gen Z** prefer AI assistants over traditional search engines
264
+ - **97% of sites** have no `llms.txt` or structured data for AI crawlers
265
+ - **1 minute** to set up with aeo.js
266
+
267
+ If your site isn't optimized for AI engines, you're invisible to a growing share of users who never open a search results page.
268
+
269
+ ## Links
270
+
271
+ - [Documentation](https://aeojs.org)
272
+ - [AEO Checker](https://check.aeojs.org)
273
+ - [npm](https://www.npmjs.com/package/aeo.js)
274
+ - [GitHub](https://github.com/multivmlabs/aeo.js)
275
+
321
276
  ## License
322
277
 
323
278
  MIT
@@ -1,4 +1,4 @@
1
- import { A as AeoConfig } from './types-Cn_Qbkmg.mjs';
1
+ import { A as AeoConfig } from './types-BlLNcw-X.mjs';
2
2
 
3
3
  /**
4
4
  * Generate a widget script tag to inject into Angular's index.html.
package/dist/angular.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AeoConfig } from './types-Cn_Qbkmg.js';
1
+ import { A as AeoConfig } from './types-BlLNcw-X.js';
2
2
 
3
3
  /**
4
4
  * Generate a widget script tag to inject into Angular's index.html.
package/dist/angular.js CHANGED
@@ -79,10 +79,19 @@ function generateRobotsTxt(config) {
79
79
  }
80
80
  lines.push("# Default for all other bots");
81
81
  lines.push("User-agent: *");
82
- lines.push("Allow: /");
82
+ for (const path of config.robots.allow.length > 0 ? config.robots.allow : ["/"]) {
83
+ lines.push(`Allow: ${path}`);
84
+ }
85
+ for (const path of config.robots.disallow) {
86
+ lines.push(`Disallow: ${path}`);
87
+ }
88
+ if (config.robots.crawlDelay > 0) {
89
+ lines.push(`Crawl-delay: ${config.robots.crawlDelay}`);
90
+ }
83
91
  lines.push("");
84
- if (config.url) {
85
- lines.push(`Sitemap: ${config.url}/sitemap.xml`);
92
+ const sitemapUrl = config.robots.sitemap || (config.url ? `${config.url}/sitemap.xml` : "");
93
+ if (sitemapUrl) {
94
+ lines.push(`Sitemap: ${sitemapUrl}`);
86
95
  }
87
96
  lines.push("");
88
97
  lines.push("# AEO (Answer Engine Optimization) files");
@@ -164,7 +173,7 @@ function detectFramework(projectRoot = process.cwd()) {
164
173
  };
165
174
  }
166
175
  function resolveConfig(config = {}) {
167
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M;
176
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N;
168
177
  const frameworkInfo = detectFramework();
169
178
  return {
170
179
  title: config.title || "My Site",
@@ -208,15 +217,16 @@ function resolveConfig(config = {}) {
208
217
  widget: {
209
218
  enabled: ((_A = config.widget) == null ? void 0 : _A.enabled) !== false,
210
219
  position: ((_B = config.widget) == null ? void 0 : _B.position) || "bottom-right",
220
+ size: ((_C = config.widget) == null ? void 0 : _C.size) || "default",
211
221
  theme: {
212
- background: ((_D = (_C = config.widget) == null ? void 0 : _C.theme) == null ? void 0 : _D.background) || "rgba(18, 18, 24, 0.9)",
213
- text: ((_F = (_E = config.widget) == null ? void 0 : _E.theme) == null ? void 0 : _F.text) || "#C0C0C5",
214
- accent: ((_H = (_G = config.widget) == null ? void 0 : _G.theme) == null ? void 0 : _H.accent) || "#E8E8EA",
215
- badge: ((_J = (_I = config.widget) == null ? void 0 : _I.theme) == null ? void 0 : _J.badge) || "#4ADE80"
222
+ background: ((_E = (_D = config.widget) == null ? void 0 : _D.theme) == null ? void 0 : _E.background) || "rgba(18, 18, 24, 0.9)",
223
+ text: ((_G = (_F = config.widget) == null ? void 0 : _F.theme) == null ? void 0 : _G.text) || "#C0C0C5",
224
+ accent: ((_I = (_H = config.widget) == null ? void 0 : _H.theme) == null ? void 0 : _I.accent) || "#E8E8EA",
225
+ badge: ((_K = (_J = config.widget) == null ? void 0 : _J.theme) == null ? void 0 : _K.badge) || "#4ADE80"
216
226
  },
217
- humanLabel: ((_K = config.widget) == null ? void 0 : _K.humanLabel) || "Human",
218
- aiLabel: ((_L = config.widget) == null ? void 0 : _L.aiLabel) || "AI",
219
- showBadge: ((_M = config.widget) == null ? void 0 : _M.showBadge) !== false
227
+ humanLabel: ((_L = config.widget) == null ? void 0 : _L.humanLabel) || "Human",
228
+ aiLabel: ((_M = config.widget) == null ? void 0 : _M.aiLabel) || "AI",
229
+ showBadge: ((_N = config.widget) == null ? void 0 : _N.showBadge) !== false
220
230
  }
221
231
  };
222
232
  }
@@ -273,7 +283,8 @@ function collectMarkdownFiles(dir, base = dir) {
273
283
  for (const entry of entries) {
274
284
  const fullPath = path.join(dir, entry);
275
285
  const stat = fs.statSync(fullPath);
276
- if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
286
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "public", "dist", ".next", ".nuxt", ".output", ".open-next", "coverage", "__tests__", "__mocks__"]);
287
+ if (stat.isDirectory() && !entry.startsWith(".") && !SKIP_DIRS.has(entry)) {
277
288
  files.push(...collectMarkdownFiles(fullPath, base));
278
289
  } else if (stat.isFile() && (path.extname(entry) === ".md" || path.extname(entry) === ".mdx")) {
279
290
  const content = fs.readFileSync(fullPath, "utf-8");
@@ -616,7 +627,8 @@ function collectUrls(dir, config, base = dir) {
616
627
  for (const entry of entries) {
617
628
  const fullPath = path.join(dir, entry);
618
629
  const stat = fs.statSync(fullPath);
619
- if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
630
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "public", "dist", ".next", ".nuxt", ".output", ".open-next", "coverage", "__tests__", "__mocks__"]);
631
+ if (stat.isDirectory() && !entry.startsWith(".") && !SKIP_DIRS.has(entry)) {
620
632
  urls.push(...collectUrls(fullPath, config, base));
621
633
  } else if (stat.isFile() && (path.extname(entry) === ".md" || path.extname(entry) === ".mdx" || path.extname(entry) === ".html")) {
622
634
  const relativePath = path.relative(base, fullPath);
@@ -844,6 +856,22 @@ function generatePageSchemas(page, config) {
844
856
  }))
845
857
  });
846
858
  }
859
+ if (faqItems.length === 0) {
860
+ const howToSteps = detectHowToSteps(page.content || "");
861
+ if (howToSteps.length > 0) {
862
+ schemas.push({
863
+ "@context": "https://schema.org",
864
+ "@type": "HowTo",
865
+ name: page.title || config.title,
866
+ step: howToSteps.map((s, i) => ({
867
+ "@type": "HowToStep",
868
+ position: i + 1,
869
+ name: s.name,
870
+ text: s.text
871
+ }))
872
+ });
873
+ }
874
+ }
847
875
  const pageType = config.schema.defaultType;
848
876
  const pageSchema = {
849
877
  "@context": "https://schema.org",
@@ -920,6 +948,34 @@ function detectFaqPatterns(content) {
920
948
  }
921
949
  return items;
922
950
  }
951
+ function detectHowToSteps(content) {
952
+ const steps = [];
953
+ const lines = content.split("\n");
954
+ for (let i = 0; i < lines.length; i++) {
955
+ const line = lines[i].trim();
956
+ const stepMatch = line.match(/^#{1,6}\s+(?:Step\s+\d+[\s:.-]*|(\d+)[.)]\s*)(.+)$/i);
957
+ if (stepMatch) {
958
+ const name = (stepMatch[2] || stepMatch[1] || "").trim();
959
+ const bodyLines = [];
960
+ for (let j = i + 1; j < lines.length; j++) {
961
+ const nextLine = lines[j].trim();
962
+ if (!nextLine) {
963
+ if (bodyLines.length > 0) break;
964
+ continue;
965
+ }
966
+ if (/^#{1,6}\s/.test(nextLine)) break;
967
+ bodyLines.push(nextLine);
968
+ }
969
+ if (name) {
970
+ steps.push({
971
+ name,
972
+ text: bodyLines.join(" ").slice(0, 500)
973
+ });
974
+ }
975
+ }
976
+ }
977
+ return steps.length >= 2 ? steps : [];
978
+ }
923
979
  async function generateAEOFiles(configOrRoot, maybeConfig) {
924
980
  var _a;
925
981
  let config;