seomd-cli 1.3.0 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seomd-cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "The official CLI for the SEO.md open standard — AEO infrastructure for technical founders",
5
5
  "homepage": "https://seomd.dev",
6
6
  "repository": {
@@ -105,7 +105,7 @@ export async function analyzeCommand(options) {
105
105
  }
106
106
 
107
107
  // Extract engines
108
- let engines = data.aeo?._analysis?.engines_tracked || ['ChatGPT'];
108
+ let engines = ['ChatGPT', 'Perplexity', 'Claude']; // Default since we removed _analysis
109
109
  if (options.engines) {
110
110
  engines = options.engines.split(',').map(e => e.trim());
111
111
  }
@@ -140,10 +140,10 @@ export async function analyzeCommand(options) {
140
140
 
141
141
  spinner.text = 'Writing analysis blocks back to repository files...';
142
142
 
143
- // Writeback to SEO.md
143
+ // Writeback to .seo/STATUS.yml
144
144
  await writeAnalysisToSeoMd(doc, results, cwd);
145
145
 
146
- // Writeback to SEO.REVERSE.md
146
+ // Writeback to .seo/REVERSE.md
147
147
  const brandName = data.identity?.brand || 'My Brand';
148
148
  await writeReverseMd(cwd, results, domain, brandName);
149
149
 
@@ -166,8 +166,8 @@ export async function analyzeCommand(options) {
166
166
  console.log(`Next Analysis Target : ${chalk.dim(aeo.next_analysis)}`);
167
167
  console.log('-----------------------');
168
168
  console.log('');
169
- console.log(chalk.green('✔ SEO.md updated.'));
170
- console.log(chalk.green('✔ SEO.REVERSE.md updated.'));
169
+ console.log(chalk.green('✔ .seo/STATUS.yml updated.'));
170
+ console.log(chalk.green('✔ .seo/REVERSE.md updated.'));
171
171
  console.log(chalk.green('✔ .seo/pages/ playbooks generated.'));
172
172
  console.log('');
173
173
 
@@ -135,25 +135,24 @@ export async function initCommand(options) {
135
135
  await fs.writeFile(path.join(workingDir, 'SEO.md'), seomdContent, 'utf8');
136
136
  spinner.succeed(chalk.green('SEO.md created'));
137
137
 
138
- // 2. Generate SEO.REVERSE.md
139
- const reverseContent = generateReverseMd(answers);
140
- await fs.writeFile(path.join(workingDir, 'SEO.REVERSE.md'), reverseContent, 'utf8');
141
- spinner.succeed(chalk.green('SEO.REVERSE.md initialized'));
142
-
143
- // 3. Create .seo/ directory structure
138
+ // 2. Create .seo/ directory structure
144
139
  await createSeomdDir(workingDir, answers);
145
140
  spinner.succeed(chalk.green('.seo/ directory created'));
146
141
 
147
- // 4. Add .seo/ to .gitignore if it exists
148
- await updateGitignore(workingDir);
142
+ // 3. Generate .seo/REVERSE.md
143
+ const reverseContent = generateReverseMd(answers);
144
+ await fs.writeFile(path.join(workingDir, '.seo', 'REVERSE.md'), reverseContent, 'utf8');
145
+ spinner.succeed(chalk.green('.seo/REVERSE.md initialized'));
146
+
147
+
149
148
 
150
149
  console.log('');
151
150
  console.log(chalk.bold.green('✓ SEO.md initialized successfully'));
152
151
  console.log('');
153
152
  console.log(chalk.dim('Files created:'));
154
153
  console.log(' ' + chalk.cyan('SEO.md') + chalk.dim(' — your living SEO config'));
155
- console.log(' ' + chalk.cyan('SEO.REVERSE.md') + chalk.dim(' — reverse engineer output (platform generated)'));
156
- console.log(' ' + chalk.cyan('.seo/') + chalk.dim(' — intelligence directory'));
154
+ console.log(' ' + chalk.cyan('.seo/REVERSE.md') + chalk.dim(' — reverse engineer output (platform generated)'));
155
+ console.log(' ' + chalk.cyan('.seo/') + chalk.dim(' — intelligence directory'));
157
156
  console.log('');
158
157
  console.log(chalk.dim('Next steps:'));
159
158
  console.log(' ' + chalk.white('npx seomd analyze') + chalk.dim(' — run your first citation analysis'));
@@ -168,15 +167,3 @@ export async function initCommand(options) {
168
167
  process.exit(1);
169
168
  }
170
169
  }
171
-
172
- async function updateGitignore(cwd) {
173
- const gitignorePath = path.join(cwd, '.gitignore');
174
- const entry = '\n# seomd intelligence directory\n.seo/reports/\n';
175
-
176
- if (await fs.pathExists(gitignorePath)) {
177
- const content = await fs.readFile(gitignorePath, 'utf8');
178
- if (!content.includes('.seo')) {
179
- await fs.appendFile(gitignorePath, entry);
180
- }
181
- }
182
- }
@@ -1,14 +1,26 @@
1
1
  import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import YAML from 'yaml';
2
5
  import { parseSeoMd } from '../utils/parser.js';
3
6
 
4
7
  export async function statusCommand(options) {
5
8
  try {
6
- const { data } = await parseSeoMd(process.cwd());
9
+ const cwd = process.cwd();
10
+ const { data } = await parseSeoMd(cwd);
11
+
12
+ let statusData = {};
13
+ const statusPath = path.join(cwd, '.seo', 'STATUS.yml');
14
+
15
+ if (await fs.pathExists(statusPath)) {
16
+ const content = await fs.readFile(statusPath, 'utf8');
17
+ statusData = YAML.parse(content) || {};
18
+ }
7
19
 
8
- const aeoAnalysis = data.aeo?._analysis || {};
9
- const intentAnalysis = data.intent?._analysis || {};
20
+ const aeoAnalysis = statusData.aeo || {};
21
+ const intentAnalysis = statusData.intent || {};
10
22
  const pagesRequired = data.pages?.required || [];
11
- const pagesAnalysis = data.pages?._analysis?.pages || [];
23
+ const pagesAnalysis = statusData.pages?.pages || [];
12
24
 
13
25
  // Check if there is any analysis data
14
26
  const hasOverallData = aeoAnalysis.overall_citation_rate !== null && aeoAnalysis.overall_citation_rate !== undefined;
@@ -18,7 +30,7 @@ export async function statusCommand(options) {
18
30
  console.log(JSON.stringify({ status: "no_data", message: "No analysis data found" }, null, 2));
19
31
  } else {
20
32
  console.log('');
21
- console.log(chalk.yellow('⚠ No analysis data found in SEO.md.'));
33
+ console.log(chalk.yellow('⚠ No analysis data found in .seo/STATUS.yml.'));
22
34
  console.log('');
23
35
  console.log('To populate analysis data:');
24
36
  console.log(` 1. Get an API key at ${chalk.cyan('https://seomd.dev/connect')}`);
@@ -47,7 +59,7 @@ export async function statusCommand(options) {
47
59
  overall: {
48
60
  citation_rate: aeoAnalysis.overall_citation_rate,
49
61
  gap_score: aeoAnalysis.overall_gap_score,
50
- last_analyzed: aeoAnalysis.last_analyzed || null,
62
+ last_analyzed: statusData.last_analyzed || null,
51
63
  },
52
64
  intent: {},
53
65
  pages: []
@@ -85,7 +97,7 @@ export async function statusCommand(options) {
85
97
  // Output beautiful terminal dashboard
86
98
  console.log('');
87
99
  console.log(chalk.bold('SEO.md Status Dashboard') + chalk.dim(` — ${data.identity?.brand || 'Brand'} (${data.site?.domain || 'domain'})`));
88
- console.log(chalk.dim(`Last analyzed: ${aeoAnalysis.last_analyzed || 'N/A'}`));
100
+ console.log(chalk.dim(`Last analyzed: ${statusData.last_analyzed || 'N/A'}`));
89
101
  console.log(chalk.dim('─'.repeat(60)));
90
102
 
91
103
  // Overall block
@@ -83,10 +83,10 @@ export async function syncCommand(options) {
83
83
 
84
84
  spinner.text = 'Updating repository files...';
85
85
 
86
- // Writeback to SEO.md
86
+ // Writeback to .seo/STATUS.yml
87
87
  await writeAnalysisToSeoMd(doc, results, cwd);
88
88
 
89
- // Writeback to SEO.REVERSE.md
89
+ // Writeback to .seo/REVERSE.md
90
90
  const brandName = data.identity?.brand || 'My Brand';
91
91
  await writeReverseMd(cwd, results, domain, brandName);
92
92
 
@@ -109,8 +109,8 @@ export async function syncCommand(options) {
109
109
  console.log(`Next Analysis Target : ${chalk.dim(aeo.next_analysis)}`);
110
110
  console.log('----------------------------');
111
111
  console.log('');
112
- console.log(chalk.green('✔ SEO.md updated.'));
113
- console.log(chalk.green('✔ SEO.REVERSE.md updated.'));
112
+ console.log(chalk.green('✔ .seo/STATUS.yml updated.'));
113
+ console.log(chalk.green('✔ .seo/REVERSE.md updated.'));
114
114
  console.log(chalk.green('✔ .seo/pages/ playbooks synchronized.'));
115
115
  console.log('');
116
116
 
@@ -9,8 +9,6 @@ export async function createSeomdDir(cwd, answers) {
9
9
 
10
10
  // Create directory structure
11
11
  await fs.ensureDir(path.join(seomdDir, 'pages'));
12
- await fs.ensureDir(path.join(seomdDir, 'reports'));
13
- await fs.ensureDir(path.join(seomdDir, 'competitors'));
14
12
 
15
13
  // Create README inside .seo/
16
14
  await fs.writeFile(
@@ -27,14 +25,6 @@ export async function createSeomdDir(cwd, answers) {
27
25
  'utf8'
28
26
  );
29
27
  }
30
-
31
- // Create initial empty report
32
- const date = new Date().toISOString().split('T')[0];
33
- await fs.writeFile(
34
- path.join(seomdDir, 'reports', `${date}.md`),
35
- generateInitialReport(brand, domain, date),
36
- 'utf8'
37
- );
38
28
  }
39
29
 
40
30
  function generateSeomdReadme(brand, domain) {
@@ -52,13 +42,8 @@ Generated by SEO.md CLI — https://seomd.dev
52
42
  \`\`\`
53
43
  .seo/
54
44
  ├── pages/ # per-page reverse-engineer analysis
55
- ├── homepage.md
56
- │ ├── services.md
57
- │ └── ...
58
- ├── reports/ # dated snapshot reports (gitignored by default)
59
- │ └── 2026-06-10.md
60
- └── competitors/ # competitor citation profiles
61
- └── comp-1.md
45
+ ├── STATUS.yml # gap scores and citation metrics (platform generated)
46
+ └── REVERSE.md # competitor playbooks (platform generated)
62
47
  \`\`\`
63
48
 
64
49
  ## Git behavior
@@ -66,10 +51,29 @@ Generated by SEO.md CLI — https://seomd.dev
66
51
  | Path | Git | Why |
67
52
  |------|-----|-----|
68
53
  | \`.seo/pages/\` | tracked | actionable playbooks belong in code review |
69
- | \`.seo/competitors/\` | tracked | competitor intel is project state |
70
- | \`.seo/reports/\` | ignored | dated snapshots are noise in git history |
54
+ | \`.seo/STATUS.yml\` | tracked | gap scores represent current project state |
55
+ | \`.seo/REVERSE.md\` | tracked | competitor intel is project state |
56
+
57
+ ## How to configure SEO.md
58
+
59
+ \`SEO.md\` is your declarative configuration file. It tells the AI optimization platform exactly what you care about. Here is a guide on how to fill it out:
60
+
61
+ ### 1. Intent (Queries)
62
+ Add queries your buyers actually type into AI engines. Think about what someone asks ChatGPT or Perplexity when they are looking for a solution like yours.
63
+
64
+ ### 2. Pages
65
+ Declare the key pages on your site. For each page, set its priority and status (\`live | draft | planned\`). The platform will scan these and generate playbooks for each one.
66
+
67
+ ### 3. AI Engine Optimization (AEO) Rules
68
+ Set the strict guidelines you want your writers or AI to follow (e.g. \`answer_first_format: true\`).
69
+
70
+ ### 4. Platform Connection
71
+ To enable live writebacks, connect your platform:
72
+ 1. Connect at https://seomd.dev/connect
73
+ 2. Add \`SEOMD_API_KEY\` to your \`.env\`
74
+ 3. Run \`npx seomd analyze\`
71
75
 
72
- \`.seo/reports/\` is added to \`.gitignore\` automatically by \`seomd init\`.
76
+ **Note: Never commit your API key to version control.**
73
77
 
74
78
  ## When to run what
75
79
 
@@ -107,22 +111,11 @@ Each \`pages/<id>.md\` contains:
107
111
  | File/dir | Owner | Editable? |
108
112
  |-----------|-------|----------|
109
113
  | \`SEO.md\` | you | yes |
110
- | \`SEO.REVERSE.md\` | platform | no |
114
+ | \`.seo/STATUS.yml\` | platform | no |
115
+ | \`.seo/REVERSE.md\` | platform | no |
111
116
  | \`.seo/pages/*.md\` | platform | no |
112
- | \`.seo/competitors/*.md\` | platform | no |
113
- | \`.seo/reports/*.md\` | platform | no |
114
117
 
115
118
  Platform-generated files are overwritten on each \`analyze\` or \`sync\`.
116
-
117
- ## Platform connection
118
-
119
- To enable live writebacks:
120
-
121
- 1. Connect at https://seomd.dev/connect
122
- 2. Add \`SEOMD_API_KEY\` to your \`.env\`
123
- 3. Run \`npx seomd analyze\`
124
-
125
- _Note: Never commit API keys or \`.env\` files to version control._
126
119
  `;
127
120
  }
128
121
 
@@ -160,28 +153,3 @@ fastest_win:
160
153
  suggested_content_outline: []
161
154
  `;
162
155
  }
163
-
164
- function generateInitialReport(brand, domain, date) {
165
- return `# SEO.md Report — ${date}
166
-
167
- ## ${brand} (${domain})
168
-
169
- ### Initial report — run \`npx seomd analyze\` to populate
170
-
171
- date: ${date}
172
- brand: "${brand}"
173
- domain: ${domain}
174
- overall_citation_rate: null
175
- overall_gap_score: null
176
-
177
- intent_summary:
178
- informational: null
179
- comparison: null
180
- transactional: null
181
- reputational: null
182
- category: null
183
-
184
- top_opportunities: []
185
- completed_wins: []
186
- `;
187
- }
@@ -4,14 +4,6 @@
4
4
 
5
5
  ### spec v1.0 | <https://seomd.dev>
6
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
7
  ## Site
16
8
 
17
9
  site:
@@ -36,8 +28,6 @@ identity:
36
28
 
37
29
  keywords:
38
30
 
39
- ### FOUNDER DECLARES
40
-
41
31
  primary: "{{primary_keyword}}"
42
32
  secondary: [] # add your secondary keywords
43
33
  negative: # terms that dilute your intent signal
@@ -52,30 +42,10 @@ keywords:
52
42
  long_tail: [] # add long-tail variations
53
43
  seasonal: null # add seasonal terms if applicable
54
44
 
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
45
  ## Intent
68
46
 
69
47
  intent:
70
48
 
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
49
  informational:
80
50
  priority: critical
81
51
  queries:
@@ -108,47 +78,11 @@ intent:
108
78
  - "best {{primary_keyword}}"
109
79
  - "top {{primary_keyword}} 2026"
110
80
 
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
81
  ## Pages
144
82
 
145
83
  pages:
146
84
  site_type: blog
147
85
 
148
- ### FOUNDER DECLARES
149
-
150
- #### status: live | draft | planned
151
-
152
86
  required:
153
87
  - id: homepage
154
88
  url: /
@@ -186,21 +120,10 @@ pages:
186
120
  status: planned
187
121
  priority: 6
188
122
 
189
- ### PLATFORM WRITES BACK
190
-
191
- _analysis:
192
- source: null
193
- last_analyzed: null
194
- pages: []
195
- missing_pages: []
196
- build_order_recommendation: []
197
-
198
123
  ## Copy
199
124
 
200
125
  copy:
201
126
 
202
- ### FOUNDER DECLARES
203
-
204
127
  h1_contains_primary_keyword: true
205
128
  meta_description_length: 150-160
206
129
  meta_description_includes_cta: true
@@ -215,8 +138,6 @@ copy:
215
138
 
216
139
  structure:
217
140
 
218
- ### FOUNDER DECLARES
219
-
220
141
  answer_first: true # direct answer in first 50 words
221
142
  faq_section_required: true # on all key pages
222
143
  faq_minimum_questions: 6
@@ -229,8 +150,6 @@ structure:
229
150
 
230
151
  authority:
231
152
 
232
- ### FOUNDER DECLARES
233
-
234
153
  cite_sources: true
235
154
  expert_quotes: false # set true when you have quotes
236
155
  eeat_signals:
@@ -243,8 +162,6 @@ authority:
243
162
 
244
163
  schema:
245
164
 
246
- ### FOUNDER DECLARES
247
-
248
165
  types:
249
166
  - Article
250
167
  - Person
@@ -258,8 +175,6 @@ schema:
258
175
 
259
176
  crawl:
260
177
 
261
- ### FOUNDER DECLARES
262
-
263
178
  sitemap: /sitemap.xml
264
179
  robots_txt: /robots.txt
265
180
  allow_ai_bots: true
@@ -282,8 +197,6 @@ crawl:
282
197
 
283
198
  performance:
284
199
 
285
- ### FOUNDER DECLARES
286
-
287
200
  lcp: 2.5s
288
201
  cls: 0.1
289
202
  fid: 100ms
@@ -294,10 +207,6 @@ performance:
294
207
 
295
208
  aeo:
296
209
 
297
- ### FOUNDER DECLARES
298
-
299
- ### AI Engine Optimization rules
300
-
301
210
  answer_first_format: true
302
211
  faq_on_all_key_pages: true
303
212
  structured_data_priority: high
@@ -305,27 +214,10 @@ aeo:
305
214
  competitors_to_monitor:
306
215
  {{competitors_to_monitor}}
307
216
 
308
- ### PLATFORM WRITES BACK
309
-
310
- _analysis:
311
- source: null
312
- overall_citation_rate: null
313
- overall_gap_score: null
314
- engines_tracked:
315
- - chatgpt
316
- - perplexity
317
- - claude
318
- - gemini
319
- - grok
320
- last_analyzed: null
321
- next_analysis: null
322
-
323
217
  ## Monitoring
324
218
 
325
219
  monitoring:
326
220
 
327
- ### FOUNDER DECLARES
328
-
329
221
  sync_schedule: monthly # monthly | weekly | on_demand
330
222
  auto_commit: false # platform commits directly to repo
331
223
  pr_mode: true # open PR instead of direct commit
@@ -335,14 +227,7 @@ monitoring:
335
227
 
336
228
  ## Platform Connection
337
229
 
338
- ### Connect at <https://seomd.dev/connect>
339
-
340
- ### Add SEOMD_API_KEY to your .env file
341
-
342
- ### Never commit your API key to version control
343
-
344
230
  platform:
345
231
  provider: null # foxcite | manual | ahrefs | semrush
346
232
  project_id: null
347
233
 
348
- ### api_key: loaded from SEOMD_API_KEY environment variable