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.
- package/CLAUDE.md +41 -0
- package/INSTALLATION.md +367 -0
- package/README.md +303 -0
- package/WORKLOG.md +162 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +185 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/dashboard/auth.d.mts +16 -0
- package/dist/dashboard/auth.d.ts +16 -0
- package/dist/dashboard/auth.js +123 -0
- package/dist/dashboard/auth.js.map +1 -0
- package/dist/dashboard/auth.mjs +87 -0
- package/dist/dashboard/auth.mjs.map +1 -0
- package/dist/dashboard/routes/analytics-api-route.d.mts +6 -0
- package/dist/dashboard/routes/analytics-api-route.d.ts +6 -0
- package/dist/dashboard/routes/analytics-api-route.js +180 -0
- package/dist/dashboard/routes/analytics-api-route.js.map +1 -0
- package/dist/dashboard/routes/analytics-api-route.mjs +145 -0
- package/dist/dashboard/routes/analytics-api-route.mjs.map +1 -0
- package/dist/dashboard/routes/api-key-route.d.mts +8 -0
- package/dist/dashboard/routes/api-key-route.d.ts +8 -0
- package/dist/dashboard/routes/api-key-route.js +173 -0
- package/dist/dashboard/routes/api-key-route.js.map +1 -0
- package/dist/dashboard/routes/api-key-route.mjs +137 -0
- package/dist/dashboard/routes/api-key-route.mjs.map +1 -0
- package/dist/dashboard/routes/citation-route.d.mts +14 -0
- package/dist/dashboard/routes/citation-route.d.ts +14 -0
- package/dist/dashboard/routes/citation-route.js +202 -0
- package/dist/dashboard/routes/citation-route.js.map +1 -0
- package/dist/dashboard/routes/citation-route.mjs +166 -0
- package/dist/dashboard/routes/citation-route.mjs.map +1 -0
- package/dist/dashboard/routes/llms-txt-route.d.mts +6 -0
- package/dist/dashboard/routes/llms-txt-route.d.ts +6 -0
- package/dist/dashboard/routes/llms-txt-route.js +119 -0
- package/dist/dashboard/routes/llms-txt-route.js.map +1 -0
- package/dist/dashboard/routes/llms-txt-route.mjs +84 -0
- package/dist/dashboard/routes/llms-txt-route.mjs.map +1 -0
- package/dist/dashboard/routes/login-route.d.mts +6 -0
- package/dist/dashboard/routes/login-route.d.ts +6 -0
- package/dist/dashboard/routes/login-route.js +313 -0
- package/dist/dashboard/routes/login-route.js.map +1 -0
- package/dist/dashboard/routes/login-route.mjs +284 -0
- package/dist/dashboard/routes/login-route.mjs.map +1 -0
- package/dist/dashboard/routes/markdown-route.d.mts +15 -0
- package/dist/dashboard/routes/markdown-route.d.ts +15 -0
- package/dist/dashboard/routes/markdown-route.js +239 -0
- package/dist/dashboard/routes/markdown-route.js.map +1 -0
- package/dist/dashboard/routes/markdown-route.mjs +204 -0
- package/dist/dashboard/routes/markdown-route.mjs.map +1 -0
- package/dist/dashboard/routes/okf-route.d.mts +13 -0
- package/dist/dashboard/routes/okf-route.d.ts +13 -0
- package/dist/dashboard/routes/okf-route.js +184 -0
- package/dist/dashboard/routes/okf-route.js.map +1 -0
- package/dist/dashboard/routes/okf-route.mjs +149 -0
- package/dist/dashboard/routes/okf-route.mjs.map +1 -0
- package/dist/dashboard/routes/sitemap-ai-route.d.mts +6 -0
- package/dist/dashboard/routes/sitemap-ai-route.d.ts +6 -0
- package/dist/dashboard/routes/sitemap-ai-route.js +134 -0
- package/dist/dashboard/routes/sitemap-ai-route.js.map +1 -0
- package/dist/dashboard/routes/sitemap-ai-route.mjs +99 -0
- package/dist/dashboard/routes/sitemap-ai-route.mjs.map +1 -0
- package/dist/dashboard/ui/components/Sidebar.d.mts +5 -0
- package/dist/dashboard/ui/components/Sidebar.d.ts +5 -0
- package/dist/dashboard/ui/components/Sidebar.js +102 -0
- package/dist/dashboard/ui/components/Sidebar.js.map +1 -0
- package/dist/dashboard/ui/components/Sidebar.mjs +68 -0
- package/dist/dashboard/ui/components/Sidebar.mjs.map +1 -0
- package/dist/dashboard/ui/globals.css +175 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.d.mts +5 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.d.ts +5 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.js +269 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.js.map +1 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs +232 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/BotManagementPage.d.mts +13 -0
- package/dist/dashboard/ui/pages/BotManagementPage.d.ts +13 -0
- package/dist/dashboard/ui/pages/BotManagementPage.js +177 -0
- package/dist/dashboard/ui/pages/BotManagementPage.js.map +1 -0
- package/dist/dashboard/ui/pages/BotManagementPage.mjs +153 -0
- package/dist/dashboard/ui/pages/BotManagementPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.d.mts +5 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.d.ts +5 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.js +203 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.js.map +1 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.mjs +168 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/SettingsPage.d.mts +8 -0
- package/dist/dashboard/ui/pages/SettingsPage.d.ts +8 -0
- package/dist/dashboard/ui/pages/SettingsPage.js +181 -0
- package/dist/dashboard/ui/pages/SettingsPage.js.map +1 -0
- package/dist/dashboard/ui/pages/SettingsPage.mjs +157 -0
- package/dist/dashboard/ui/pages/SettingsPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.d.mts +5 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.d.ts +5 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.js +183 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.js.map +1 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.mjs +148 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.mjs.map +1 -0
- package/dist/index.d.mts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +372 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +346 -0
- package/dist/index.mjs.map +1 -0
- 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
|