-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathcli-init.ts
More file actions
117 lines (101 loc) · 3.5 KB
/
Copy pathcli-init.ts
File metadata and controls
117 lines (101 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// CLI init --from logic: crawl a URL and generate pathfinder.yaml.
import fs from "fs";
import path from "path";
import { crawlSite, type CrawlOptions } from "./crawl.js";
import { generateConfigYaml, detectSourceType } from "./config-generator.js";
/**
* Validate that the input URL is a valid HTTP(S) URL.
*/
export function validateInitUrl(url: string): void {
let parsed: URL;
try {
parsed = new URL(url);
} catch {
throw new Error(
`Invalid URL: "${url}". Please provide a full URL like https://docs.example.com`,
);
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
throw new Error(`URL must be http or https, got: ${parsed.protocol}`);
}
}
/**
* Write generated config YAML to the target directory.
*/
export function writeGeneratedConfig(
targetDir: string,
yamlContent: string,
force: boolean = false,
): void {
const dest = path.join(targetDir, "pathfinder.yaml");
if (fs.existsSync(dest) && !force) {
throw new Error(
`pathfinder.yaml already exists at ${dest}. Use --force to overwrite.`,
);
}
fs.writeFileSync(dest, yamlContent, "utf-8");
}
/**
* Run the full init-from-url flow: crawl, generate config, write file.
*/
export async function initFromUrl(options: {
url: string;
targetDir: string;
rateLimit?: number;
maxPages?: number;
force?: boolean;
}): Promise<void> {
const { url, targetDir, rateLimit, maxPages, force } = options;
validateInitUrl(url);
const hostname = new URL(url).hostname;
const cacheDir = path.join(targetDir, ".pathfinder", "cache", hostname);
console.log(`\nCrawling ${url}...`);
console.log(` Rate limit: ${rateLimit ?? 500}ms between requests`);
console.log(` Max pages: ${maxPages ?? 500}`);
console.log(` Cache dir: ${cacheDir}\n`);
const crawlOptions: CrawlOptions = {
rateLimit: rateLimit ?? 500,
maxPages: maxPages ?? 500,
cacheDir,
};
const result = await crawlSite(url, crawlOptions);
if (result.pages.length === 0) {
console.error("\nNo pages were successfully crawled.");
if (result.failedUrls.length > 0) {
console.error(`\nFailed URLs (${result.failedUrls.length}):`);
for (const failed of result.failedUrls.slice(0, 10)) {
console.error(` ${failed.status} ${failed.url} (${failed.reason})`);
}
}
process.exit(1);
}
console.log(
`\nCrawled ${result.pages.length} pages via ${result.discoveryMethod}.`,
);
if (result.warnings.length > 0) {
console.log("\nWarnings:");
for (const warning of result.warnings) {
console.log(` Warning: ${warning}`);
}
}
if (result.failedUrls.length > 0) {
console.log(`\nFailed to fetch ${result.failedUrls.length} URLs:`);
for (const failed of result.failedUrls.slice(0, 5)) {
console.log(` ${failed.status} ${failed.url} (${failed.reason})`);
}
if (result.failedUrls.length > 5) {
console.log(` ... and ${result.failedUrls.length - 5} more`);
}
}
const yamlContent = generateConfigYaml(result, url);
writeGeneratedConfig(targetDir, yamlContent, force);
const sourceType =
result.pages.length > 0 ? detectSourceType(result.pages) : "unknown";
console.log(`\nGenerated pathfinder.yaml`);
console.log(` Source type: ${sourceType}`);
console.log(` Pages cached: ${result.pages.length} in ${cacheDir}`);
console.log(`\nNext steps:`);
console.log(` 1. Review pathfinder.yaml and adjust if needed`);
console.log(` 2. Set OPENAI_API_KEY and DATABASE_URL in .env`);
console.log(` 3. Run: pathfinder serve`);
}