seomd-cli 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.
@@ -0,0 +1,372 @@
1
+ # SEO.md
2
+
3
+ ## {{brand}}
4
+
5
+ ### spec v1.0 | <https://seomd.dev>
6
+
7
+ #### generated: {{date}}
8
+
9
+ ## FIELD OWNERSHIP
10
+
11
+ ### no prefix = founder declares (you own this)
12
+
13
+ ### _analysis: = platform writes back (do not edit manually)
14
+
15
+ ## Site
16
+
17
+ site:
18
+ type: saas
19
+ domain: {{domain}}
20
+ canonical: https://{{domain}}
21
+ locale: en-US
22
+ launched: null # YYYY-MM-DD
23
+
24
+ ## Identity
25
+
26
+ identity:
27
+ brand: "{{brand}}"
28
+ tagline: null
29
+ social:
30
+ twitter: null
31
+ linkedin: null
32
+ github: null
33
+ schema_org_type: SoftwareApplication
34
+
35
+ ## Keywords
36
+
37
+ keywords:
38
+
39
+ ### FOUNDER DECLARES
40
+
41
+ primary: "{{primary_keyword}}"
42
+ secondary: [] # add your secondary keywords
43
+ negative: # terms that dilute your intent signal
44
+ - "free"
45
+ - "tutorial"
46
+ - "how to"
47
+ competitor_terms:
48
+ {{competitor_terms}}
49
+ category_terms: # unbranded category queries
50
+ - "best {{primary_keyword}}"
51
+ - "top {{primary_keyword}} tools"
52
+ long_tail: [] # add long-tail variations
53
+ seasonal: null # add seasonal terms if applicable
54
+
55
+ ### PLATFORM WRITES BACK
56
+
57
+ _analysis:
58
+ source: null
59
+ primary_search_volume: null
60
+ primary_intent_type: null
61
+ primary_trend: null
62
+ recommended_secondary: []
63
+ negative_additions_suggested: []
64
+ last_analyzed: null
65
+ next_analysis: null
66
+
67
+ ## Intent
68
+
69
+ intent:
70
+
71
+ ### FOUNDER DECLARES
72
+
73
+ #### Add queries your buyers actually type into AI engines
74
+
75
+ #### Tip: think about what someone asks ChatGPT or Perplexity
76
+
77
+ #### when they are looking for a solution like yours
78
+
79
+ informational:
80
+ priority: medium
81
+ queries:
82
+ - "what is {{primary_keyword}}"
83
+ - "how does {{primary_keyword}} work"
84
+
85
+ comparison:
86
+ priority: high
87
+ queries:
88
+ {{competitors_comparison_queries}}
89
+ - "best {{primary_keyword}} for startups"
90
+
91
+ transactional:
92
+ priority: high
93
+ queries:
94
+ - "is {{brand_lower}} worth it"
95
+ - "should I use {{brand_lower}}"
96
+ - "{{brand_lower}} pricing"
97
+
98
+ reputational:
99
+ priority: medium
100
+ queries:
101
+ - "{{brand_lower}} reviews"
102
+ - "is {{brand_lower}} legit"
103
+ - "{{brand_lower}} problems"
104
+
105
+ category:
106
+ priority: critical
107
+ queries:
108
+ - "best {{primary_keyword}}"
109
+ - "top {{primary_keyword}} 2026"
110
+
111
+ ### PLATFORM WRITES BACK
112
+
113
+ _analysis:
114
+ source: null
115
+ last_analyzed: null
116
+ next_analysis: null
117
+ informational:
118
+ citation_rate: null
119
+ top_cited_competitor: null
120
+ gap_score: null
121
+ trend: null
122
+ comparison:
123
+ citation_rate: null
124
+ top_cited_competitor: null
125
+ gap_score: null
126
+ trend: null
127
+ transactional:
128
+ citation_rate: null
129
+ top_cited_competitor: null
130
+ gap_score: null
131
+ trend: null
132
+ reputational:
133
+ citation_rate: null
134
+ top_cited_competitor: null
135
+ gap_score: null
136
+ trend: null
137
+ category:
138
+ citation_rate: null
139
+ top_cited_competitor: null
140
+ gap_score: null
141
+ trend: null
142
+
143
+ ## Pages
144
+
145
+ pages:
146
+ site_type: saas
147
+
148
+ ### FOUNDER DECLARES
149
+
150
+ #### status: live | draft | planned
151
+
152
+ required:
153
+ - id: homepage
154
+ url: /
155
+ primary_keyword: null
156
+ status: planned
157
+ priority: 1
158
+
159
+ - id: features
160
+ url: /features
161
+ primary_keyword: null
162
+ status: planned
163
+ priority: 2
164
+
165
+ - id: pricing
166
+ url: /pricing
167
+ primary_keyword: null
168
+ status: planned
169
+ priority: 3
170
+
171
+ - id: comparison
172
+ url: /vs/[competitor]
173
+ primary_keyword: null
174
+ status: planned
175
+ priority: 4
176
+
177
+ - id: alternatives
178
+ url: /alternatives/[competitor]
179
+ primary_keyword: null
180
+ status: planned
181
+ priority: 5
182
+
183
+ - id: use-cases
184
+ url: /for/[segment]
185
+ primary_keyword: null
186
+ status: planned
187
+ priority: 6
188
+
189
+ - id: integrations
190
+ url: /integrations
191
+ primary_keyword: null
192
+ status: planned
193
+ priority: 7
194
+
195
+ - id: changelog
196
+ url: /changelog
197
+ primary_keyword: null
198
+ status: planned
199
+ priority: 8
200
+
201
+ - id: docs
202
+ url: /docs
203
+ primary_keyword: null
204
+ status: planned
205
+ priority: 9
206
+
207
+ - id: blog
208
+ url: /blog
209
+ primary_keyword: null
210
+ status: planned
211
+ priority: 10
212
+
213
+ ### PLATFORM WRITES BACK
214
+
215
+ _analysis:
216
+ source: null
217
+ last_analyzed: null
218
+ pages: []
219
+ missing_pages: []
220
+ build_order_recommendation: []
221
+
222
+ ## Copy
223
+
224
+ copy:
225
+
226
+ ### FOUNDER DECLARES
227
+
228
+ h1_contains_primary_keyword: true
229
+ meta_description_length: 150-160
230
+ meta_description_includes_cta: true
231
+ min_word_count:
232
+ homepage: 800
233
+ feature_page: 600
234
+ blog_post: 1200
235
+ comparison_page: 1500
236
+ reading_level: 8 # grade level target
237
+
238
+ ## Structure
239
+
240
+ structure:
241
+
242
+ ### FOUNDER DECLARES
243
+
244
+ answer_first: true # direct answer in first 50 words
245
+ faq_section_required: true # on all key pages
246
+ faq_minimum_questions: 6
247
+ statistics_per_page: 2 # minimum data points with sources
248
+ citations_required: true # link to primary sources
249
+ short_paragraphs: true # max 3 sentences
250
+ heading_hierarchy: strict # H1 > H2 > H3, no skipping
251
+
252
+ ## Authority
253
+
254
+ authority:
255
+
256
+ ### FOUNDER DECLARES
257
+
258
+ cite_sources: true
259
+ expert_quotes: false # set true when you have quotes
260
+ eeat_signals:
261
+ experience: null # describe your experience signal
262
+ expertise: null # describe your expertise signal
263
+ authority: null # describe your authority signal
264
+ trust: null # describe your trust signal
265
+
266
+ ## Schema
267
+
268
+ schema:
269
+
270
+ ### FOUNDER DECLARES
271
+
272
+ types:
273
+ - SoftwareApplication
274
+ - Organization
275
+ - FAQPage
276
+ - WebPage
277
+ faq_schema: true
278
+ breadcrumb_schema: true
279
+ organization_schema: true
280
+
281
+ ## Crawl
282
+
283
+ crawl:
284
+
285
+ ### FOUNDER DECLARES
286
+
287
+ sitemap: /sitemap.xml
288
+ robots_txt: /robots.txt
289
+ allow_ai_bots: true
290
+ allowed_bots:
291
+ - Googlebot
292
+ - Bingbot
293
+ - PerplexityBot
294
+ - ChatGPT-User
295
+ - GPTBot
296
+ - ClaudeBot
297
+ - anthropic-ai
298
+ - cohere-ai
299
+ disallow:
300
+ - /admin
301
+ - /checkout
302
+ - /user/*
303
+ - /api/*
304
+
305
+ ## Performance
306
+
307
+ performance:
308
+
309
+ ### FOUNDER DECLARES
310
+
311
+ lcp: 2.5s
312
+ cls: 0.1
313
+ fid: 100ms
314
+ page_size: 500kb
315
+ ttfb: 800ms
316
+
317
+ ## AEO
318
+
319
+ aeo:
320
+
321
+ ### FOUNDER DECLARES
322
+
323
+ ### AI Engine Optimization rules
324
+
325
+ answer_first_format: true
326
+ faq_on_all_key_pages: true
327
+ structured_data_priority: high
328
+ content_freshness_target: 30d # update key pages within 30 days
329
+ competitors_to_monitor:
330
+ {{competitors_to_monitor}}
331
+
332
+ ### PLATFORM WRITES BACK
333
+
334
+ _analysis:
335
+ source: null
336
+ overall_citation_rate: null
337
+ overall_gap_score: null
338
+ engines_tracked:
339
+ - chatgpt
340
+ - perplexity
341
+ - claude
342
+ - gemini
343
+ - grok
344
+ last_analyzed: null
345
+ next_analysis: null
346
+
347
+ ## Monitoring
348
+
349
+ monitoring:
350
+
351
+ ### FOUNDER DECLARES
352
+
353
+ sync_schedule: monthly # monthly | weekly | on_demand
354
+ auto_commit: false # platform commits directly to repo
355
+ pr_mode: true # open PR instead of direct commit
356
+ branch: main
357
+ alert_on_gap_score_above: 80 # alert when gap score exceeds threshold
358
+ alert_on_citation_drop: true # alert if citation rate drops
359
+
360
+ ## Platform Connection
361
+
362
+ ### Connect at <https://seomd.dev/connect>
363
+
364
+ ### Add SEOMD_API_KEY to your .env file
365
+
366
+ ### Never commit your API key to version control
367
+
368
+ platform:
369
+ provider: null # foxcite | manual | ahrefs | semrush
370
+ project_id: null
371
+
372
+ ### api_key: loaded from SEOMD_API_KEY environment variable
@@ -0,0 +1,41 @@
1
+ import axios from 'axios';
2
+ import dotenv from 'dotenv';
3
+
4
+ // Load .env configuration
5
+ dotenv.config();
6
+
7
+ const API_URL = process.env.SEOMD_API_URL || 'https://api.foxcite.com';
8
+ const API_KEY = process.env.SEOMD_API_KEY;
9
+ const PAYMENT_TOKEN = process.env.SEOMD_PAYMENT_TOKEN;
10
+ const SEOMD_DOMAIN = process.env.SEOMD_DOMAIN;
11
+
12
+ export const client = axios.create({
13
+ baseURL: API_URL,
14
+ timeout: 300000, // 5 minutes timeout for LLM audits
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ ...(API_KEY ? { 'Authorization': `Bearer ${API_KEY}` } : {}),
18
+ ...(PAYMENT_TOKEN ? { 'x-payment-token': PAYMENT_TOKEN } : {}),
19
+ ...(SEOMD_DOMAIN ? { 'x-seomd-domain': SEOMD_DOMAIN } : {})
20
+ }
21
+ });
22
+
23
+ // Interceptor for cleaner error feedback
24
+ client.interceptors.response.use(
25
+ (response) => response,
26
+ (error) => {
27
+ if (error.response) {
28
+ const status = error.response.status;
29
+ const detail = error.response.data?.detail || error.response.data?.message || error.message;
30
+
31
+ if (status === 401) {
32
+ return Promise.reject(new Error('Authentication failed: Invalid or missing API key (SEOMD_API_KEY).'));
33
+ }
34
+ if (status === 402) {
35
+ return Promise.reject(new Error(`Payment Required: Insufficient scan credits. ${detail}`));
36
+ }
37
+ return Promise.reject(new Error(`API Error (${status}): ${detail}`));
38
+ }
39
+ return Promise.reject(new Error(`Network Error: ${error.message}`));
40
+ }
41
+ );
@@ -0,0 +1,132 @@
1
+ export const SITE_TYPES = [
2
+ { name: 'SaaS / Software', value: 'saas' },
3
+ { name: 'Ecommerce', value: 'ecommerce' },
4
+ { name: 'Local Business', value: 'local' },
5
+ { name: 'Blog / Media', value: 'blog' },
6
+ { name: 'Marketplace', value: 'marketplace' },
7
+ ];
8
+
9
+ export const INTENT_CATEGORIES = [
10
+ 'informational',
11
+ 'comparison',
12
+ 'transactional',
13
+ 'reputational',
14
+ 'category',
15
+ ];
16
+
17
+ export const INTENT_PRIORITIES = {
18
+ saas: {
19
+ informational: 'medium',
20
+ comparison: 'high',
21
+ transactional: 'high',
22
+ reputational: 'medium',
23
+ category: 'critical',
24
+ },
25
+ ecommerce: {
26
+ informational: 'low',
27
+ comparison: 'high',
28
+ transactional: 'critical',
29
+ reputational: 'high',
30
+ category: 'high',
31
+ },
32
+ local: {
33
+ informational: 'medium',
34
+ comparison: 'medium',
35
+ transactional: 'critical',
36
+ reputational: 'critical',
37
+ category: 'high',
38
+ },
39
+ blog: {
40
+ informational: 'critical',
41
+ comparison: 'medium',
42
+ transactional: 'low',
43
+ reputational: 'medium',
44
+ category: 'high',
45
+ },
46
+ marketplace: {
47
+ informational: 'medium',
48
+ comparison: 'high',
49
+ transactional: 'critical',
50
+ reputational: 'high',
51
+ category: 'high',
52
+ },
53
+ };
54
+
55
+ export const REQUIRED_PAGES = {
56
+ saas: [
57
+ { id: 'homepage', url: '/', priority: 1 },
58
+ { id: 'features', url: '/features', priority: 2 },
59
+ { id: 'pricing', url: '/pricing', priority: 3 },
60
+ { id: 'comparison', url: '/vs/[competitor]', priority: 4 },
61
+ { id: 'alternatives', url: '/alternatives/[competitor]', priority: 5 },
62
+ { id: 'use-cases', url: '/for/[segment]', priority: 6 },
63
+ { id: 'integrations', url: '/integrations', priority: 7 },
64
+ { id: 'changelog', url: '/changelog', priority: 8 },
65
+ { id: 'docs', url: '/docs', priority: 9 },
66
+ { id: 'blog', url: '/blog', priority: 10 },
67
+ ],
68
+ ecommerce: [
69
+ { id: 'homepage', url: '/', priority: 1 },
70
+ { id: 'category', url: '/[category]', priority: 2 },
71
+ { id: 'product', url: '/products/[slug]', priority: 3 },
72
+ { id: 'collection', url: '/collections/[slug]', priority: 4 },
73
+ { id: 'reviews', url: '/reviews', priority: 5 },
74
+ { id: 'cart', url: '/cart', priority: 6 },
75
+ { id: 'checkout', url: '/checkout', priority: 7 },
76
+ ],
77
+ local: [
78
+ { id: 'homepage', url: '/', priority: 1 },
79
+ { id: 'services', url: '/services', priority: 2 },
80
+ { id: 'location', url: '/[city]', priority: 3 },
81
+ { id: 'about', url: '/about', priority: 4 },
82
+ { id: 'reviews', url: '/reviews', priority: 5 },
83
+ { id: 'faq', url: '/faq', priority: 6 },
84
+ { id: 'contact', url: '/contact', priority: 7 },
85
+ { id: 'service-area', url: '/service-area', priority: 8 },
86
+ ],
87
+ blog: [
88
+ { id: 'homepage', url: '/', priority: 1 },
89
+ { id: 'category', url: '/[category]', priority: 2 },
90
+ { id: 'article', url: '/[category]/[slug]', priority: 3 },
91
+ { id: 'author', url: '/author/[slug]', priority: 4 },
92
+ { id: 'about', url: '/about', priority: 5 },
93
+ { id: 'newsletter', url: '/newsletter', priority: 6 },
94
+ ],
95
+ marketplace: [
96
+ { id: 'homepage', url: '/', priority: 1 },
97
+ { id: 'listing', url: '/listings/[slug]', priority: 2 },
98
+ { id: 'category', url: '/[category]', priority: 3 },
99
+ { id: 'seller-profile', url: '/sellers/[slug]', priority: 4 },
100
+ { id: 'search-results', url: '/search', priority: 5 },
101
+ { id: 'how-it-works', url: '/how-it-works', priority: 6 },
102
+ { id: 'pricing', url: '/pricing', priority: 7 },
103
+ { id: 'trust-safety', url: '/trust', priority: 8 },
104
+ ],
105
+ };
106
+
107
+ export const SCHEMA_TYPES = {
108
+ saas: ['SoftwareApplication', 'Organization', 'FAQPage', 'WebPage'],
109
+ ecommerce: ['Product', 'Organization', 'FAQPage', 'BreadcrumbList'],
110
+ local: ['LocalBusiness', 'FAQPage', 'Review', 'GeoCoordinates'],
111
+ blog: ['Article', 'Person', 'FAQPage', 'BreadcrumbList'],
112
+ marketplace: ['WebSite', 'Organization', 'FAQPage', 'Product'],
113
+ };
114
+
115
+ export const PERFORMANCE_THRESHOLDS = {
116
+ lcp: '2.5s',
117
+ cls: '0.1',
118
+ fid: '100ms',
119
+ page_size: '500kb',
120
+ time_to_first_byte: '800ms',
121
+ };
122
+
123
+ export const AI_BOTS = [
124
+ 'Googlebot',
125
+ 'Bingbot',
126
+ 'PerplexityBot',
127
+ 'ChatGPT-User',
128
+ 'GPTBot',
129
+ 'ClaudeBot',
130
+ 'anthropic-ai',
131
+ 'cohere-ai',
132
+ ];
@@ -0,0 +1,46 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import YAML from 'yaml';
4
+
5
+ /**
6
+ * Parses the SEO.md file in the specified directory.
7
+ * Returns both the plain JavaScript object and the YAML Document (for comment preservation).
8
+ *
9
+ * @param {string} [cwd=process.cwd()] - Current working directory
10
+ * @returns {Promise<{doc: YAML.Document, data: any}>} Parsed SEO.md representation
11
+ */
12
+ export async function parseSeoMd(cwd = process.cwd()) {
13
+ const seomdPath = path.join(cwd, 'SEO.md');
14
+
15
+ if (!(await fs.pathExists(seomdPath))) {
16
+ throw new Error(`SEO.md not found at ${seomdPath}. Run 'seomd init' first.`);
17
+ }
18
+
19
+ const content = await fs.readFile(seomdPath, 'utf8');
20
+
21
+ try {
22
+ const doc = YAML.parseDocument(content);
23
+ const data = doc.toJS();
24
+
25
+ if (!data || typeof data !== 'object') {
26
+ throw new Error('SEO.md is empty or invalid YAML structure');
27
+ }
28
+
29
+ return { doc, data };
30
+ } catch (err) {
31
+ throw new Error(`Failed to parse SEO.md: ${err.message}`);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Writes the YAML Document back to SEO.md in the specified directory.
37
+ *
38
+ * @param {YAML.Document} doc - The YAML Document to write
39
+ * @param {string} [cwd=process.cwd()] - Current working directory
40
+ * @returns {Promise<void>}
41
+ */
42
+ export async function writeSeoMd(doc, cwd = process.cwd()) {
43
+ const seomdPath = path.join(cwd, 'SEO.md');
44
+ const content = doc.toString();
45
+ await fs.writeFile(seomdPath, content, 'utf8');
46
+ }