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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geo-ai-search-optimization",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Install and run a GEO-first, SEO-supported Codex skill for AI search optimization.",
5
5
  "type": "module",
6
6
  "bin": {
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
+ }