geo-ai-search-optimization 1.0.3 → 1.0.4
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 +12 -0
- package/package.json +1 -1
- package/src/cli.js +30 -0
- package/src/index.js +1 -0
- package/src/schema.js +180 -0
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ geo-ai-search-optimization install --target ./tmp/custom-skills --json
|
|
|
29
29
|
geo-ai-search-optimization where
|
|
30
30
|
geo-ai-search-optimization doctor
|
|
31
31
|
geo-ai-search-optimization init-llms ./site --site-name "Acme Docs" --site-url "https://example.com"
|
|
32
|
+
geo-ai-search-optimization init-schema organization ./site --site-url "https://example.com"
|
|
33
|
+
geo-ai-search-optimization init-schema faq-page ./site --json
|
|
32
34
|
geo-ai-search-optimization audit ./my-site --type saas
|
|
33
35
|
geo-ai-search-optimization audit ./my-site --json --out ./reports/geo-audit.json
|
|
34
36
|
geo-ai-search-optimization scan ./my-site --max-file-size 500000 --max-examples 3
|
|
@@ -37,6 +39,16 @@ geo-ai-search-optimization version
|
|
|
37
39
|
geo-ai-search-optimization help
|
|
38
40
|
```
|
|
39
41
|
|
|
42
|
+
## New in 1.0.4
|
|
43
|
+
|
|
44
|
+
- `init-schema` command for generating JSON-LD starter files
|
|
45
|
+
- supported schema templates:
|
|
46
|
+
- `organization`
|
|
47
|
+
- `article`
|
|
48
|
+
- `faq-page`
|
|
49
|
+
- `product`
|
|
50
|
+
- `breadcrumb-list`
|
|
51
|
+
|
|
40
52
|
## New in 1.0.3
|
|
41
53
|
|
|
42
54
|
- `audit` command with a GEO score, blockers, high-impact fixes, SEO support fixes, and experiments
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { renderScanMarkdown, scanProject, writeScanOutput } from "./scan.js";
|
|
|
7
7
|
import { renderDoctorMarkdown, runDoctor } from "./doctor.js";
|
|
8
8
|
import { createLlmsTxt } from "./llms-txt.js";
|
|
9
9
|
import { auditProject, renderAuditMarkdown, writeAuditOutput } from "./audit.js";
|
|
10
|
+
import { createSchemaTemplate } from "./schema.js";
|
|
10
11
|
|
|
11
12
|
let cachedVersion;
|
|
12
13
|
|
|
@@ -32,6 +33,7 @@ function printHelp() {
|
|
|
32
33
|
" geo-ai-search-optimization where",
|
|
33
34
|
" geo-ai-search-optimization doctor [--json]",
|
|
34
35
|
" geo-ai-search-optimization init-llms [target-dir] [--site-name <name>] [--site-url <url>] [--overwrite] [--json]",
|
|
36
|
+
" geo-ai-search-optimization init-schema <type> [target-dir] [--site-url <url>] [--overwrite] [--json]",
|
|
35
37
|
" geo-ai-search-optimization audit <project-path> [--type <site-type>] [--json] [--out <file>] [--max-file-size <bytes>] [--max-examples <count>]",
|
|
36
38
|
" geo-ai-search-optimization scan <project-path> [--json] [--out <file>] [--max-file-size <bytes>] [--max-examples <count>]",
|
|
37
39
|
" geo-ai-search-optimization version",
|
|
@@ -145,6 +147,29 @@ async function handleInitLlms(args) {
|
|
|
145
147
|
process.stdout.write(`Created llms.txt at ${result.outputPath}\n`);
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
async function handleInitSchema(args) {
|
|
151
|
+
const [schemaType, ...rest] = args;
|
|
152
|
+
if (!schemaType || schemaType.startsWith("-")) {
|
|
153
|
+
throw new Error("init-schema requires a schema type");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const targetDir = rest.find((value) => !value.startsWith("-")) || ".";
|
|
157
|
+
const outputJson = hasFlag(rest, "--json");
|
|
158
|
+
const result = await createSchemaTemplate({
|
|
159
|
+
schemaType,
|
|
160
|
+
targetDir,
|
|
161
|
+
siteUrl: getFlagValue(rest, "--site-url"),
|
|
162
|
+
overwrite: hasFlag(rest, "--overwrite")
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (outputJson) {
|
|
166
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
process.stdout.write(`Created ${result.schemaType} schema template at ${result.outputPath}\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
async function handleAudit(args) {
|
|
149
174
|
const projectPath = args.find((value) => !value.startsWith("-"));
|
|
150
175
|
if (!projectPath) {
|
|
@@ -209,6 +234,11 @@ export async function runCli(args = []) {
|
|
|
209
234
|
return;
|
|
210
235
|
}
|
|
211
236
|
|
|
237
|
+
if (command === "init-schema") {
|
|
238
|
+
await handleInitSchema(rest);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
212
242
|
if (command === "audit") {
|
|
213
243
|
await handleAudit(rest);
|
|
214
244
|
return;
|
package/src/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export { installSkill } from "./install-skill.js";
|
|
|
3
3
|
export { runCli } from "./cli.js";
|
|
4
4
|
export { runDoctor, renderDoctorMarkdown } from "./doctor.js";
|
|
5
5
|
export { createLlmsTxt } from "./llms-txt.js";
|
|
6
|
+
export { createSchemaTemplate } from "./schema.js";
|
|
6
7
|
export { scanProject, renderScanMarkdown, writeScanOutput } from "./scan.js";
|
package/src/schema.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const TEMPLATE_TYPES = new Set([
|
|
5
|
+
"organization",
|
|
6
|
+
"article",
|
|
7
|
+
"faq-page",
|
|
8
|
+
"product",
|
|
9
|
+
"breadcrumb-list"
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
async function pathExists(targetPath) {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(targetPath);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeSchemaType(schemaType) {
|
|
22
|
+
const resolved = (schemaType || "").toLowerCase();
|
|
23
|
+
if (!TEMPLATE_TYPES.has(resolved)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Unsupported schema type: ${schemaType}. Valid values: ${Array.from(TEMPLATE_TYPES).join(", ")}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildOrganizationTemplate(siteUrl) {
|
|
32
|
+
return {
|
|
33
|
+
"@context": "https://schema.org",
|
|
34
|
+
"@type": "Organization",
|
|
35
|
+
name: "Your Organization",
|
|
36
|
+
url: siteUrl,
|
|
37
|
+
logo: `${siteUrl}/logo.png`,
|
|
38
|
+
sameAs: [
|
|
39
|
+
"https://www.linkedin.com/company/your-company",
|
|
40
|
+
"https://x.com/your-brand"
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildArticleTemplate(siteUrl) {
|
|
46
|
+
return {
|
|
47
|
+
"@context": "https://schema.org",
|
|
48
|
+
"@type": "Article",
|
|
49
|
+
headline: "Your article headline",
|
|
50
|
+
description: "Summarize the article in one clear sentence.",
|
|
51
|
+
author: {
|
|
52
|
+
"@type": "Person",
|
|
53
|
+
name: "Author Name"
|
|
54
|
+
},
|
|
55
|
+
publisher: {
|
|
56
|
+
"@type": "Organization",
|
|
57
|
+
name: "Your Organization",
|
|
58
|
+
url: siteUrl
|
|
59
|
+
},
|
|
60
|
+
datePublished: "2026-03-12",
|
|
61
|
+
dateModified: "2026-03-12",
|
|
62
|
+
mainEntityOfPage: `${siteUrl}/blog/your-article-slug`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildFaqPageTemplate() {
|
|
67
|
+
return {
|
|
68
|
+
"@context": "https://schema.org",
|
|
69
|
+
"@type": "FAQPage",
|
|
70
|
+
mainEntity: [
|
|
71
|
+
{
|
|
72
|
+
"@type": "Question",
|
|
73
|
+
name: "What problem does this page answer?",
|
|
74
|
+
acceptedAnswer: {
|
|
75
|
+
"@type": "Answer",
|
|
76
|
+
text: "Replace this with the direct answer visible on the page."
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"@type": "Question",
|
|
81
|
+
name: "Who is this for?",
|
|
82
|
+
acceptedAnswer: {
|
|
83
|
+
"@type": "Answer",
|
|
84
|
+
text: "Replace this with a concise, factual answer."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildProductTemplate(siteUrl) {
|
|
92
|
+
return {
|
|
93
|
+
"@context": "https://schema.org",
|
|
94
|
+
"@type": "Product",
|
|
95
|
+
name: "Your Product",
|
|
96
|
+
description: "Describe the product with factual, user-facing language.",
|
|
97
|
+
brand: {
|
|
98
|
+
"@type": "Brand",
|
|
99
|
+
name: "Your Brand"
|
|
100
|
+
},
|
|
101
|
+
offers: {
|
|
102
|
+
"@type": "Offer",
|
|
103
|
+
url: `${siteUrl}/pricing`,
|
|
104
|
+
priceCurrency: "USD",
|
|
105
|
+
price: "99",
|
|
106
|
+
availability: "https://schema.org/InStock"
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildBreadcrumbListTemplate(siteUrl) {
|
|
112
|
+
return {
|
|
113
|
+
"@context": "https://schema.org",
|
|
114
|
+
"@type": "BreadcrumbList",
|
|
115
|
+
itemListElement: [
|
|
116
|
+
{
|
|
117
|
+
"@type": "ListItem",
|
|
118
|
+
position: 1,
|
|
119
|
+
name: "Home",
|
|
120
|
+
item: siteUrl
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"@type": "ListItem",
|
|
124
|
+
position: 2,
|
|
125
|
+
name: "Section",
|
|
126
|
+
item: `${siteUrl}/section`
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"@type": "ListItem",
|
|
130
|
+
position: 3,
|
|
131
|
+
name: "Current Page",
|
|
132
|
+
item: `${siteUrl}/section/current-page`
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildSchemaTemplate(schemaType, siteUrl) {
|
|
139
|
+
switch (schemaType) {
|
|
140
|
+
case "organization":
|
|
141
|
+
return buildOrganizationTemplate(siteUrl);
|
|
142
|
+
case "article":
|
|
143
|
+
return buildArticleTemplate(siteUrl);
|
|
144
|
+
case "faq-page":
|
|
145
|
+
return buildFaqPageTemplate();
|
|
146
|
+
case "product":
|
|
147
|
+
return buildProductTemplate(siteUrl);
|
|
148
|
+
case "breadcrumb-list":
|
|
149
|
+
return buildBreadcrumbListTemplate(siteUrl);
|
|
150
|
+
default:
|
|
151
|
+
throw new Error(`Unsupported schema type: ${schemaType}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildFileName(schemaType) {
|
|
156
|
+
return `${schemaType}.schema.json`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function createSchemaTemplate(options = {}) {
|
|
160
|
+
const schemaType = normalizeSchemaType(options.schemaType);
|
|
161
|
+
const targetDir = path.resolve(options.targetDir || ".");
|
|
162
|
+
const siteUrl = options.siteUrl || "https://example.com";
|
|
163
|
+
const outputPath = path.join(targetDir, buildFileName(schemaType));
|
|
164
|
+
const overwrite = Boolean(options.overwrite);
|
|
165
|
+
|
|
166
|
+
if (!overwrite && (await pathExists(outputPath))) {
|
|
167
|
+
throw new Error(`${path.basename(outputPath)} already exists at ${outputPath}. Use --overwrite to replace it.`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
171
|
+
|
|
172
|
+
const schema = buildSchemaTemplate(schemaType, siteUrl);
|
|
173
|
+
await fs.writeFile(outputPath, `${JSON.stringify(schema, null, 2)}\n`, "utf8");
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
schemaType,
|
|
177
|
+
outputPath,
|
|
178
|
+
siteUrl
|
|
179
|
+
};
|
|
180
|
+
}
|