seomd-cli 1.2.0 → 1.3.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/README.md CHANGED
@@ -91,12 +91,14 @@ seomd status --json
91
91
  Copy `.env.example` from your platform provider docs, or create one with the vars you need:
92
92
 
93
93
  Required for live audits:
94
+
94
95
  ```bash
95
96
  SEOMD_API_URL=
96
97
  SEOMD_API_KEY=your_key_here
97
98
  ```
98
99
 
99
100
  Optional:
101
+
100
102
  ```bash
101
103
  SEOMD_PAYMENT_TOKEN= # x402 pay-per-scan token
102
104
  SEOMD_DOMAIN= # override domain header
@@ -108,9 +110,10 @@ SEOMD_DOMAIN= # override domain header
108
110
 
109
111
  ### `seomd init`
110
112
 
111
- Scaffolds `SEO.md`, `SEO.REVERSE.md`, and the `.seomd/` intelligence directory.
113
+ Scaffolds `SEO.md`, `SEO.REVERSE.md`, and the `.seo/` intelligence directory.
112
114
 
113
115
  **Usage:**
116
+
114
117
  ```bash
115
118
  seomd init # interactive 5-question flow
116
119
  seomd init -y --type local # skip prompts, use defaults
@@ -119,6 +122,7 @@ seomd init --type saas --brand "MyApp" --domain myapp.com --primary-keyword "bil
119
122
  ```
120
123
 
121
124
  **Options:**
125
+
122
126
  | Flag | Description |
123
127
  |------|-------------|
124
128
  | `-y, --yes` | skip prompts, use defaults |
@@ -130,6 +134,7 @@ seomd init --type saas --brand "MyApp" --domain myapp.com --primary-keyword "bil
130
134
  | `--output <dir>` | scaffold into a new (empty) directory instead of cwd |
131
135
 
132
136
  **Behavior:**
137
+
133
138
  - Interactive flow by default (5 questions)
134
139
  - Non-interactive when `-y` is set **OR** any config flag (`--brand`, `--domain`, `--primary-keyword`, `--competitors`) is provided
135
140
  - `--type` alone pre-selects site type in the interactive flow
@@ -151,7 +156,7 @@ Runs an AI search audit via your connected platform and writes results back into
151
156
 
152
157
  - `SEO.md` (`_analysis` blocks)
153
158
  - `SEO.REVERSE.md` (generated reverse view)
154
- - `.seomd/pages/*.md` (per-page playbooks when available)
159
+ - `.seo/pages/*.md` (per-page playbooks when available)
155
160
 
156
161
  ### `seomd sync`
157
162
 
@@ -223,9 +228,11 @@ To enable live intelligence writebacks (using automated platforms like [Foxcite]
223
228
 
224
229
  1. Obtain a developer API key from your platform provider.
225
230
  2. Export the key as an environment variable:
231
+
226
232
  ```bash
227
233
  export SEOMD_API_KEY="your_api_key_here"
228
234
  ```
235
+
229
236
  3. Run `seomd sync` or `seomd analyze`.
230
237
 
231
238
  _Note: Never commit your API keys or `.env` files containing keys to version control._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seomd-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.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": {
@@ -35,7 +35,6 @@
35
35
  "yaml": "^2.3.4"
36
36
  },
37
37
  "devDependencies": {
38
- "@eslint/js": "^10.0.1",
39
38
  "eslint": "^8.50.0",
40
39
  "eslint-plugin-n": "^18.1.0",
41
40
  "jest": "^29.7.0"
@@ -11,16 +11,16 @@ dotenv.config();
11
11
  function matchRoute(pattern, url) {
12
12
  const cleanPattern = pattern.replace(/\/$/, '');
13
13
  const cleanUrl = url.replace(/\/$/, '');
14
-
14
+
15
15
  if (cleanPattern.toLowerCase() === cleanUrl.toLowerCase()) {
16
16
  return true;
17
17
  }
18
-
18
+
19
19
  // Replace "/[param]" with optional group "(?:/([^/]+))?"
20
20
  const regexPattern = cleanPattern
21
21
  .replace(/\/\[[^\]]+\]/g, '(?:\\/([^/]+))?')
22
22
  .replace(/\//g, '\\/');
23
-
23
+
24
24
  const regex = new RegExp('^' + regexPattern + '\\/?$', 'i');
25
25
  return regex.test(url);
26
26
  }
@@ -147,7 +147,7 @@ export async function analyzeCommand(options) {
147
147
  const brandName = data.identity?.brand || 'My Brand';
148
148
  await writeReverseMd(cwd, results, domain, brandName);
149
149
 
150
- // Writeback to .seomd/pages/*.md
150
+ // Writeback to .seo/pages/*.md
151
151
  await writePageAnalysis(cwd, results);
152
152
 
153
153
  spinner.succeed(chalk.green('Analysis completed successfully!'));
@@ -168,7 +168,7 @@ export async function analyzeCommand(options) {
168
168
  console.log('');
169
169
  console.log(chalk.green('✔ SEO.md updated.'));
170
170
  console.log(chalk.green('✔ SEO.REVERSE.md updated.'));
171
- console.log(chalk.green('✔ .seomd/pages/ playbooks generated.'));
171
+ console.log(chalk.green('✔ .seo/pages/ playbooks generated.'));
172
172
  console.log('');
173
173
 
174
174
  } catch (err) {
@@ -60,10 +60,10 @@ export async function initCommand(options) {
60
60
  primary_keyword: options.primaryKeyword || '',
61
61
  competitors: options.competitors
62
62
  ? options.competitors
63
- .split(',')
64
- .map((c) => c.trim())
65
- .filter(Boolean)
66
- .slice(0, 3)
63
+ .split(',')
64
+ .map((c) => c.trim())
65
+ .filter(Boolean)
66
+ .slice(0, 3)
67
67
  : [],
68
68
  };
69
69
  } else {
@@ -140,11 +140,11 @@ export async function initCommand(options) {
140
140
  await fs.writeFile(path.join(workingDir, 'SEO.REVERSE.md'), reverseContent, 'utf8');
141
141
  spinner.succeed(chalk.green('SEO.REVERSE.md initialized'));
142
142
 
143
- // 3. Create .seomd/ directory structure
143
+ // 3. Create .seo/ directory structure
144
144
  await createSeomdDir(workingDir, answers);
145
- spinner.succeed(chalk.green('.seomd/ directory created'));
145
+ spinner.succeed(chalk.green('.seo/ directory created'));
146
146
 
147
- // 4. Add .seomd/ to .gitignore if it exists
147
+ // 4. Add .seo/ to .gitignore if it exists
148
148
  await updateGitignore(workingDir);
149
149
 
150
150
  console.log('');
@@ -153,7 +153,7 @@ export async function initCommand(options) {
153
153
  console.log(chalk.dim('Files created:'));
154
154
  console.log(' ' + chalk.cyan('SEO.md') + chalk.dim(' — your living SEO config'));
155
155
  console.log(' ' + chalk.cyan('SEO.REVERSE.md') + chalk.dim(' — reverse engineer output (platform generated)'));
156
- console.log(' ' + chalk.cyan('.seomd/') + chalk.dim(' — intelligence directory'));
156
+ console.log(' ' + chalk.cyan('.seo/') + chalk.dim(' — intelligence directory'));
157
157
  console.log('');
158
158
  console.log(chalk.dim('Next steps:'));
159
159
  console.log(' ' + chalk.white('npx seomd analyze') + chalk.dim(' — run your first citation analysis'));
@@ -171,11 +171,11 @@ export async function initCommand(options) {
171
171
 
172
172
  async function updateGitignore(cwd) {
173
173
  const gitignorePath = path.join(cwd, '.gitignore');
174
- const entry = '\n# seomd intelligence directory\n.seomd/reports/\n';
174
+ const entry = '\n# seomd intelligence directory\n.seo/reports/\n';
175
175
 
176
176
  if (await fs.pathExists(gitignorePath)) {
177
177
  const content = await fs.readFile(gitignorePath, 'utf8');
178
- if (!content.includes('.seomd')) {
178
+ if (!content.includes('.seo')) {
179
179
  await fs.appendFile(gitignorePath, entry);
180
180
  }
181
181
  }
@@ -90,7 +90,7 @@ export async function syncCommand(options) {
90
90
  const brandName = data.identity?.brand || 'My Brand';
91
91
  await writeReverseMd(cwd, results, domain, brandName);
92
92
 
93
- // Writeback to .seomd/pages/*.md
93
+ // Writeback to .seo/pages/*.md
94
94
  await writePageAnalysis(cwd, results);
95
95
 
96
96
  spinner.succeed(chalk.green('Sync completed successfully!'));
@@ -111,7 +111,7 @@ export async function syncCommand(options) {
111
111
  console.log('');
112
112
  console.log(chalk.green('✔ SEO.md updated.'));
113
113
  console.log(chalk.green('✔ SEO.REVERSE.md updated.'));
114
- console.log(chalk.green('✔ .seomd/pages/ playbooks synchronized.'));
114
+ console.log(chalk.green('✔ .seo/pages/ playbooks synchronized.'));
115
115
  console.log('');
116
116
 
117
117
  } catch (err) {
@@ -5,14 +5,14 @@ import { REQUIRED_PAGES } from '../utils/constants.js';
5
5
  export async function createSeomdDir(cwd, answers) {
6
6
  const { site_type, brand, domain } = answers;
7
7
  const pages = REQUIRED_PAGES[site_type] || REQUIRED_PAGES.saas;
8
- const seomdDir = path.join(cwd, '.seomd');
8
+ const seomdDir = path.join(cwd, '.seo');
9
9
 
10
10
  // Create directory structure
11
11
  await fs.ensureDir(path.join(seomdDir, 'pages'));
12
12
  await fs.ensureDir(path.join(seomdDir, 'reports'));
13
13
  await fs.ensureDir(path.join(seomdDir, 'competitors'));
14
14
 
15
- // Create README inside .seomd/
15
+ // Create README inside .seo/
16
16
  await fs.writeFile(
17
17
  path.join(seomdDir, 'README.md'),
18
18
  generateSeomdReadme(brand, domain),
@@ -38,19 +38,19 @@ export async function createSeomdDir(cwd, answers) {
38
38
  }
39
39
 
40
40
  function generateSeomdReadme(brand, domain) {
41
- return `# .seomd/
41
+ return `# .seo/
42
42
 
43
43
  AI search optimization workspace for **${brand}** (${domain})
44
44
  Generated by SEO.md CLI — https://seomd.dev
45
45
 
46
46
  ## What is this?
47
47
 
48
- \`.seomd/\` stores platform-generated intelligence from citation audits, competitor analysis, and reverse-engineered playbooks. It lives alongside your source code so SEO strategy is version-controlled and reviewable in PRs.
48
+ \`.seo/\` stores platform-generated intelligence from citation audits, competitor analysis, and reverse-engineered playbooks. It lives alongside your source code so SEO strategy is version-controlled and reviewable in PRs.
49
49
 
50
50
  ## Directory Structure
51
51
 
52
52
  \`\`\`
53
- .seomd/
53
+ .seo/
54
54
  ├── pages/ # per-page reverse-engineer analysis
55
55
  │ ├── homepage.md
56
56
  │ ├── services.md
@@ -65,11 +65,11 @@ Generated by SEO.md CLI — https://seomd.dev
65
65
 
66
66
  | Path | Git | Why |
67
67
  |------|-----|-----|
68
- | \`.seomd/pages/\` | tracked | actionable playbooks belong in code review |
69
- | \`.seomd/competitors/\` | tracked | competitor intel is project state |
70
- | \`.seomd/reports/\` | ignored | dated snapshots are noise in git history |
68
+ | \`.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 |
71
71
 
72
- \`.seomd/reports/\` is added to \`.gitignore\` automatically by \`seomd init\`.
72
+ \`.seo/reports/\` is added to \`.gitignore\` automatically by \`seomd init\`.
73
73
 
74
74
  ## When to run what
75
75
 
@@ -108,9 +108,9 @@ Each \`pages/<id>.md\` contains:
108
108
  |-----------|-------|----------|
109
109
  | \`SEO.md\` | you | yes |
110
110
  | \`SEO.REVERSE.md\` | platform | no |
111
- | \`.seomd/pages/*.md\` | platform | no |
112
- | \`.seomd/competitors/*.md\` | platform | no |
113
- | \`.seomd/reports/*.md\` | platform | no |
111
+ | \`.seo/pages/*.md\` | platform | no |
112
+ | \`.seo/competitors/*.md\` | platform | no |
113
+ | \`.seo/reports/*.md\` | platform | no |
114
114
 
115
115
  Platform-generated files are overwritten on each \`analyze\` or \`sync\`.
116
116
 
@@ -110,13 +110,13 @@ export async function writeReverseMd(cwd, response, defaultDomain = 'example.com
110
110
  }
111
111
 
112
112
  /**
113
- * Writes individual page playbooks into .seomd/pages/{id}.md files.
113
+ * Writes individual page playbooks into .seo/pages/{id}.md files.
114
114
  *
115
115
  * @param {string} cwd - Current working directory
116
116
  * @param {any} response - The API response from analyze/sync
117
117
  */
118
118
  export async function writePageAnalysis(cwd, response) {
119
- const seomdDir = path.join(cwd, '.seomd');
119
+ const seomdDir = path.join(cwd, '.seo');
120
120
  const pagesDir = path.join(seomdDir, 'pages');
121
121
 
122
122
  await fs.ensureDir(pagesDir);