extract-from-sitemap 0.0.1 ā 0.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/cli.ts +133 -13
- package/package.json +1 -1
package/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "fs";
|
|
13
13
|
import { join, dirname, resolve } from "path";
|
|
14
14
|
import { extractFromSitemap } from "./mod.js";
|
|
15
|
+
import { secrets } from "bun";
|
|
15
16
|
|
|
16
17
|
interface Config {
|
|
17
18
|
outDir: string;
|
|
@@ -30,10 +31,14 @@ interface Manifest {
|
|
|
30
31
|
timestamp: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
const SECRETS_SERVICE = "extract-from-sitemap-cli";
|
|
35
|
+
const SECRETS_KEY = "parallel-api-key";
|
|
36
|
+
|
|
33
37
|
class OAuth {
|
|
34
38
|
private clientId: string;
|
|
35
39
|
private redirectUri: string;
|
|
36
40
|
private scope: string;
|
|
41
|
+
private server?: Bun.Server;
|
|
37
42
|
|
|
38
43
|
constructor() {
|
|
39
44
|
this.clientId = "extract-from-sitemap-cli";
|
|
@@ -57,8 +62,10 @@ class OAuth {
|
|
|
57
62
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
58
63
|
authUrl.searchParams.set("state", Math.random().toString(36));
|
|
59
64
|
|
|
60
|
-
console.log(`\nš
|
|
61
|
-
|
|
65
|
+
console.log(`\nš Opening browser for authorization...`);
|
|
66
|
+
|
|
67
|
+
// Open browser automatically
|
|
68
|
+
await this.openBrowser(authUrl.toString());
|
|
62
69
|
|
|
63
70
|
// Start simple HTTP server to catch the callback
|
|
64
71
|
const code = await this.startCallbackServer();
|
|
@@ -90,6 +97,33 @@ class OAuth {
|
|
|
90
97
|
return access_token;
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
private async openBrowser(url: string): Promise<void> {
|
|
101
|
+
try {
|
|
102
|
+
const { spawn } = require("child_process");
|
|
103
|
+
const platform = process.platform;
|
|
104
|
+
|
|
105
|
+
let command: string;
|
|
106
|
+
let args: string[];
|
|
107
|
+
|
|
108
|
+
if (platform === "darwin") {
|
|
109
|
+
command = "open";
|
|
110
|
+
args = [url];
|
|
111
|
+
} else if (platform === "win32") {
|
|
112
|
+
command = "start";
|
|
113
|
+
args = ["", url];
|
|
114
|
+
} else {
|
|
115
|
+
// Linux/Unix
|
|
116
|
+
command = "xdg-open";
|
|
117
|
+
args = [url];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
spawn(command, args, { detached: true, stdio: "ignore" });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.log(`\nš Please visit this URL to authorize the application:`);
|
|
123
|
+
console.log(`${url}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
93
127
|
private async generatePKCE(): Promise<{
|
|
94
128
|
codeVerifier: string;
|
|
95
129
|
codeChallenge: string;
|
|
@@ -111,9 +145,9 @@ class OAuth {
|
|
|
111
145
|
|
|
112
146
|
private async startCallbackServer(): Promise<string> {
|
|
113
147
|
return new Promise((resolve, reject) => {
|
|
114
|
-
|
|
148
|
+
this.server = Bun.serve({
|
|
115
149
|
port: 3737,
|
|
116
|
-
fetch(req) {
|
|
150
|
+
fetch: (req) => {
|
|
117
151
|
const url = new URL(req.url);
|
|
118
152
|
|
|
119
153
|
if (url.pathname === "/callback") {
|
|
@@ -130,9 +164,14 @@ class OAuth {
|
|
|
130
164
|
|
|
131
165
|
if (code) {
|
|
132
166
|
resolve(code);
|
|
133
|
-
server
|
|
167
|
+
// Don't stop server here - let the cleanup happen in the finally block
|
|
134
168
|
return new Response(
|
|
135
|
-
"ā
Authorization successful! You can close this window and return to the terminal."
|
|
169
|
+
"ā
Authorization successful! You can close this window and return to the terminal.",
|
|
170
|
+
{
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "text/html",
|
|
173
|
+
},
|
|
174
|
+
}
|
|
136
175
|
);
|
|
137
176
|
}
|
|
138
177
|
}
|
|
@@ -143,11 +182,21 @@ class OAuth {
|
|
|
143
182
|
|
|
144
183
|
// Timeout after 5 minutes
|
|
145
184
|
setTimeout(() => {
|
|
146
|
-
|
|
185
|
+
this.stopServer();
|
|
147
186
|
reject(new Error("OAuth flow timed out"));
|
|
148
187
|
}, 300000);
|
|
188
|
+
}).finally(() => {
|
|
189
|
+
// Ensure server is stopped after promise resolves or rejects
|
|
190
|
+
this.stopServer();
|
|
149
191
|
});
|
|
150
192
|
}
|
|
193
|
+
|
|
194
|
+
private stopServer(): void {
|
|
195
|
+
if (this.server) {
|
|
196
|
+
this.server.stop();
|
|
197
|
+
this.server = undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
151
200
|
}
|
|
152
201
|
|
|
153
202
|
async function loadConfig(): Promise<Config> {
|
|
@@ -195,7 +244,22 @@ async function loadConfig(): Promise<Config> {
|
|
|
195
244
|
}
|
|
196
245
|
|
|
197
246
|
async function getApiKey(): Promise<string> {
|
|
198
|
-
// Check
|
|
247
|
+
// Check if we have a stored API key in the keychain
|
|
248
|
+
try {
|
|
249
|
+
const storedKey = await secrets.get({
|
|
250
|
+
service: SECRETS_SERVICE,
|
|
251
|
+
name: SECRETS_KEY,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (storedKey) {
|
|
255
|
+
console.log("š Using stored API key from keychain");
|
|
256
|
+
return storedKey;
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.warn("ā ļø Could not access keychain:", error.message);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check environment variables as fallback
|
|
199
263
|
let apiKey = process.env.PARALLEL_API_KEY;
|
|
200
264
|
|
|
201
265
|
if (!apiKey && existsSync(".env")) {
|
|
@@ -207,13 +271,43 @@ async function getApiKey(): Promise<string> {
|
|
|
207
271
|
}
|
|
208
272
|
}
|
|
209
273
|
|
|
210
|
-
if (
|
|
211
|
-
console.log("š
|
|
212
|
-
|
|
213
|
-
|
|
274
|
+
if (apiKey) {
|
|
275
|
+
console.log("š Using API key from environment");
|
|
276
|
+
// Store it in keychain for future use
|
|
277
|
+
try {
|
|
278
|
+
await secrets.set({
|
|
279
|
+
service: SECRETS_SERVICE,
|
|
280
|
+
name: SECRETS_KEY,
|
|
281
|
+
value: apiKey,
|
|
282
|
+
});
|
|
283
|
+
console.log("š¾ API key stored in keychain for future use");
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.warn("ā ļø Could not store API key in keychain:", error.message);
|
|
286
|
+
}
|
|
287
|
+
return apiKey;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// No API key found, start OAuth flow
|
|
291
|
+
console.log("š No API key found. Starting OAuth flow...");
|
|
292
|
+
const oauth = new OAuth();
|
|
293
|
+
const newApiKey = await oauth.getApiKey();
|
|
294
|
+
|
|
295
|
+
// Store the new API key in keychain
|
|
296
|
+
try {
|
|
297
|
+
await secrets.set({
|
|
298
|
+
service: SECRETS_SERVICE,
|
|
299
|
+
name: SECRETS_KEY,
|
|
300
|
+
value: newApiKey,
|
|
301
|
+
});
|
|
302
|
+
console.log("š¾ API key stored securely in keychain");
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.warn("ā ļø Could not store API key in keychain:", error.message);
|
|
305
|
+
console.log(
|
|
306
|
+
"š” You may need to set PARALLEL_API_KEY environment variable for future runs"
|
|
307
|
+
);
|
|
214
308
|
}
|
|
215
309
|
|
|
216
|
-
return
|
|
310
|
+
return newApiKey;
|
|
217
311
|
}
|
|
218
312
|
|
|
219
313
|
function loadManifest(outDir: string): Manifest {
|
|
@@ -311,9 +405,34 @@ async function processCustomUrls(
|
|
|
311
405
|
return files;
|
|
312
406
|
}
|
|
313
407
|
|
|
408
|
+
// Add command for clearing stored credentials
|
|
409
|
+
async function clearCredentials(): Promise<void> {
|
|
410
|
+
try {
|
|
411
|
+
const deleted = await secrets.delete({
|
|
412
|
+
service: SECRETS_SERVICE,
|
|
413
|
+
name: SECRETS_KEY,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
if (deleted) {
|
|
417
|
+
console.log("ā
Cleared stored API key from keychain");
|
|
418
|
+
} else {
|
|
419
|
+
console.log("ā¹ļø No stored API key found to clear");
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.error("ā Error clearing credentials:", error.message);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
314
426
|
async function main() {
|
|
315
427
|
console.log("š Extract from Sitemap CLI");
|
|
316
428
|
|
|
429
|
+
// Check for special commands
|
|
430
|
+
const args = process.argv.slice(2);
|
|
431
|
+
if (args.includes("--clear-credentials")) {
|
|
432
|
+
await clearCredentials();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
317
436
|
try {
|
|
318
437
|
const config = await loadConfig();
|
|
319
438
|
const apiKey = await getApiKey();
|
|
@@ -423,6 +542,7 @@ async function main() {
|
|
|
423
542
|
console.log(`ā ļø Errors: ${totalErrors}`);
|
|
424
543
|
}
|
|
425
544
|
console.log(`š Output directory: ${resolve(config.outDir)}`);
|
|
545
|
+
console.log(`\nš” Use --clear-credentials to remove stored API key`);
|
|
426
546
|
} catch (error) {
|
|
427
547
|
console.error("š„ Fatal error:", error.message);
|
|
428
548
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "extract-from-sitemap",
|
|
3
3
|
"bin": "cli.ts",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"main": "mod.js",
|
|
6
6
|
"description": "A module and CLI that allows extracting all pages from a sitemap into markdown and a llms.txt, using Parallel.ai APIs.",
|
|
7
7
|
"files": [
|