third-audience-mdx 1.0.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 (108) hide show
  1. package/CLAUDE.md +41 -0
  2. package/INSTALLATION.md +367 -0
  3. package/README.md +303 -0
  4. package/WORKLOG.md +162 -0
  5. package/dist/cli/index.d.mts +1 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/index.js +208 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/index.mjs +185 -0
  10. package/dist/cli/index.mjs.map +1 -0
  11. package/dist/dashboard/auth.d.mts +16 -0
  12. package/dist/dashboard/auth.d.ts +16 -0
  13. package/dist/dashboard/auth.js +123 -0
  14. package/dist/dashboard/auth.js.map +1 -0
  15. package/dist/dashboard/auth.mjs +87 -0
  16. package/dist/dashboard/auth.mjs.map +1 -0
  17. package/dist/dashboard/routes/analytics-api-route.d.mts +6 -0
  18. package/dist/dashboard/routes/analytics-api-route.d.ts +6 -0
  19. package/dist/dashboard/routes/analytics-api-route.js +180 -0
  20. package/dist/dashboard/routes/analytics-api-route.js.map +1 -0
  21. package/dist/dashboard/routes/analytics-api-route.mjs +145 -0
  22. package/dist/dashboard/routes/analytics-api-route.mjs.map +1 -0
  23. package/dist/dashboard/routes/api-key-route.d.mts +8 -0
  24. package/dist/dashboard/routes/api-key-route.d.ts +8 -0
  25. package/dist/dashboard/routes/api-key-route.js +173 -0
  26. package/dist/dashboard/routes/api-key-route.js.map +1 -0
  27. package/dist/dashboard/routes/api-key-route.mjs +137 -0
  28. package/dist/dashboard/routes/api-key-route.mjs.map +1 -0
  29. package/dist/dashboard/routes/citation-route.d.mts +14 -0
  30. package/dist/dashboard/routes/citation-route.d.ts +14 -0
  31. package/dist/dashboard/routes/citation-route.js +202 -0
  32. package/dist/dashboard/routes/citation-route.js.map +1 -0
  33. package/dist/dashboard/routes/citation-route.mjs +166 -0
  34. package/dist/dashboard/routes/citation-route.mjs.map +1 -0
  35. package/dist/dashboard/routes/llms-txt-route.d.mts +6 -0
  36. package/dist/dashboard/routes/llms-txt-route.d.ts +6 -0
  37. package/dist/dashboard/routes/llms-txt-route.js +119 -0
  38. package/dist/dashboard/routes/llms-txt-route.js.map +1 -0
  39. package/dist/dashboard/routes/llms-txt-route.mjs +84 -0
  40. package/dist/dashboard/routes/llms-txt-route.mjs.map +1 -0
  41. package/dist/dashboard/routes/login-route.d.mts +6 -0
  42. package/dist/dashboard/routes/login-route.d.ts +6 -0
  43. package/dist/dashboard/routes/login-route.js +313 -0
  44. package/dist/dashboard/routes/login-route.js.map +1 -0
  45. package/dist/dashboard/routes/login-route.mjs +284 -0
  46. package/dist/dashboard/routes/login-route.mjs.map +1 -0
  47. package/dist/dashboard/routes/markdown-route.d.mts +15 -0
  48. package/dist/dashboard/routes/markdown-route.d.ts +15 -0
  49. package/dist/dashboard/routes/markdown-route.js +239 -0
  50. package/dist/dashboard/routes/markdown-route.js.map +1 -0
  51. package/dist/dashboard/routes/markdown-route.mjs +204 -0
  52. package/dist/dashboard/routes/markdown-route.mjs.map +1 -0
  53. package/dist/dashboard/routes/okf-route.d.mts +13 -0
  54. package/dist/dashboard/routes/okf-route.d.ts +13 -0
  55. package/dist/dashboard/routes/okf-route.js +184 -0
  56. package/dist/dashboard/routes/okf-route.js.map +1 -0
  57. package/dist/dashboard/routes/okf-route.mjs +149 -0
  58. package/dist/dashboard/routes/okf-route.mjs.map +1 -0
  59. package/dist/dashboard/routes/sitemap-ai-route.d.mts +6 -0
  60. package/dist/dashboard/routes/sitemap-ai-route.d.ts +6 -0
  61. package/dist/dashboard/routes/sitemap-ai-route.js +134 -0
  62. package/dist/dashboard/routes/sitemap-ai-route.js.map +1 -0
  63. package/dist/dashboard/routes/sitemap-ai-route.mjs +99 -0
  64. package/dist/dashboard/routes/sitemap-ai-route.mjs.map +1 -0
  65. package/dist/dashboard/ui/components/Sidebar.d.mts +5 -0
  66. package/dist/dashboard/ui/components/Sidebar.d.ts +5 -0
  67. package/dist/dashboard/ui/components/Sidebar.js +102 -0
  68. package/dist/dashboard/ui/components/Sidebar.js.map +1 -0
  69. package/dist/dashboard/ui/components/Sidebar.mjs +68 -0
  70. package/dist/dashboard/ui/components/Sidebar.mjs.map +1 -0
  71. package/dist/dashboard/ui/globals.css +175 -0
  72. package/dist/dashboard/ui/pages/BotAnalyticsPage.d.mts +5 -0
  73. package/dist/dashboard/ui/pages/BotAnalyticsPage.d.ts +5 -0
  74. package/dist/dashboard/ui/pages/BotAnalyticsPage.js +269 -0
  75. package/dist/dashboard/ui/pages/BotAnalyticsPage.js.map +1 -0
  76. package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs +232 -0
  77. package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs.map +1 -0
  78. package/dist/dashboard/ui/pages/BotManagementPage.d.mts +13 -0
  79. package/dist/dashboard/ui/pages/BotManagementPage.d.ts +13 -0
  80. package/dist/dashboard/ui/pages/BotManagementPage.js +177 -0
  81. package/dist/dashboard/ui/pages/BotManagementPage.js.map +1 -0
  82. package/dist/dashboard/ui/pages/BotManagementPage.mjs +153 -0
  83. package/dist/dashboard/ui/pages/BotManagementPage.mjs.map +1 -0
  84. package/dist/dashboard/ui/pages/LlmTrafficPage.d.mts +5 -0
  85. package/dist/dashboard/ui/pages/LlmTrafficPage.d.ts +5 -0
  86. package/dist/dashboard/ui/pages/LlmTrafficPage.js +203 -0
  87. package/dist/dashboard/ui/pages/LlmTrafficPage.js.map +1 -0
  88. package/dist/dashboard/ui/pages/LlmTrafficPage.mjs +168 -0
  89. package/dist/dashboard/ui/pages/LlmTrafficPage.mjs.map +1 -0
  90. package/dist/dashboard/ui/pages/SettingsPage.d.mts +8 -0
  91. package/dist/dashboard/ui/pages/SettingsPage.d.ts +8 -0
  92. package/dist/dashboard/ui/pages/SettingsPage.js +181 -0
  93. package/dist/dashboard/ui/pages/SettingsPage.js.map +1 -0
  94. package/dist/dashboard/ui/pages/SettingsPage.mjs +157 -0
  95. package/dist/dashboard/ui/pages/SettingsPage.mjs.map +1 -0
  96. package/dist/dashboard/ui/pages/SystemHealthPage.d.mts +5 -0
  97. package/dist/dashboard/ui/pages/SystemHealthPage.d.ts +5 -0
  98. package/dist/dashboard/ui/pages/SystemHealthPage.js +183 -0
  99. package/dist/dashboard/ui/pages/SystemHealthPage.js.map +1 -0
  100. package/dist/dashboard/ui/pages/SystemHealthPage.mjs +148 -0
  101. package/dist/dashboard/ui/pages/SystemHealthPage.mjs.map +1 -0
  102. package/dist/index.d.mts +84 -0
  103. package/dist/index.d.ts +84 -0
  104. package/dist/index.js +372 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/index.mjs +346 -0
  107. package/dist/index.mjs.map +1 -0
  108. package/package.json +125 -0
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # third-audience-mdx
2
+
3
+ For two decades, websites were built for two audiences: **humans** and **search engines**. Today there is a third audience rapidly growing: **AI agents and LLM crawlers**.
4
+
5
+ AI systems like ChatGPT, Perplexity, Claude, and Gemini actively crawl the web to answer questions, generate summaries, and provide recommendations. Most sites serve these bots the same HTML built for humans — full of navigation menus, scripts, ads, and layout markup that adds noise and reduces accuracy.
6
+
7
+ **third-audience-mdx** automatically serves clean, structured Markdown versions of your MDX content to AI crawlers, while humans continue to see your normal site. Think of it as **SEO for the AI era** — Generative Engine Optimization (GEO).
8
+
9
+ Drop-in npm package for Next.js App Router. Tracks bot visits and AI citations. Zero external services. Everything stored in flat JSONL files.
10
+
11
+ > Inspired by [third-audience-wordpress-plugin](https://github.com/spcaeo/third-audience-wordpress-plugin) by [@spcaeo](https://github.com/spcaeo).
12
+
13
+ ---
14
+
15
+ ## Features
16
+
17
+ - **`.md` URL for every page** — `GET /your-post.md` returns clean Markdown of the matching MDX file
18
+ - **Content negotiation** — `Accept: text/markdown` header serves Markdown to any AI crawler automatically
19
+ - **`/llms.txt`** — auto-generated AI content index from your MDX frontmatter
20
+ - **`/sitemap-ai.xml`** — AI-specific sitemap with title and description metadata
21
+ - **`/okf/` bundle** — Open Knowledge Format: all content as self-navigable `.md` files
22
+ - **Bot detection** — 20+ known AI crawlers (ClaudeBot, GPTBot, PerplexityBot, Gemini, etc.) plus heuristic detection
23
+ - **Bot analytics** — every crawler visit logged to `data/ta-visits.jsonl`
24
+ - **Citation tracking** — server-side referrer detection + client-side JS for ChatGPT, Perplexity, Claude, Gemini, Copilot
25
+ - **Citation alerts** — notifies on first citation, new platform, citation spikes
26
+ - **Dashboard** — React web UI at `/third-audience/` with 4 pages
27
+ - **CLI** — `npx third-audience init/health/export`
28
+ - **JSX stripping** — serves clean Markdown to AI; removes imports, component tags, expressions
29
+ - **File-system cache** — memory LRU + disk cache, invalidated when MDX source changes
30
+
31
+ ---
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ npm install third-audience-mdx
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Quick Setup
42
+
43
+ ```bash
44
+ npx third-audience init
45
+ ```
46
+
47
+ The wizard detects your Next.js project, asks for your content directory, and writes the required files.
48
+
49
+ ---
50
+
51
+ ## Manual Setup
52
+
53
+ ### 1. Wrap your Next.js config
54
+
55
+ **`next.config.ts`**
56
+ ```ts
57
+ import { withThirdAudience } from 'third-audience-mdx'
58
+
59
+ export default withThirdAudience({
60
+ contentDir: 'content', // where your .mdx files live
61
+ dataDir: 'data', // where analytics logs go
62
+ dashboard: true,
63
+ dashboardSecret: process.env.THIRD_AUDIENCE_SECRET,
64
+ })
65
+ ```
66
+
67
+ ### 2. Add middleware
68
+
69
+ **`middleware.ts`** (project root)
70
+ ```ts
71
+ export { thirdAudienceMiddleware as middleware } from 'third-audience-mdx'
72
+
73
+ export const config = {
74
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
75
+ }
76
+ ```
77
+
78
+ ### 3. Add API routes
79
+
80
+ Create these files in your `app/` directory:
81
+
82
+ **`app/api/third-audience/markdown/[...slug]/route.ts`**
83
+ ```ts
84
+ export { GET } from 'third-audience-mdx/routes/markdown'
85
+ ```
86
+
87
+ **`app/api/third-audience/okf/[...path]/route.ts`**
88
+ ```ts
89
+ export { GET } from 'third-audience-mdx/routes/okf'
90
+ ```
91
+
92
+ **`app/api/third-audience/llms-txt/route.ts`**
93
+ ```ts
94
+ export { GET } from 'third-audience-mdx/routes/llms-txt'
95
+ ```
96
+
97
+ **`app/api/third-audience/sitemap-ai/route.ts`**
98
+ ```ts
99
+ export { GET } from 'third-audience-mdx/routes/sitemap-ai'
100
+ ```
101
+
102
+ **`app/api/third-audience/citation/route.ts`**
103
+ ```ts
104
+ export { GET, POST } from 'third-audience-mdx/routes/citation'
105
+ ```
106
+
107
+ **`app/api/third-audience/analytics/route.ts`**
108
+ ```ts
109
+ export { GET } from 'third-audience-mdx/routes/analytics'
110
+ ```
111
+
112
+ **`app/api/third-audience/bots-config/route.ts`**
113
+ ```ts
114
+ export { GET, POST } from 'third-audience-mdx/routes/bots-config'
115
+ ```
116
+
117
+ ### 4. Add the dashboard
118
+
119
+ **`app/third-audience/layout.tsx`**
120
+ ```tsx
121
+ import type { ReactNode } from 'react'
122
+ import { Sidebar } from 'third-audience-mdx/dashboard/ui/components/Sidebar'
123
+ import 'third-audience-mdx/dashboard/ui/globals.css'
124
+
125
+ export default function DashboardLayout({ children }: { children: ReactNode }) {
126
+ return (
127
+ <html lang="en">
128
+ <body>
129
+ <div className="ta-layout">
130
+ <Sidebar />
131
+ <main className="ta-main">{children}</main>
132
+ </div>
133
+ </body>
134
+ </html>
135
+ )
136
+ }
137
+ ```
138
+
139
+ **`app/third-audience/page.tsx`** — Bot Analytics
140
+ ```tsx
141
+ import { BotAnalyticsPage } from 'third-audience-mdx/dashboard/ui/pages/BotAnalyticsPage'
142
+ export const dynamic = 'force-dynamic'
143
+ export default function Page() { return <BotAnalyticsPage /> }
144
+ ```
145
+
146
+ **`app/third-audience/citations/page.tsx`** — LLM Traffic
147
+ ```tsx
148
+ import { LlmTrafficPage } from 'third-audience-mdx/dashboard/ui/pages/LlmTrafficPage'
149
+ export const dynamic = 'force-dynamic'
150
+ export default function Page() { return <LlmTrafficPage /> }
151
+ ```
152
+
153
+ **`app/third-audience/bots/page.tsx`** — Bot Management
154
+ ```tsx
155
+ import { BotManagementPage } from 'third-audience-mdx/dashboard/ui/pages/BotManagementPage'
156
+ export const dynamic = 'force-dynamic'
157
+ export default function Page() { return <BotManagementPage config={{ allowlist: [], blocklist: [], track_unknown: true }} /> }
158
+ ```
159
+
160
+ **`app/third-audience/health/page.tsx`** — System Health
161
+ ```tsx
162
+ import { SystemHealthPage } from 'third-audience-mdx/dashboard/ui/pages/SystemHealthPage'
163
+ export const dynamic = 'force-dynamic'
164
+ export default function Page() { return <SystemHealthPage /> }
165
+ ```
166
+
167
+ ### 5. Add client-side citation tracker
168
+
169
+ Copy `node_modules/third-audience-mdx/src/public/citation-tracker.js` to `public/` and add to your root layout:
170
+
171
+ ```tsx
172
+ // app/layout.tsx
173
+ <script src="/citation-tracker.js" async />
174
+ ```
175
+
176
+ ### 6. Environment variables
177
+
178
+ **`.env.local`**
179
+ ```
180
+ THIRD_AUDIENCE_SECRET=your-secret-here
181
+ NEXT_PUBLIC_SITE_URL=https://yoursite.com
182
+ TA_CONTENT_DIR=content
183
+ TA_DATA_DIR=data
184
+ ```
185
+
186
+ ### 7. Add to `.gitignore`
187
+
188
+ ```
189
+ data/ta-visits.jsonl
190
+ data/ta-citations.jsonl
191
+ data/ta-cache/
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Dashboard
197
+
198
+ Visit `/third-audience/` on your site. Protected by `THIRD_AUDIENCE_SECRET` (HTTP Basic or Bearer token).
199
+
200
+ | Page | URL | Description |
201
+ |---|---|---|
202
+ | Bot Analytics | `/third-audience/` | Hero metrics, daily visits chart, top bots, top pages |
203
+ | LLM Traffic | `/third-audience/citations` | Citations per platform, country, page |
204
+ | Bot Management | `/third-audience/bots` | Known bots list, allowlist/blocklist editor |
205
+ | System Health | `/third-audience/health` | Node version, file status, cache stats, quick links |
206
+
207
+ ---
208
+
209
+ ## Endpoints
210
+
211
+ | Endpoint | Description |
212
+ |---|---|
213
+ | `GET /your-page.md` | Clean Markdown of any MDX file |
214
+ | `Accept: text/markdown` | Auto-serves Markdown to AI crawlers |
215
+ | `GET /llms.txt` | AI content index from frontmatter |
216
+ | `GET /sitemap-ai.xml` | AI sitemap with metadata |
217
+ | `GET /okf/` | OKF bundle index |
218
+ | `GET /okf/[slug].md` | Individual page as Markdown |
219
+ | `POST /api/third-audience/citation` | Client-side citation report receiver |
220
+
221
+ ---
222
+
223
+ ## CLI
224
+
225
+ ```bash
226
+ npx third-audience init # interactive setup wizard
227
+ npx third-audience health # system health check
228
+ npx third-audience export # export visits as CSV
229
+ npx third-audience export citations # export citations as CSV
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Configuration
235
+
236
+ ```ts
237
+ withThirdAudience({
238
+ contentDir: 'content', // .mdx source directory (default: 'content')
239
+ dataDir: 'data', // analytics log directory (default: 'data')
240
+ dashboard: true, // mount /third-audience/ (default: true)
241
+ dashboardSecret: '...', // protect dashboard with a secret
242
+ notifications: {
243
+ email: { smtp: '...', to: '...' },
244
+ slack: { webhookUrl: '...' },
245
+ },
246
+ bots: {
247
+ allowlist: [], // always track these UAs
248
+ blocklist: [], // never track these UAs
249
+ },
250
+ cache: {
251
+ ttl: 3600, // cache TTL in seconds (default: 3600)
252
+ maxMemoryEntries: 500, // max in-memory entries (default: 500)
253
+ },
254
+ })
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Data Files
260
+
261
+ ```
262
+ data/
263
+ ta-visits.jsonl # one JSON line per bot visit
264
+ ta-citations.jsonl # one JSON line per citation click
265
+ ta-bots-config.json # allowlist/blocklist overrides (edited via dashboard)
266
+ ta-cache/ # rendered Markdown cache files
267
+ ```
268
+
269
+ Each line in `ta-visits.jsonl`:
270
+ ```json
271
+ { "timestamp": "2026-06-20T10:00:00Z", "bot_name": "ClaudeBot", "bot_category": "ai_crawler", "confidence": "high", "url": "/blog/my-post", "country": "US", "response_ms": 42, "cache_hit": true }
272
+ ```
273
+
274
+ Each line in `ta-citations.jsonl`:
275
+ ```json
276
+ { "timestamp": "2026-06-20T10:00:00Z", "platform": "ChatGPT", "query": "how to do X", "url": "/blog/my-post", "country": "US" }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Detected AI Crawlers
282
+
283
+ ClaudeBot, GPTBot, ChatGPT-User, PerplexityBot, Googlebot-AI (Google-Extended), Applebot-Extended, CCBot, CohereCrawler, AI2Bot, Bytespider, Diffbot, YouBot, FacebookBot — plus heuristic detection for unknown bots.
284
+
285
+ ---
286
+
287
+ ## Project Structure
288
+
289
+ ```
290
+ src/
291
+ core/ MDX reader, Markdown renderer (JSX stripper), middleware, Next.js config wrapper
292
+ detection/ Bot detection pipeline — known patterns → heuristics → auto-learner
293
+ analytics/ Visit tracker, geolocation (geoip-lite), performance stats
294
+ citations/ Citation tracker, citation alerts
295
+ cache/ Two-tier cache (memory LRU + file system)
296
+ discovery/ llms.txt generator, sitemap-ai generator
297
+ okf/ OKF bundle — index + per-page + internal link rewriting
298
+ dashboard/ Auth, API route handlers, React UI pages and components
299
+ cli/ init, health, export commands
300
+ public/ citation-tracker.js (client-side, framework-agnostic)
301
+ nextjs-app/ Ready-to-copy Next.js App Router files
302
+ ```
303
+
package/WORKLOG.md ADDED
@@ -0,0 +1,162 @@
1
+ # Work Log
2
+
3
+ Newest entries at top.
4
+
5
+ ---
6
+
7
+ ## 2026-06-21 (session 6 — git init + push)
8
+
9
+ **Asked:** Commit and merge to https://github.com/pooran/third-audience-mdx-plugin
10
+
11
+ **Done:**
12
+ - `git init` — project had no git repo yet
13
+ - Added remote `origin` pointing to the GitHub URL
14
+ - Staged all 80 project files (excluded `data/` per .gitignore)
15
+ - Created initial commit with full Next.js npm package
16
+ - Fetched remote `main` (had existing WP plugin history)
17
+ - Resolved merge conflicts in README.md and INSTALLATION.md by keeping our Next.js versions (`git checkout --ours`)
18
+ - Pushed merged history to `https://github.com/pooran/third-audience-mdx-plugin`
19
+
20
+ **Files touched:** .git/ (created), all tracked files now in git
21
+
22
+ ---
23
+
24
+ ## 2026-06-21 (session 5 — API key + settings page)
25
+
26
+ **Asked:** Check how WP plugin handles auth, implement same approach.
27
+
28
+ **Learned from WP plugin:**
29
+ - WP uses `current_user_can('manage_options')` — relies entirely on WP's own login, no custom login page
30
+ - For headless/external API calls: `X-TA-Api-Key` header, key stored encrypted in `wp_options` using AES-256 keyed on `SECURE_AUTH_KEY`
31
+ - Settings page shows masked key with "Copy" clipboard button and "Regenerate" button
32
+ - Nonce verification on all AJAX/state-changing requests
33
+
34
+ **What we added to match:**
35
+ - `admin-store.ts`: AES-256-GCM encrypted API key field in `ta-admin.json`; `generateApiKey()`, `getApiKey()`, `rotateApiKey()`, `verifyApiKey()`; `initAdmin()` now also generates API key on first setup
36
+ - `auth.ts`: Replaced `checkDashboardAuth` with `checkApiAuth()` — accepts `X-TA-Api-Key` header, `Authorization: Bearer`, or valid session cookie (in that order). Added `unauthorizedResponse()` helper.
37
+ - `routes/api-key-route.ts`: GET returns masked key; POST `{action:"rotate"}` rotates and returns new key once
38
+ - `ui/pages/SettingsPage.tsx`: API key display (masked), copy button, rotate button (with confirm dialog), change password link; mirrors WP settings page pattern
39
+ - `ui/components/Sidebar.tsx`: Added Settings nav item with gear icon
40
+ - Consumer files: `app/api/third-audience/api-key/route.ts`, `app/third-audience/settings/page.tsx`
41
+ - `package.json` + `tsup.config.ts`: exports and build entries updated
42
+
43
+ **Files touched:** src/dashboard/admin-store.ts, auth.ts, routes/api-key-route.ts, ui/pages/SettingsPage.tsx, ui/components/Sidebar.tsx, nextjs-app/app/api/third-audience/api-key/route.ts, nextjs-app/app/third-audience/settings/page.tsx, package.json, tsup.config.ts
44
+
45
+ ---
46
+
47
+ ## 2026-06-21 (session 5 — admin auth)
48
+
49
+ **Asked:** Dashboard should require login. Default password `Chang3M3Now!`. Force reset on first login. `/setup-ta-admin` slash command.
50
+
51
+ **Done:**
52
+ - `src/dashboard/admin-store.ts` — password hashing (SHA-256 keyed on secret), HMAC-signed session tokens, `initAdmin()` / `verifyPassword()` / `updatePassword()` / `signSession()` / `verifySession()`
53
+ - Default password hardcoded as `Chang3M3Now!`, `isDefaultPassword: true` until changed
54
+ - `src/dashboard/routes/login-route.ts` — GET: login form HTML; POST: verify password → set HttpOnly cookie; force-reset redirect if default password still active; reset form validates length + confirmation
55
+ - `src/core/middleware.ts` — auth guard on all `/third-audience/*` except `/login`; rewrite `/third-audience/login` → API handler
56
+ - `src/nextjs-app/app/third-audience/layout.tsx` — red sticky warning banner when `isDefaultPassword: true`
57
+ - `src/nextjs-app/app/api/third-audience/login/route.ts` — consumer route re-export
58
+ - `package.json` — added `routes/login` and `dashboard/admin-store` exports
59
+ - `tsup.config.ts` — added `admin-store.ts` to build entries
60
+ - `.gitignore` — added `data/ta-admin.json`
61
+ - `plugin/skills/setup-ta-admin/SKILL.md` — `/setup-ta-admin` slash command: 7-step checklist, init script, credential printout, verify curl, security notes
62
+ - `INSTALLATION.md` — added Step 9 for admin login setup
63
+
64
+ **Files touched:** src/dashboard/admin-store.ts, routes/login-route.ts, core/middleware.ts, nextjs-app/app/third-audience/layout.tsx, nextjs-app/app/api/third-audience/login/route.ts, package.json, tsup.config.ts, .gitignore, plugin/skills/setup-ta-admin/SKILL.md, INSTALLATION.md
65
+
66
+ ---
67
+
68
+ ## 2026-06-21 (session 5 — continued)
69
+
70
+ **Asked:** Make the plugin work for other LLMs (Perplexity, Codex).
71
+
72
+ **Done:**
73
+ - Added `.codex-plugin/plugin.json` — Codex/Perplexity manifest with full interface metadata
74
+ - Added `.cursor-plugin/plugin.json` — Cursor manifest
75
+ - Added `.kimi-plugin/plugin.json` — Kimi Code manifest with skillInstructions tool mapping
76
+ - Added `gemini-extension.json` + `GEMINI.md` — Gemini CLI support (loads skills via @-includes)
77
+ - Added `hooks/hooks.json`, `hooks-codex.json`, `hooks-cursor.json` — empty hook stubs required by runtimes
78
+ - Added `AGENTS.md` → symlink to `CLAUDE.md` (OpenAI Codex/Perplexity context file convention)
79
+ - Updated `plugin/README.md` with runtime support table
80
+
81
+ **Files touched:** plugin/.codex-plugin/plugin.json, .cursor-plugin/plugin.json, .kimi-plugin/plugin.json, gemini-extension.json, GEMINI.md, AGENTS.md, hooks/*, README.md
82
+
83
+ ---
84
+
85
+ ## 2026-06-21 (session 5)
86
+
87
+ **Asked:** Package as a Claude Code plugin. Update README/plugin with "third audience" origin story from the original repo (remove WordPress references).
88
+
89
+ **Done:**
90
+ - Built Claude Code plugin at `plugin/` — package.json with `pi.skills`, CLAUDE.md, README.md
91
+ - Wrote 3 skills: `setup-third-audience` (11-step install wizard), `add-ai-route` (detect + create missing routes), `debug-bot-detection` (full pipeline diagnosis with curl test matrix)
92
+ - Fetched origin story from https://github.com/spcaeo/third-audience-wordpress-plugin: "For two decades, websites were built for two audiences: humans and search engines. Today there is a third: AI agents and LLM crawlers."
93
+ - Updated README.md header with origin concept + GEO framing, removing old tagline
94
+ - Updated plugin/README.md and plugin/CLAUDE.md with same concept, no WordPress references
95
+
96
+ **Files touched:** plugin/package.json, plugin/CLAUDE.md, plugin/README.md, plugin/skills/setup-third-audience/SKILL.md, plugin/skills/add-ai-route/SKILL.md, plugin/skills/debug-bot-detection/SKILL.md, README.md
97
+
98
+ ---
99
+
100
+ ## 2026-06-20 (session 4)
101
+
102
+ **Asked:** Update README and INSTALLATION.md. Remove WordPress plugin references. Delete wordpress-plugin/. Add attribution to original repo.
103
+
104
+ **Done:**
105
+ - Rewrote README.md — full features list, complete manual setup section, all API routes, dashboard pages, CLI, config reference, data schema, detected bots list, project structure
106
+ - Rewrote INSTALLATION.md — 10-step guide, troubleshooting section, final directory structure
107
+ - Removed "Differences from the WordPress Plugin" table from README
108
+ - Added attribution to https://github.com/spcaeo/third-audience-wordpress-plugin
109
+ - wordpress-plugin/ deleted (user ran rm -rf manually)
110
+
111
+ **Files touched:** README.md, INSTALLATION.md
112
+
113
+ ---
114
+
115
+ ## 2026-06-20 (session 3)
116
+
117
+ **Asked:** Complete the MDX plugin for Next.js — dashboard UI, consumer route files, sitemap route.
118
+
119
+ **Done:**
120
+ - Built 4 dashboard React pages (Next.js App Router server components): BotAnalyticsPage, LlmTrafficPage, BotManagementPage, SystemHealthPage
121
+ - Built shared UI components: Sidebar, Card, HeroCard, VisitsChart (SVG bar chart)
122
+ - Built Apple-inspired design system CSS (globals.css, 200+ lines)
123
+ - Added missing sitemap-ai route handler (src/dashboard/routes/sitemap-ai-route.ts)
124
+ - Created full Next.js consumer app structure (src/nextjs-app/) with all route.ts and page.tsx files
125
+ - Added bots-config API route (GET/POST for allowlist/blocklist)
126
+ - Added dashboard layout.tsx with Sidebar + CSS import
127
+ - Expanded package.json exports map (16 subpath exports)
128
+ - Added tsup.config.ts for JSX + CSS build
129
+ - Created INSTALLATION.md with step-by-step setup guide
130
+
131
+ **Files touched:** src/dashboard/ui/globals.css, components/Sidebar.tsx, Card.tsx, HeroCard.tsx, VisitsChart.tsx, pages/BotAnalyticsPage.tsx, LlmTrafficPage.tsx, BotManagementPage.tsx, SystemHealthPage.tsx, routes/sitemap-ai-route.ts, src/nextjs-app/app/** (12 files), package.json, tsup.config.ts, INSTALLATION.md
132
+
133
+ ---
134
+
135
+ ## 2026-06-20 (session 2)
136
+
137
+ **Asked:** Clone WordPress plugin, move to /wordpress-plugin, create MDX-ready plugin.
138
+
139
+ **Done:**
140
+ - Cloned https://github.com/pooran/third-audience-mdx-plugin (123 files) into `wordpress-plugin/`
141
+ - Scaffolded full MDX npm package structure in `src/`
142
+ - Wrote all core modules: config, middleware, mdx-reader, markdown-renderer, bot-detection-pipeline, known-patterns, visit-tracker, geolocation, performance-stats, citation-tracker, citation-alerts, cache-manager, llms-txt, sitemap-ai, okf-bundle, all route handlers, auth, CLI (init/health/export), client citation-tracker.js
143
+ - Created package.json, tsconfig.json, .gitignore, README.md
144
+ - Wrote spec doc at docs/superpowers/specs/2026-06-20-third-audience-mdx-design.md
145
+
146
+ **Files touched:** wordpress-plugin/ (123 files), src/ (30+ TypeScript files), package.json, tsconfig.json, .gitignore, README.md, docs/superpowers/specs/2026-06-20-third-audience-mdx-design.md
147
+
148
+ **Still pending:** Dashboard React UI pages (Bot Analytics, LLM Traffic, Bot Management, System Health), sitemap-ai route handler, Next.js app/api route files for consumers.
149
+
150
+ ---
151
+
152
+ ## 2026-06-20
153
+
154
+ **Asked:** Set up project tracking — CLAUDE.md, WORKLOG.md, memory entries.
155
+
156
+ **Done:**
157
+ - Created CLAUDE.md with: scope rules, worklog note, global rules, Execution (ultracode & workflows) section, and Discipline (verify, don't guess) section.
158
+ - Created WORKLOG.md (this file).
159
+ - Wrote memory entries: project-scope, ultracode-workflows reference, and verify-dont-guess behavior.
160
+ - Added all three entries to MEMORY.md index.
161
+
162
+ **Files touched:** CLAUDE.md, WORKLOG.md, /Users/pooran/.claude/projects/-Users-pooran-Downloads-Com-N-third/memory/project_scope.md, ultracode_workflows.md, verify_dont_guess.md, MEMORY.md
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/commands/init.ts
27
+ var import_fs = __toESM(require("fs"));
28
+ var import_path = __toESM(require("path"));
29
+ var import_readline = __toESM(require("readline"));
30
+ async function init() {
31
+ const cwd = process.cwd();
32
+ console.log("\n\u{1F3AF} third-audience-mdx setup\n");
33
+ const pkgPath = import_path.default.join(cwd, "package.json");
34
+ if (!import_fs.default.existsSync(pkgPath)) {
35
+ console.error("No package.json found. Run this from your Next.js project root.");
36
+ process.exit(1);
37
+ }
38
+ const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
39
+ if (!pkg.dependencies?.next && !pkg.devDependencies?.next) {
40
+ console.warn("\u26A0 next not found in package.json \u2014 make sure this is a Next.js project.");
41
+ }
42
+ const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
43
+ const ask = (q, def) => new Promise(
44
+ (r) => rl.question(`${q} (${def}): `, (ans) => r(ans.trim() || def))
45
+ );
46
+ const contentDir = await ask("Content directory (where your .mdx files live)", "content");
47
+ const dataDir = await ask("Data directory (for analytics logs)", "data");
48
+ const secret = await ask("Dashboard secret (leave blank to disable auth in dev)", "");
49
+ rl.close();
50
+ const middlewarePath = import_path.default.join(cwd, "middleware.ts");
51
+ if (!import_fs.default.existsSync(middlewarePath)) {
52
+ import_fs.default.writeFileSync(middlewarePath, `export { thirdAudienceMiddleware as middleware } from 'third-audience-mdx'
53
+ export const config = { matcher: ['/((?!_next|api).*)'] }
54
+ `);
55
+ console.log("\u2705 Created middleware.ts");
56
+ } else {
57
+ console.log("\u26A0 middleware.ts already exists \u2014 add thirdAudienceMiddleware manually.");
58
+ }
59
+ const envPath = import_path.default.join(cwd, ".env.local");
60
+ const envLines = [`THIRD_AUDIENCE_SECRET=${secret}`, `NEXT_PUBLIC_SITE_URL=http://localhost:3000`];
61
+ const envContent = envLines.join("\n") + "\n";
62
+ if (!import_fs.default.existsSync(envPath)) {
63
+ import_fs.default.writeFileSync(envPath, envContent);
64
+ console.log("\u2705 Created .env.local");
65
+ } else {
66
+ import_fs.default.appendFileSync(envPath, "\n# Third Audience\n" + envContent);
67
+ console.log("\u2705 Appended to .env.local");
68
+ }
69
+ import_fs.default.mkdirSync(import_path.default.join(cwd, dataDir, "ta-cache"), { recursive: true });
70
+ const gitignorePath = import_path.default.join(cwd, ".gitignore");
71
+ const gitignoreAdditions = `
72
+ # Third Audience analytics (local only)
73
+ ${dataDir}/ta-visits.jsonl
74
+ ${dataDir}/ta-citations.jsonl
75
+ ${dataDir}/ta-cache/
76
+ `;
77
+ if (import_fs.default.existsSync(gitignorePath)) {
78
+ import_fs.default.appendFileSync(gitignorePath, gitignoreAdditions);
79
+ } else {
80
+ import_fs.default.writeFileSync(gitignorePath, gitignoreAdditions.trimStart());
81
+ }
82
+ console.log(`\u2705 Created ${dataDir}/ and updated .gitignore`);
83
+ console.log(`
84
+ \u2705 Setup complete!
85
+
86
+ Next steps:
87
+ 1. Add to next.config.ts:
88
+ import { withThirdAudience } from 'third-audience-mdx'
89
+ export default withThirdAudience({ contentDir: '${contentDir}', dataDir: '${dataDir}' })
90
+
91
+ 2. Add API routes in app/api/third-audience/ (see docs)
92
+
93
+ 3. Visit /third-audience/ for your dashboard
94
+ `);
95
+ }
96
+
97
+ // src/cli/commands/health.ts
98
+ var import_fs2 = __toESM(require("fs"));
99
+ var import_path2 = __toESM(require("path"));
100
+ async function health() {
101
+ const cwd = process.cwd();
102
+ const checks = [];
103
+ const nodeVersion = process.versions.node;
104
+ const [nodeMajor] = nodeVersion.split(".").map(Number);
105
+ checks.push({ label: `Node.js ${nodeVersion}`, ok: nodeMajor >= 18, note: nodeMajor < 18 ? "requires Node 18+" : void 0 });
106
+ const pkgPath = import_path2.default.join(cwd, "package.json");
107
+ checks.push({ label: "package.json", ok: import_fs2.default.existsSync(pkgPath) });
108
+ const nextPath = import_path2.default.join(cwd, "node_modules", "next");
109
+ checks.push({ label: "next installed", ok: import_fs2.default.existsSync(nextPath) });
110
+ const middlewarePath = import_path2.default.join(cwd, "middleware.ts");
111
+ checks.push({ label: "middleware.ts", ok: import_fs2.default.existsSync(middlewarePath) });
112
+ const contentDir = process.env.TA_CONTENT_DIR ?? "content";
113
+ const contentPath = import_path2.default.join(cwd, contentDir);
114
+ const contentExists = import_fs2.default.existsSync(contentPath);
115
+ const mdxCount = contentExists ? countFiles(contentPath, [".mdx", ".md"]) : 0;
116
+ checks.push({ label: `contentDir (${contentDir})`, ok: contentExists, note: contentExists ? `${mdxCount} MDX files` : "directory not found" });
117
+ const dataDir = process.env.TA_DATA_DIR ?? "data";
118
+ const dataPath = import_path2.default.join(cwd, dataDir);
119
+ checks.push({ label: `dataDir (${dataDir})`, ok: import_fs2.default.existsSync(dataPath) });
120
+ const visitsPath = import_path2.default.join(cwd, dataDir, "ta-visits.jsonl");
121
+ const citationsPath = import_path2.default.join(cwd, dataDir, "ta-citations.jsonl");
122
+ const visitLines = import_fs2.default.existsSync(visitsPath) ? countLines(visitsPath) : 0;
123
+ const citationLines = import_fs2.default.existsSync(citationsPath) ? countLines(citationsPath) : 0;
124
+ checks.push({ label: "ta-visits.jsonl", ok: true, note: `${visitLines} records` });
125
+ checks.push({ label: "ta-citations.jsonl", ok: true, note: `${citationLines} records` });
126
+ checks.push({ label: "THIRD_AUDIENCE_SECRET", ok: !!process.env.THIRD_AUDIENCE_SECRET, note: !process.env.THIRD_AUDIENCE_SECRET ? "not set (dashboard is open)" : "set" });
127
+ console.log("\n\u{1F3E5} third-audience health check\n");
128
+ for (const c of checks) {
129
+ const icon = c.ok ? "\u2705" : "\u274C";
130
+ const note = c.note ? ` (${c.note})` : "";
131
+ console.log(` ${icon} ${c.label}${note}`);
132
+ }
133
+ console.log();
134
+ }
135
+ function countFiles(dir, exts) {
136
+ let count = 0;
137
+ for (const entry of import_fs2.default.readdirSync(dir, { withFileTypes: true })) {
138
+ if (entry.isDirectory()) count += countFiles(import_path2.default.join(dir, entry.name), exts);
139
+ else if (exts.some((e) => entry.name.endsWith(e))) count++;
140
+ }
141
+ return count;
142
+ }
143
+ function countLines(filePath) {
144
+ return import_fs2.default.readFileSync(filePath, "utf-8").split("\n").filter(Boolean).length;
145
+ }
146
+
147
+ // src/cli/commands/export.ts
148
+ var import_fs3 = __toESM(require("fs"));
149
+ var import_path3 = __toESM(require("path"));
150
+ async function exportData(args2) {
151
+ const dataDir = process.env.TA_DATA_DIR ?? "data";
152
+ const type = args2[0] ?? "visits";
153
+ const outPath = args2[1] ?? `ta-${type}-export.csv`;
154
+ const file = type === "citations" ? "ta-citations.jsonl" : "ta-visits.jsonl";
155
+ const jsonlPath = import_path3.default.join(process.cwd(), dataDir, file);
156
+ if (!import_fs3.default.existsSync(jsonlPath)) {
157
+ console.error(`No data found at ${jsonlPath}`);
158
+ process.exit(1);
159
+ }
160
+ const records = import_fs3.default.readFileSync(jsonlPath, "utf-8").split("\n").filter(Boolean).map((l) => {
161
+ try {
162
+ return JSON.parse(l);
163
+ } catch {
164
+ return null;
165
+ }
166
+ }).filter(Boolean);
167
+ if (records.length === 0) {
168
+ console.log("No records to export.");
169
+ return;
170
+ }
171
+ const headers = Object.keys(records[0]);
172
+ const csv = [
173
+ headers.join(","),
174
+ ...records.map((r) => headers.map((h) => csvCell(r[h])).join(","))
175
+ ].join("\n") + "\n";
176
+ import_fs3.default.writeFileSync(outPath, csv);
177
+ console.log(`\u2705 Exported ${records.length} records to ${outPath}`);
178
+ }
179
+ function csvCell(val) {
180
+ if (val === null || val === void 0) return "";
181
+ const s = String(val);
182
+ if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
183
+ return s;
184
+ }
185
+
186
+ // src/cli/index.ts
187
+ var [, , command, ...args] = process.argv;
188
+ switch (command) {
189
+ case "init":
190
+ init().catch(console.error);
191
+ break;
192
+ case "health":
193
+ health().catch(console.error);
194
+ break;
195
+ case "export":
196
+ exportData(args).catch(console.error);
197
+ break;
198
+ default:
199
+ console.log(`third-audience CLI
200
+
201
+ Commands:
202
+ init Set up third-audience-mdx in your Next.js project
203
+ health Show system health status
204
+ export Export analytics data as CSV
205
+
206
+ Usage: npx third-audience <command>`);
207
+ }
208
+ //# sourceMappingURL=index.js.map