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 +9 -2
- package/package.json +1 -2
- package/src/commands/analyze.js +5 -5
- package/src/commands/init.js +10 -10
- package/src/commands/sync.js +2 -2
- package/src/generators/directory.js +12 -12
- package/src/utils/writeback.js +2 -2
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 `.
|
|
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
|
-
- `.
|
|
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.
|
|
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"
|
package/src/commands/analyze.js
CHANGED
|
@@ -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 .
|
|
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('✔ .
|
|
171
|
+
console.log(chalk.green('✔ .seo/pages/ playbooks generated.'));
|
|
172
172
|
console.log('');
|
|
173
173
|
|
|
174
174
|
} catch (err) {
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 .
|
|
143
|
+
// 3. Create .seo/ directory structure
|
|
144
144
|
await createSeomdDir(workingDir, answers);
|
|
145
|
-
spinner.succeed(chalk.green('.
|
|
145
|
+
spinner.succeed(chalk.green('.seo/ directory created'));
|
|
146
146
|
|
|
147
|
-
// 4. Add .
|
|
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('.
|
|
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.
|
|
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('.
|
|
178
|
+
if (!content.includes('.seo')) {
|
|
179
179
|
await fs.appendFile(gitignorePath, entry);
|
|
180
180
|
}
|
|
181
181
|
}
|
package/src/commands/sync.js
CHANGED
|
@@ -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 .
|
|
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('✔ .
|
|
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, '.
|
|
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 .
|
|
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 `# .
|
|
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
|
-
\`.
|
|
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
|
-
.
|
|
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
|
-
| \`.
|
|
69
|
-
| \`.
|
|
70
|
-
| \`.
|
|
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
|
-
\`.
|
|
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
|
-
| \`.
|
|
112
|
-
| \`.
|
|
113
|
-
| \`.
|
|
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
|
|
package/src/utils/writeback.js
CHANGED
|
@@ -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 .
|
|
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, '.
|
|
119
|
+
const seomdDir = path.join(cwd, '.seo');
|
|
120
120
|
const pagesDir = path.join(seomdDir, 'pages');
|
|
121
121
|
|
|
122
122
|
await fs.ensureDir(pagesDir);
|