geo-ai-search-optimization 1.0.0 → 1.0.2
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 +19 -1
- package/package.json +1 -1
- package/src/cli.js +121 -26
- package/src/doctor.js +140 -0
- package/src/index.js +3 -1
- package/src/install-skill.js +4 -2
- package/src/llms-txt.js +76 -0
- package/src/scan.js +7 -0
package/README.md
CHANGED
|
@@ -25,11 +25,28 @@ npx geo-ai-search-optimization
|
|
|
25
25
|
```bash
|
|
26
26
|
geo-ai-search-optimization
|
|
27
27
|
geo-ai-search-optimization install
|
|
28
|
+
geo-ai-search-optimization install --target ./tmp/custom-skills --json
|
|
28
29
|
geo-ai-search-optimization where
|
|
29
|
-
geo-ai-search-optimization
|
|
30
|
+
geo-ai-search-optimization doctor
|
|
31
|
+
geo-ai-search-optimization init-llms ./site --site-name "Acme Docs" --site-url "https://example.com"
|
|
32
|
+
geo-ai-search-optimization scan ./my-site --max-file-size 500000 --max-examples 3
|
|
33
|
+
geo-ai-search-optimization scan ./my-site --json --out ./reports/geo-scan.json
|
|
34
|
+
geo-ai-search-optimization version
|
|
30
35
|
geo-ai-search-optimization help
|
|
31
36
|
```
|
|
32
37
|
|
|
38
|
+
## New in 1.0.2
|
|
39
|
+
|
|
40
|
+
- `init-llms` command for generating an `llms.txt` starter template
|
|
41
|
+
- keeps the `1.0.1` CLI upgrades: `doctor`, custom `install --target`, and richer `scan` output controls
|
|
42
|
+
|
|
43
|
+
## New in 1.0.1
|
|
44
|
+
|
|
45
|
+
- `doctor` command for installation and environment checks
|
|
46
|
+
- `install --target <dir>` for custom skill destinations
|
|
47
|
+
- `scan --out <file>` to save reports
|
|
48
|
+
- `scan --max-file-size` and `scan --max-examples` for tighter project scans
|
|
49
|
+
|
|
33
50
|
## Install location
|
|
34
51
|
|
|
35
52
|
By default the skill is installed to:
|
|
@@ -42,6 +59,7 @@ Override with:
|
|
|
42
59
|
- `CODEX_HOME`
|
|
43
60
|
- `CODEX_SKILLS_DIR`
|
|
44
61
|
- `GEO_SKILL_INSTALL_DIR`
|
|
62
|
+
- `GEO_SKILL_SKIP_POSTINSTALL`
|
|
45
63
|
|
|
46
64
|
## Resource contents
|
|
47
65
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { installSkill } from "./install-skill.js";
|
|
2
|
-
import { getBundledSkillPath, getInstalledSkillPath, getSkillName, getSkillsDir } from "./paths.js";
|
|
3
|
-
import { renderScanMarkdown, scanProject } from "./scan.js";
|
|
4
1
|
import { fileURLToPath } from "node:url";
|
|
5
2
|
import { readFile } from "node:fs/promises";
|
|
6
3
|
import path from "node:path";
|
|
4
|
+
import { installSkill } from "./install-skill.js";
|
|
5
|
+
import { getBundledSkillPath, getInstalledSkillPath, getSkillName, getSkillsDir } from "./paths.js";
|
|
6
|
+
import { renderScanMarkdown, scanProject, writeScanOutput } from "./scan.js";
|
|
7
|
+
import { renderDoctorMarkdown, runDoctor } from "./doctor.js";
|
|
8
|
+
import { createLlmsTxt } from "./llms-txt.js";
|
|
7
9
|
|
|
8
10
|
let cachedVersion;
|
|
9
11
|
|
|
@@ -25,9 +27,11 @@ function printHelp() {
|
|
|
25
27
|
"",
|
|
26
28
|
"Usage:",
|
|
27
29
|
" geo-ai-search-optimization",
|
|
28
|
-
" geo-ai-search-optimization install",
|
|
30
|
+
" geo-ai-search-optimization install [--target <dir>] [--json]",
|
|
29
31
|
" geo-ai-search-optimization where",
|
|
30
|
-
" geo-ai-search-optimization
|
|
32
|
+
" geo-ai-search-optimization doctor [--json]",
|
|
33
|
+
" geo-ai-search-optimization init-llms [target-dir] [--site-name <name>] [--site-url <url>] [--overwrite] [--json]",
|
|
34
|
+
" geo-ai-search-optimization scan <project-path> [--json] [--out <file>] [--max-file-size <bytes>] [--max-examples <count>]",
|
|
31
35
|
" geo-ai-search-optimization version",
|
|
32
36
|
" geo-ai-search-optimization help",
|
|
33
37
|
"",
|
|
@@ -40,6 +44,105 @@ function printHelp() {
|
|
|
40
44
|
);
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
function getFlagValue(args, flagName) {
|
|
48
|
+
const index = args.indexOf(flagName);
|
|
49
|
+
if (index === -1) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (index === args.length - 1) {
|
|
53
|
+
throw new Error(`${flagName} requires a value`);
|
|
54
|
+
}
|
|
55
|
+
return args[index + 1];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasFlag(args, ...flagNames) {
|
|
59
|
+
return flagNames.some((flagName) => args.includes(flagName));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parsePositiveInteger(value, flagName) {
|
|
63
|
+
const parsed = Number.parseInt(value, 10);
|
|
64
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
65
|
+
throw new Error(`${flagName} must be a positive integer`);
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function handleInstall(args) {
|
|
71
|
+
const targetDir = getFlagValue(args, "--target");
|
|
72
|
+
const outputJson = hasFlag(args, "--json");
|
|
73
|
+
const result = await installSkill({ targetDir, silent: outputJson });
|
|
74
|
+
|
|
75
|
+
if (outputJson) {
|
|
76
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleWhere() {
|
|
81
|
+
process.stdout.write(
|
|
82
|
+
[
|
|
83
|
+
`skill: ${getSkillName()}`,
|
|
84
|
+
`bundled: ${getBundledSkillPath()}`,
|
|
85
|
+
`skillsDir: ${getSkillsDir()}`,
|
|
86
|
+
`installed: ${getInstalledSkillPath()}`
|
|
87
|
+
].join("\n") + "\n"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function handleDoctor(args) {
|
|
92
|
+
const report = await runDoctor();
|
|
93
|
+
if (hasFlag(args, "--json")) {
|
|
94
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write(renderDoctorMarkdown(report));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function handleScan(args) {
|
|
101
|
+
const projectPath = args.find((value) => !value.startsWith("-"));
|
|
102
|
+
if (!projectPath) {
|
|
103
|
+
throw new Error("scan requires a project path");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const maxFileSizeValue = getFlagValue(args, "--max-file-size");
|
|
107
|
+
const maxExamplesValue = getFlagValue(args, "--max-examples");
|
|
108
|
+
const summary = await scanProject(projectPath, {
|
|
109
|
+
maxFileSize: maxFileSizeValue ? parsePositiveInteger(maxFileSizeValue, "--max-file-size") : undefined,
|
|
110
|
+
maxExamples: maxExamplesValue ? parsePositiveInteger(maxExamplesValue, "--max-examples") : undefined
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const outputJson = hasFlag(args, "--json");
|
|
114
|
+
const renderedOutput = outputJson
|
|
115
|
+
? `${JSON.stringify(summary, null, 2)}\n`
|
|
116
|
+
: renderScanMarkdown(summary);
|
|
117
|
+
|
|
118
|
+
const outputPath = getFlagValue(args, "--out");
|
|
119
|
+
if (outputPath) {
|
|
120
|
+
const resolvedOutputPath = await writeScanOutput(outputPath, renderedOutput);
|
|
121
|
+
process.stdout.write(`Saved scan output to ${resolvedOutputPath}\n`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
process.stdout.write(renderedOutput);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function handleInitLlms(args) {
|
|
129
|
+
const targetDir = args.find((value) => !value.startsWith("-")) || ".";
|
|
130
|
+
const outputJson = hasFlag(args, "--json");
|
|
131
|
+
const result = await createLlmsTxt({
|
|
132
|
+
targetDir,
|
|
133
|
+
siteName: getFlagValue(args, "--site-name"),
|
|
134
|
+
siteUrl: getFlagValue(args, "--site-url"),
|
|
135
|
+
overwrite: hasFlag(args, "--overwrite")
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (outputJson) {
|
|
139
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
process.stdout.write(`Created llms.txt at ${result.outputPath}\n`);
|
|
144
|
+
}
|
|
145
|
+
|
|
43
146
|
export async function runCli(args = []) {
|
|
44
147
|
const [command = "install", ...rest] = args;
|
|
45
148
|
|
|
@@ -54,35 +157,27 @@ export async function runCli(args = []) {
|
|
|
54
157
|
}
|
|
55
158
|
|
|
56
159
|
if (command === "install") {
|
|
57
|
-
await
|
|
160
|
+
await handleInstall(rest);
|
|
58
161
|
return;
|
|
59
162
|
}
|
|
60
163
|
|
|
61
164
|
if (command === "where") {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
165
|
+
handleWhere();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (command === "doctor") {
|
|
170
|
+
await handleDoctor(rest);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (command === "init-llms") {
|
|
175
|
+
await handleInitLlms(rest);
|
|
70
176
|
return;
|
|
71
177
|
}
|
|
72
178
|
|
|
73
179
|
if (command === "scan") {
|
|
74
|
-
|
|
75
|
-
if (!projectPath) {
|
|
76
|
-
throw new Error("scan requires a project path");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const outputJson = rest.includes("--json");
|
|
80
|
-
const summary = await scanProject(projectPath);
|
|
81
|
-
if (outputJson) {
|
|
82
|
-
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
83
|
-
} else {
|
|
84
|
-
process.stdout.write(renderScanMarkdown(summary));
|
|
85
|
-
}
|
|
180
|
+
await handleScan(rest);
|
|
86
181
|
return;
|
|
87
182
|
}
|
|
88
183
|
|
package/src/doctor.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { getBundledSkillPath, getInstalledSkillPath, getSkillName, getSkillsDir } from "./paths.js";
|
|
6
|
+
|
|
7
|
+
async function pathExists(targetPath) {
|
|
8
|
+
try {
|
|
9
|
+
await fs.access(targetPath);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function findExistingParent(targetPath) {
|
|
17
|
+
let current = path.resolve(targetPath);
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
if (await pathExists(current)) {
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parent = path.dirname(current);
|
|
25
|
+
if (parent === current) {
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
current = parent;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function checkWritable(targetPath) {
|
|
33
|
+
const existingParent = await findExistingParent(targetPath);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(existingParent, fsConstants.W_OK);
|
|
37
|
+
return {
|
|
38
|
+
ok: true,
|
|
39
|
+
checkedPath: existingParent
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
checkedPath: existingParent
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function runDoctor() {
|
|
50
|
+
const bundledPath = getBundledSkillPath();
|
|
51
|
+
const installedPath = getInstalledSkillPath();
|
|
52
|
+
const skillsDir = getSkillsDir();
|
|
53
|
+
|
|
54
|
+
const [bundledExists, installedExists, writableCheck] = await Promise.all([
|
|
55
|
+
pathExists(bundledPath),
|
|
56
|
+
pathExists(installedPath),
|
|
57
|
+
checkWritable(skillsDir)
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
skill: getSkillName(),
|
|
62
|
+
nodeVersion: process.version,
|
|
63
|
+
platform: process.platform,
|
|
64
|
+
bundledPath,
|
|
65
|
+
installedPath,
|
|
66
|
+
skillsDir,
|
|
67
|
+
envOverrides: {
|
|
68
|
+
CODEX_HOME: process.env.CODEX_HOME || null,
|
|
69
|
+
CODEX_SKILLS_DIR: process.env.CODEX_SKILLS_DIR || null,
|
|
70
|
+
GEO_SKILL_INSTALL_DIR: process.env.GEO_SKILL_INSTALL_DIR || null
|
|
71
|
+
},
|
|
72
|
+
checks: {
|
|
73
|
+
bundledSkillPresent: bundledExists,
|
|
74
|
+
installedSkillPresent: installedExists,
|
|
75
|
+
skillsDirWritable: writableCheck.ok,
|
|
76
|
+
skillsDirWritableCheckPath: writableCheck.checkedPath
|
|
77
|
+
},
|
|
78
|
+
recommendations: buildDoctorRecommendations({
|
|
79
|
+
bundledExists,
|
|
80
|
+
installedExists,
|
|
81
|
+
writableCheck,
|
|
82
|
+
skillsDir
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildDoctorRecommendations(context) {
|
|
88
|
+
const recommendations = [];
|
|
89
|
+
|
|
90
|
+
if (!context.bundledExists) {
|
|
91
|
+
recommendations.push("Bundled skill files are missing. Reinstall the npm package before using the CLI.");
|
|
92
|
+
}
|
|
93
|
+
if (!context.installedExists) {
|
|
94
|
+
recommendations.push("The skill is not installed into the Codex skills directory yet. Run `geo-ai-search-optimization install`.");
|
|
95
|
+
}
|
|
96
|
+
if (!context.writableCheck.ok) {
|
|
97
|
+
recommendations.push(
|
|
98
|
+
`The target skills directory is not writable from this environment. Use --target or set CODEX_SKILLS_DIR to a writable location.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (recommendations.length === 0) {
|
|
102
|
+
recommendations.push("Environment looks healthy. You can install, scan, and iterate on the skill from this machine.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return recommendations;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function renderDoctorMarkdown(report) {
|
|
109
|
+
const lines = [
|
|
110
|
+
"# GEO CLI Doctor",
|
|
111
|
+
"",
|
|
112
|
+
`- Skill: \`${report.skill}\``,
|
|
113
|
+
`- Node: \`${report.nodeVersion}\``,
|
|
114
|
+
`- Platform: \`${report.platform}\``,
|
|
115
|
+
`- Bundled path: \`${report.bundledPath}\``,
|
|
116
|
+
`- Installed path: \`${report.installedPath}\``,
|
|
117
|
+
`- Skills dir: \`${report.skillsDir}\``,
|
|
118
|
+
"",
|
|
119
|
+
"## Checks",
|
|
120
|
+
"",
|
|
121
|
+
`- bundledSkillPresent: \`${report.checks.bundledSkillPresent}\``,
|
|
122
|
+
`- installedSkillPresent: \`${report.checks.installedSkillPresent}\``,
|
|
123
|
+
`- skillsDirWritable: \`${report.checks.skillsDirWritable}\` via \`${report.checks.skillsDirWritableCheckPath}\``,
|
|
124
|
+
"",
|
|
125
|
+
"## Environment Overrides",
|
|
126
|
+
"",
|
|
127
|
+
`- CODEX_HOME: \`${report.envOverrides.CODEX_HOME ?? "unset"}\``,
|
|
128
|
+
`- CODEX_SKILLS_DIR: \`${report.envOverrides.CODEX_SKILLS_DIR ?? "unset"}\``,
|
|
129
|
+
`- GEO_SKILL_INSTALL_DIR: \`${report.envOverrides.GEO_SKILL_INSTALL_DIR ?? "unset"}\``,
|
|
130
|
+
"",
|
|
131
|
+
"## Recommendations",
|
|
132
|
+
""
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
for (const recommendation of report.recommendations) {
|
|
136
|
+
lines.push(`- ${recommendation}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return `${lines.join("\n")}\n`;
|
|
140
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { installSkill } from "./install-skill.js";
|
|
2
2
|
export { runCli } from "./cli.js";
|
|
3
|
-
export {
|
|
3
|
+
export { runDoctor, renderDoctorMarkdown } from "./doctor.js";
|
|
4
|
+
export { createLlmsTxt } from "./llms-txt.js";
|
|
5
|
+
export { scanProject, renderScanMarkdown, writeScanOutput } from "./scan.js";
|
package/src/install-skill.js
CHANGED
|
@@ -28,7 +28,8 @@ export async function installSkill(options = {}) {
|
|
|
28
28
|
skill: getSkillName(),
|
|
29
29
|
installedAt: new Date().toISOString(),
|
|
30
30
|
installedFrom: sourceDir,
|
|
31
|
-
packageName: "geo-ai-search-optimization"
|
|
31
|
+
packageName: "geo-ai-search-optimization",
|
|
32
|
+
targetDir
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
await fs.writeFile(
|
|
@@ -44,6 +45,7 @@ export async function installSkill(options = {}) {
|
|
|
44
45
|
return {
|
|
45
46
|
targetDir,
|
|
46
47
|
sourceDir,
|
|
47
|
-
skillName: getSkillName()
|
|
48
|
+
skillName: getSkillName(),
|
|
49
|
+
manifest
|
|
48
50
|
};
|
|
49
51
|
}
|
package/src/llms-txt.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
async function pathExists(targetPath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(targetPath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildLlmsTxtContent({ siteName, siteUrl }) {
|
|
14
|
+
const resolvedSiteName = siteName || "Your Site Name";
|
|
15
|
+
const resolvedSiteUrl = siteUrl || "https://example.com";
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
`# ${resolvedSiteName}`,
|
|
19
|
+
"",
|
|
20
|
+
`> GEO-first site profile for AI assistants and answer engines.`,
|
|
21
|
+
"",
|
|
22
|
+
`Canonical: ${resolvedSiteUrl}`,
|
|
23
|
+
"",
|
|
24
|
+
"## About",
|
|
25
|
+
"",
|
|
26
|
+
"- Summarize what this site does in 1-2 sentences.",
|
|
27
|
+
"- Describe the primary audience and core use cases.",
|
|
28
|
+
"- Mention what makes the content or product uniquely trustworthy.",
|
|
29
|
+
"",
|
|
30
|
+
"## Priority URLs",
|
|
31
|
+
"",
|
|
32
|
+
`- ${resolvedSiteUrl}/`,
|
|
33
|
+
`- ${resolvedSiteUrl}/pricing`,
|
|
34
|
+
`- ${resolvedSiteUrl}/docs`,
|
|
35
|
+
`- ${resolvedSiteUrl}/blog`,
|
|
36
|
+
"",
|
|
37
|
+
"## Recommended Sources",
|
|
38
|
+
"",
|
|
39
|
+
"- Link to pages with first-party research, methodology, benchmarks, or clear product facts.",
|
|
40
|
+
"- Prefer canonical pages over campaign or temporary URLs.",
|
|
41
|
+
"",
|
|
42
|
+
"## AI Assistant Guidance",
|
|
43
|
+
"",
|
|
44
|
+
"- Use the latest canonical URLs when citing this site.",
|
|
45
|
+
"- Prefer pages with explicit dates, authors, reviewers, or methodology notes.",
|
|
46
|
+
"- Avoid summarizing outdated pages when newer versions exist.",
|
|
47
|
+
"",
|
|
48
|
+
"## Update Policy",
|
|
49
|
+
"",
|
|
50
|
+
"- Replace this section with the site’s actual content freshness and review cadence.",
|
|
51
|
+
""
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function createLlmsTxt(options = {}) {
|
|
56
|
+
const targetDir = path.resolve(options.targetDir || ".");
|
|
57
|
+
const outputPath = path.join(targetDir, "llms.txt");
|
|
58
|
+
const overwrite = Boolean(options.overwrite);
|
|
59
|
+
|
|
60
|
+
if (!overwrite && (await pathExists(outputPath))) {
|
|
61
|
+
throw new Error(`llms.txt already exists at ${outputPath}. Use --overwrite to replace it.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
65
|
+
const content = buildLlmsTxtContent({
|
|
66
|
+
siteName: options.siteName,
|
|
67
|
+
siteUrl: options.siteUrl
|
|
68
|
+
});
|
|
69
|
+
await fs.writeFile(outputPath, content, "utf8");
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
outputPath,
|
|
73
|
+
siteName: options.siteName || "Your Site Name",
|
|
74
|
+
siteUrl: options.siteUrl || "https://example.com"
|
|
75
|
+
};
|
|
76
|
+
}
|
package/src/scan.js
CHANGED
|
@@ -207,6 +207,13 @@ export async function scanProject(rootInput, options = {}) {
|
|
|
207
207
|
return summary;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
export async function writeScanOutput(outputPath, content) {
|
|
211
|
+
const resolvedPath = path.resolve(outputPath);
|
|
212
|
+
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
213
|
+
await fs.writeFile(resolvedPath, content, "utf8");
|
|
214
|
+
return resolvedPath;
|
|
215
|
+
}
|
|
216
|
+
|
|
210
217
|
export function renderScanMarkdown(summary) {
|
|
211
218
|
const lines = [
|
|
212
219
|
"# GEO Signal Scan",
|