offcourse 0.0.2 ā 1.0.0
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/.github/workflows/ci.yml +50 -0
- package/.husky/commit-msg +2 -0
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +3 -0
- package/.prettierrc +8 -0
- package/.release-it.json +23 -0
- package/ARCHITECTURE.md +233 -0
- package/CHANGELOG.md +78 -0
- package/README.md +255 -20
- package/commitlint.config.js +4 -0
- package/dist/ai/openRouter.d.ts +47 -0
- package/dist/ai/openRouter.d.ts.map +1 -0
- package/dist/ai/openRouter.js +116 -0
- package/dist/ai/openRouter.js.map +1 -0
- package/dist/ai/transcriptPolisher.d.ts +24 -0
- package/dist/ai/transcriptPolisher.d.ts.map +1 -0
- package/dist/ai/transcriptPolisher.js +89 -0
- package/dist/ai/transcriptPolisher.js.map +1 -0
- package/dist/cli/commands/config.d.ts +13 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +66 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/enrich.d.ts +14 -0
- package/dist/cli/commands/enrich.d.ts.map +1 -0
- package/dist/cli/commands/enrich.js +271 -0
- package/dist/cli/commands/enrich.js.map +1 -0
- package/dist/cli/commands/inspect.d.ts +11 -0
- package/dist/cli/commands/inspect.d.ts.map +1 -0
- package/dist/cli/commands/inspect.js +365 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/login.d.ts +12 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +55 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/status.d.ts +15 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +118 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +16 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +922 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/syncGhl.d.ts +20 -0
- package/dist/cli/commands/syncGhl.d.ts.map +1 -0
- package/dist/cli/commands/syncGhl.js +483 -0
- package/dist/cli/commands/syncGhl.js.map +1 -0
- package/dist/cli/commands/syncHighLevel.d.ts +24 -0
- package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
- package/dist/cli/commands/syncHighLevel.js +483 -0
- package/dist/cli/commands/syncHighLevel.js.map +1 -0
- package/dist/cli/commands/syncHighLevel.test.d.ts +2 -0
- package/dist/cli/commands/syncHighLevel.test.d.ts.map +1 -0
- package/dist/cli/commands/syncHighLevel.test.js +102 -0
- package/dist/cli/commands/syncHighLevel.test.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/configManager.d.ts +31 -0
- package/dist/config/configManager.d.ts.map +1 -0
- package/dist/config/configManager.js +64 -0
- package/dist/config/configManager.js.map +1 -0
- package/dist/config/paths.d.ts +21 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +33 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/paths.test.d.ts +2 -0
- package/dist/config/paths.test.d.ts.map +1 -0
- package/dist/config/paths.test.js +70 -0
- package/dist/config/paths.test.js.map +1 -0
- package/dist/config/schema.d.ts +60 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +50 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/schema.test.d.ts +2 -0
- package/dist/config/schema.test.d.ts.map +1 -0
- package/dist/config/schema.test.js +151 -0
- package/dist/config/schema.test.js.map +1 -0
- package/dist/downloader/hlsDownloader.d.ts +58 -0
- package/dist/downloader/hlsDownloader.d.ts.map +1 -0
- package/dist/downloader/hlsDownloader.js +254 -0
- package/dist/downloader/hlsDownloader.js.map +1 -0
- package/dist/downloader/hlsDownloader.test.d.ts +2 -0
- package/dist/downloader/hlsDownloader.test.d.ts.map +1 -0
- package/dist/downloader/hlsDownloader.test.js +116 -0
- package/dist/downloader/hlsDownloader.test.js.map +1 -0
- package/dist/downloader/hlsValidator.d.ts +35 -0
- package/dist/downloader/hlsValidator.d.ts.map +1 -0
- package/dist/downloader/hlsValidator.js +148 -0
- package/dist/downloader/hlsValidator.js.map +1 -0
- package/dist/downloader/index.d.ts +26 -0
- package/dist/downloader/index.d.ts.map +1 -0
- package/dist/downloader/index.js +52 -0
- package/dist/downloader/index.js.map +1 -0
- package/dist/downloader/loomDownloader.d.ts +56 -0
- package/dist/downloader/loomDownloader.d.ts.map +1 -0
- package/dist/downloader/loomDownloader.js +559 -0
- package/dist/downloader/loomDownloader.js.map +1 -0
- package/dist/downloader/loomDownloader.test.d.ts +2 -0
- package/dist/downloader/loomDownloader.test.d.ts.map +1 -0
- package/dist/downloader/loomDownloader.test.js +36 -0
- package/dist/downloader/loomDownloader.test.js.map +1 -0
- package/dist/downloader/queue.d.ts +56 -0
- package/dist/downloader/queue.d.ts.map +1 -0
- package/dist/downloader/queue.js +88 -0
- package/dist/downloader/queue.js.map +1 -0
- package/dist/downloader/queue.test.d.ts +2 -0
- package/dist/downloader/queue.test.d.ts.map +1 -0
- package/dist/downloader/queue.test.js +158 -0
- package/dist/downloader/queue.test.js.map +1 -0
- package/dist/downloader/videoDownloader.d.ts +32 -0
- package/dist/downloader/videoDownloader.d.ts.map +1 -0
- package/dist/downloader/videoDownloader.js +173 -0
- package/dist/downloader/videoDownloader.js.map +1 -0
- package/dist/downloader/vimeoDownloader.d.ts +52 -0
- package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
- package/dist/downloader/vimeoDownloader.js +565 -0
- package/dist/downloader/vimeoDownloader.js.map +1 -0
- package/dist/downloader/vimeoDownloader.test.d.ts +2 -0
- package/dist/downloader/vimeoDownloader.test.d.ts.map +1 -0
- package/dist/downloader/vimeoDownloader.test.js +51 -0
- package/dist/downloader/vimeoDownloader.test.js.map +1 -0
- package/dist/scraper/auth.d.ts +29 -0
- package/dist/scraper/auth.d.ts.map +1 -0
- package/dist/scraper/auth.js +115 -0
- package/dist/scraper/auth.js.map +1 -0
- package/dist/scraper/extractor.d.ts +49 -0
- package/dist/scraper/extractor.d.ts.map +1 -0
- package/dist/scraper/extractor.js +627 -0
- package/dist/scraper/extractor.js.map +1 -0
- package/dist/scraper/extractor.test.d.ts +2 -0
- package/dist/scraper/extractor.test.d.ts.map +1 -0
- package/dist/scraper/extractor.test.js +65 -0
- package/dist/scraper/extractor.test.js.map +1 -0
- package/dist/scraper/ghl/auth.d.ts +25 -0
- package/dist/scraper/ghl/auth.d.ts.map +1 -0
- package/dist/scraper/ghl/auth.js +187 -0
- package/dist/scraper/ghl/auth.js.map +1 -0
- package/dist/scraper/ghl/extractor.d.ts +96 -0
- package/dist/scraper/ghl/extractor.d.ts.map +1 -0
- package/dist/scraper/ghl/extractor.js +345 -0
- package/dist/scraper/ghl/extractor.js.map +1 -0
- package/dist/scraper/ghl/index.d.ts +4 -0
- package/dist/scraper/ghl/index.d.ts.map +1 -0
- package/dist/scraper/ghl/index.js +4 -0
- package/dist/scraper/ghl/index.js.map +1 -0
- package/dist/scraper/ghl/navigator.d.ts +93 -0
- package/dist/scraper/ghl/navigator.d.ts.map +1 -0
- package/dist/scraper/ghl/navigator.js +447 -0
- package/dist/scraper/ghl/navigator.js.map +1 -0
- package/dist/scraper/highlevel/auth.d.ts +25 -0
- package/dist/scraper/highlevel/auth.d.ts.map +1 -0
- package/dist/scraper/highlevel/auth.js +189 -0
- package/dist/scraper/highlevel/auth.js.map +1 -0
- package/dist/scraper/highlevel/extractor.d.ts +97 -0
- package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
- package/dist/scraper/highlevel/extractor.js +386 -0
- package/dist/scraper/highlevel/extractor.js.map +1 -0
- package/dist/scraper/highlevel/extractor.test.d.ts +2 -0
- package/dist/scraper/highlevel/extractor.test.d.ts.map +1 -0
- package/dist/scraper/highlevel/extractor.test.js +101 -0
- package/dist/scraper/highlevel/extractor.test.js.map +1 -0
- package/dist/scraper/highlevel/index.d.ts +3 -0
- package/dist/scraper/highlevel/index.d.ts.map +1 -0
- package/dist/scraper/highlevel/index.js +3 -0
- package/dist/scraper/highlevel/index.js.map +1 -0
- package/dist/scraper/highlevel/navigator.d.ts +93 -0
- package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
- package/dist/scraper/highlevel/navigator.js +492 -0
- package/dist/scraper/highlevel/navigator.js.map +1 -0
- package/dist/scraper/highlevel/navigator.test.d.ts +2 -0
- package/dist/scraper/highlevel/navigator.test.d.ts.map +1 -0
- package/dist/scraper/highlevel/navigator.test.js +78 -0
- package/dist/scraper/highlevel/navigator.test.js.map +1 -0
- package/dist/scraper/navigator.d.ts +65 -0
- package/dist/scraper/navigator.d.ts.map +1 -0
- package/dist/scraper/navigator.js +300 -0
- package/dist/scraper/navigator.js.map +1 -0
- package/dist/scraper/navigator.test.d.ts +2 -0
- package/dist/scraper/navigator.test.d.ts.map +1 -0
- package/dist/scraper/navigator.test.js +63 -0
- package/dist/scraper/navigator.test.js.map +1 -0
- package/dist/scraper/skoolApi.d.ts +17 -0
- package/dist/scraper/skoolApi.d.ts.map +1 -0
- package/dist/scraper/skoolApi.js +72 -0
- package/dist/scraper/skoolApi.js.map +1 -0
- package/dist/scraper/videoInterceptor.d.ts +19 -0
- package/dist/scraper/videoInterceptor.d.ts.map +1 -0
- package/dist/scraper/videoInterceptor.js +315 -0
- package/dist/scraper/videoInterceptor.js.map +1 -0
- package/dist/shared/auth.d.ts +58 -0
- package/dist/shared/auth.d.ts.map +1 -0
- package/dist/shared/auth.js +211 -0
- package/dist/shared/auth.js.map +1 -0
- package/dist/shared/fs.d.ts +31 -0
- package/dist/shared/fs.d.ts.map +1 -0
- package/dist/shared/fs.js +73 -0
- package/dist/shared/fs.js.map +1 -0
- package/dist/shared/http.d.ts +15 -0
- package/dist/shared/http.d.ts.map +1 -0
- package/dist/shared/http.js +31 -0
- package/dist/shared/http.js.map +1 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/state/database.d.ts +245 -0
- package/dist/state/database.d.ts.map +1 -0
- package/dist/state/database.js +676 -0
- package/dist/state/database.js.map +1 -0
- package/dist/state/database.test.d.ts +2 -0
- package/dist/state/database.test.d.ts.map +1 -0
- package/dist/state/database.test.js +34 -0
- package/dist/state/database.test.js.map +1 -0
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +2 -0
- package/dist/state/index.js.map +1 -0
- package/dist/storage/fileSystem.d.ts +56 -0
- package/dist/storage/fileSystem.d.ts.map +1 -0
- package/dist/storage/fileSystem.js +121 -0
- package/dist/storage/fileSystem.js.map +1 -0
- package/dist/transcription/whisperService.d.ts +27 -0
- package/dist/transcription/whisperService.d.ts.map +1 -0
- package/dist/transcription/whisperService.js +102 -0
- package/dist/transcription/whisperService.js.map +1 -0
- package/eslint.config.js +55 -0
- package/package.json +68 -11
- package/src/__fixtures__/highlevel-post-response.json +68 -0
- package/src/__fixtures__/hls-master-playlist.m3u8 +24 -0
- package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +38 -0
- package/src/cli/commands/config.ts +74 -0
- package/src/cli/commands/inspect.ts +441 -0
- package/src/cli/commands/login.ts +68 -0
- package/src/cli/commands/status.ts +147 -0
- package/src/cli/commands/sync.ts +1235 -0
- package/src/cli/commands/syncHighLevel.test.ts +144 -0
- package/src/cli/commands/syncHighLevel.ts +639 -0
- package/src/cli/index.ts +121 -0
- package/src/config/configManager.ts +75 -0
- package/src/config/paths.test.ts +83 -0
- package/src/config/paths.ts +36 -0
- package/src/config/schema.test.ts +173 -0
- package/src/config/schema.ts +65 -0
- package/src/downloader/hlsDownloader.test.ts +148 -0
- package/src/downloader/hlsDownloader.ts +327 -0
- package/src/downloader/hlsValidator.ts +196 -0
- package/src/downloader/index.ts +122 -0
- package/src/downloader/loomDownloader.test.ts +43 -0
- package/src/downloader/loomDownloader.ts +742 -0
- package/src/downloader/queue.test.ts +199 -0
- package/src/downloader/queue.ts +118 -0
- package/src/downloader/vimeoDownloader.test.ts +62 -0
- package/src/downloader/vimeoDownloader.ts +722 -0
- package/src/scraper/extractor.test.ts +124 -0
- package/src/scraper/extractor.ts +757 -0
- package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +41 -0
- package/src/scraper/highlevel/extractor.test.ts +134 -0
- package/src/scraper/highlevel/extractor.ts +537 -0
- package/src/scraper/highlevel/index.ts +2 -0
- package/src/scraper/highlevel/navigator.test.ts +110 -0
- package/src/scraper/highlevel/navigator.ts +668 -0
- package/src/scraper/highlevel/schemas.ts +183 -0
- package/src/scraper/navigator.test.ts +122 -0
- package/src/scraper/navigator.ts +355 -0
- package/src/scraper/schemas.ts +177 -0
- package/src/scraper/videoInterceptor.ts +435 -0
- package/src/shared/auth.test.ts +58 -0
- package/src/shared/auth.ts +251 -0
- package/src/shared/firebase.ts +151 -0
- package/src/shared/fs.ts +80 -0
- package/src/shared/http.ts +34 -0
- package/src/shared/index.ts +6 -0
- package/src/shared/slug.ts +26 -0
- package/src/shared/url.test.ts +122 -0
- package/src/shared/url.ts +57 -0
- package/src/state/database.test.ts +49 -0
- package/src/state/database.ts +919 -0
- package/src/state/index.ts +14 -0
- package/src/storage/fileSystem.test.ts +64 -0
- package/src/storage/fileSystem.ts +175 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +29 -0
- package/cli.js +0 -45
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`formatHighLevelMarkdown > snapshot: complete lesson with all parts 1`] = `
|
|
4
|
+
"# Complete Module: Advanced Techniques
|
|
5
|
+
|
|
6
|
+
In this comprehensive module, you'll learn advanced strategies.
|
|
7
|
+
|
|
8
|
+
## Video
|
|
9
|
+
|
|
10
|
+
Video URL: https://cdn.example.com/videos/advanced-techniques.mp4
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Let's dive into the key concepts:
|
|
15
|
+
|
|
16
|
+
- Concept A
|
|
17
|
+
- Concept B
|
|
18
|
+
- Concept C
|
|
19
|
+
Remember to practice daily!
|
|
20
|
+
"
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
exports[`formatHighLevelMarkdown > snapshot: lesson with HTML entities and special chars 1`] = `
|
|
24
|
+
"# Q&A Session: Common Questions
|
|
25
|
+
|
|
26
|
+
Your questions answered!
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
Q: What's the best approach?
|
|
31
|
+
|
|
32
|
+
A: It depends on your goals & resources.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
<script> tags are blocked
|
|
37
|
+
"
|
|
38
|
+
`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfigValue, loadConfig, updateConfig } from "../../config/configManager.js";
|
|
3
|
+
import { CONFIG_FILE } from "../../config/paths.js";
|
|
4
|
+
import type { Config } from "../../config/schema.js";
|
|
5
|
+
import { configSchema } from "../../config/schema.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shows all current configuration values.
|
|
9
|
+
*/
|
|
10
|
+
export function configShowCommand(): void {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
|
|
13
|
+
console.log(chalk.blue("\nāļø Configuration\n"));
|
|
14
|
+
console.log(chalk.gray(` File: ${CONFIG_FILE}\n`));
|
|
15
|
+
|
|
16
|
+
for (const [key, value] of Object.entries(config)) {
|
|
17
|
+
console.log(` ${chalk.cyan(key)}: ${chalk.white(String(value))}`);
|
|
18
|
+
}
|
|
19
|
+
console.log();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sets a configuration value.
|
|
24
|
+
*/
|
|
25
|
+
export function configSetCommand(key: string, value: string): void {
|
|
26
|
+
const validKeys = Object.keys(configSchema.shape) as (keyof Config)[];
|
|
27
|
+
|
|
28
|
+
if (!validKeys.includes(key as keyof Config)) {
|
|
29
|
+
console.log(chalk.red(`\nā Unknown config key: ${key}`));
|
|
30
|
+
console.log(chalk.gray(` Valid keys: ${validKeys.join(", ")}\n`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Parse value based on expected type
|
|
35
|
+
const currentValue = getConfigValue(key as keyof Config);
|
|
36
|
+
let parsedValue: string | number | boolean;
|
|
37
|
+
|
|
38
|
+
if (typeof currentValue === "boolean") {
|
|
39
|
+
parsedValue = value === "true" || value === "1";
|
|
40
|
+
} else if (typeof currentValue === "number") {
|
|
41
|
+
parsedValue = parseInt(value, 10);
|
|
42
|
+
if (isNaN(parsedValue)) {
|
|
43
|
+
console.log(chalk.red(`\nā Invalid number: ${value}\n`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
parsedValue = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
updateConfig({ [key]: parsedValue });
|
|
52
|
+
console.log(chalk.green(`\nā
Set ${key} = ${parsedValue}\n`));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.log(chalk.red(`\nā Invalid value for ${key}: ${value}`));
|
|
55
|
+
console.log(chalk.gray(` ${String(error)}\n`));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Gets a specific configuration value.
|
|
62
|
+
*/
|
|
63
|
+
export function configGetCommand(key: string): void {
|
|
64
|
+
const validKeys = Object.keys(configSchema.shape) as (keyof Config)[];
|
|
65
|
+
|
|
66
|
+
if (!validKeys.includes(key as keyof Config)) {
|
|
67
|
+
console.log(chalk.red(`\nā Unknown config key: ${key}`));
|
|
68
|
+
console.log(chalk.gray(` Valid keys: ${validKeys.join(", ")}\n`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const value = getConfigValue(key as keyof Config);
|
|
73
|
+
console.log(String(value));
|
|
74
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { loadConfig } from "../../config/configManager.js";
|
|
5
|
+
import { expandPath } from "../../config/paths.js";
|
|
6
|
+
import { getAuthenticatedSession, isSkoolLoginPage } from "../../shared/auth.js";
|
|
7
|
+
import { ensureDir, outputFile } from "../../shared/fs.js";
|
|
8
|
+
|
|
9
|
+
const SKOOL_DOMAIN = "www.skool.com";
|
|
10
|
+
const SKOOL_LOGIN_URL = "https://www.skool.com/login";
|
|
11
|
+
|
|
12
|
+
interface InspectOptions {
|
|
13
|
+
output?: string;
|
|
14
|
+
full?: boolean;
|
|
15
|
+
click?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Inspects the page structure and logs useful debugging info.
|
|
20
|
+
*/
|
|
21
|
+
export async function inspectCommand(url: string, options: InspectOptions): Promise<void> {
|
|
22
|
+
console.log(chalk.blue("\nš Page Inspector\n"));
|
|
23
|
+
|
|
24
|
+
const config = loadConfig();
|
|
25
|
+
const spinner = ora("Connecting...").start();
|
|
26
|
+
|
|
27
|
+
let browser;
|
|
28
|
+
let session;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const result = await getAuthenticatedSession(
|
|
32
|
+
{
|
|
33
|
+
domain: SKOOL_DOMAIN,
|
|
34
|
+
loginUrl: SKOOL_LOGIN_URL,
|
|
35
|
+
isLoginPage: isSkoolLoginPage,
|
|
36
|
+
},
|
|
37
|
+
{ headless: false } // Always visible for inspection
|
|
38
|
+
);
|
|
39
|
+
browser = result.browser;
|
|
40
|
+
session = result.session;
|
|
41
|
+
spinner.succeed("Connected");
|
|
42
|
+
} catch {
|
|
43
|
+
spinner.fail("Failed to connect");
|
|
44
|
+
console.log(chalk.red("\nā Please run: course-grab login\n"));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Collect network requests to find video URLs
|
|
50
|
+
const videoRequests: { url: string; resourceType: string }[] = [];
|
|
51
|
+
session.page.on("request", (request) => {
|
|
52
|
+
const reqUrl = request.url();
|
|
53
|
+
const resourceType = request.resourceType();
|
|
54
|
+
if (
|
|
55
|
+
resourceType === "media" ||
|
|
56
|
+
reqUrl.includes(".mp4") ||
|
|
57
|
+
reqUrl.includes(".m3u8") ||
|
|
58
|
+
reqUrl.includes(".webm") ||
|
|
59
|
+
reqUrl.includes("vimeo") ||
|
|
60
|
+
reqUrl.includes("wistia") ||
|
|
61
|
+
reqUrl.includes("mux.com") ||
|
|
62
|
+
reqUrl.includes("cloudflare") ||
|
|
63
|
+
reqUrl.includes("stream")
|
|
64
|
+
) {
|
|
65
|
+
videoRequests.push({ url: reqUrl, resourceType });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const pageSpinner = ora("Loading page...").start();
|
|
70
|
+
await session.page.goto(url, { timeout: 60000 });
|
|
71
|
+
// Use domcontentloaded instead of networkidle - some pages never stop loading
|
|
72
|
+
await session.page.waitForLoadState("domcontentloaded");
|
|
73
|
+
// Give it a moment for JS to render
|
|
74
|
+
await session.page.waitForTimeout(2000);
|
|
75
|
+
pageSpinner.succeed("Page loaded");
|
|
76
|
+
|
|
77
|
+
console.log(chalk.cyan("\nš Page Info:\n"));
|
|
78
|
+
console.log(` URL: ${session.page.url()}`);
|
|
79
|
+
console.log(` Title: ${await session.page.title()}`);
|
|
80
|
+
|
|
81
|
+
// Look for video preview/placeholder elements
|
|
82
|
+
const previewInfo = await session.page.evaluate(() => {
|
|
83
|
+
const previews: {
|
|
84
|
+
selector: string;
|
|
85
|
+
description: string;
|
|
86
|
+
element: string;
|
|
87
|
+
}[] = [];
|
|
88
|
+
|
|
89
|
+
// Common video preview patterns
|
|
90
|
+
const previewSelectors = [
|
|
91
|
+
// Play buttons
|
|
92
|
+
'[class*="play"]',
|
|
93
|
+
'[class*="Play"]',
|
|
94
|
+
'button[class*="video"]',
|
|
95
|
+
'[aria-label*="play" i]',
|
|
96
|
+
'[aria-label*="Play" i]',
|
|
97
|
+
// Thumbnail overlays
|
|
98
|
+
'[class*="thumbnail"]',
|
|
99
|
+
'[class*="poster"]',
|
|
100
|
+
'[class*="preview"]',
|
|
101
|
+
'[class*="cover"]',
|
|
102
|
+
// SVG play icons
|
|
103
|
+
'svg[class*="play"]',
|
|
104
|
+
// Clickable video containers
|
|
105
|
+
'[class*="video-container"]',
|
|
106
|
+
'[class*="player-container"]',
|
|
107
|
+
'[class*="video-wrapper"]',
|
|
108
|
+
// Data attributes
|
|
109
|
+
"[data-video]",
|
|
110
|
+
"[data-video-id]",
|
|
111
|
+
"[data-src]",
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const selector of previewSelectors) {
|
|
115
|
+
const elements = document.querySelectorAll(selector);
|
|
116
|
+
elements.forEach((el) => {
|
|
117
|
+
const rect = el.getBoundingClientRect();
|
|
118
|
+
// Only consider visible, reasonably sized elements
|
|
119
|
+
if (rect.width > 50 && rect.height > 50) {
|
|
120
|
+
previews.push({
|
|
121
|
+
selector,
|
|
122
|
+
description: `<${el.tagName.toLowerCase()}> class="${el.className}" (${Math.round(rect.width)}x${Math.round(rect.height)})`,
|
|
123
|
+
element: el.outerHTML.substring(0, 200),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return previews;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (previewInfo.length > 0) {
|
|
133
|
+
console.log(chalk.yellow("\nš¬ Potential Video Previews/Placeholders:\n"));
|
|
134
|
+
const seen = new Set<string>();
|
|
135
|
+
for (const preview of previewInfo) {
|
|
136
|
+
if (seen.has(preview.description)) continue;
|
|
137
|
+
seen.add(preview.description);
|
|
138
|
+
console.log(` ${preview.description}`);
|
|
139
|
+
console.log(chalk.gray(` selector: ${preview.selector}`));
|
|
140
|
+
console.log(chalk.gray(` html: ${preview.element.substring(0, 100)}...`));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// If --click flag, try to click on video preview
|
|
145
|
+
if (options.click) {
|
|
146
|
+
console.log(chalk.cyan("\nš Attempting to click video preview...\n"));
|
|
147
|
+
|
|
148
|
+
const clicked = await session.page.evaluate(() => {
|
|
149
|
+
// Try various selectors for play button
|
|
150
|
+
const playSelectors = [
|
|
151
|
+
'[class*="play"]',
|
|
152
|
+
'[class*="Play"]',
|
|
153
|
+
'button[class*="video"]',
|
|
154
|
+
'[class*="poster"]',
|
|
155
|
+
'[class*="thumbnail"]',
|
|
156
|
+
'[class*="video-container"]',
|
|
157
|
+
'[class*="player"]',
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
for (const selector of playSelectors) {
|
|
161
|
+
const el = document.querySelector(selector);
|
|
162
|
+
if (el && el instanceof HTMLElement) {
|
|
163
|
+
const rect = el.getBoundingClientRect();
|
|
164
|
+
if (rect.width > 50 && rect.height > 50) {
|
|
165
|
+
el.click();
|
|
166
|
+
return { clicked: true, selector, element: el.outerHTML.substring(0, 100) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { clicked: false };
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (clicked.clicked) {
|
|
174
|
+
console.log(chalk.green(` ā Clicked on: ${clicked.selector}`));
|
|
175
|
+
console.log(chalk.gray(` ${clicked.element}...`));
|
|
176
|
+
|
|
177
|
+
// Wait for video element to appear
|
|
178
|
+
console.log(chalk.gray(" Waiting for video element..."));
|
|
179
|
+
try {
|
|
180
|
+
await session.page.waitForSelector(
|
|
181
|
+
"video, iframe[src*='vimeo'], iframe[src*='wistia'], iframe[src*='youtube']",
|
|
182
|
+
{
|
|
183
|
+
timeout: 5000,
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
console.log(chalk.green(" ā Video element appeared!"));
|
|
187
|
+
} catch {
|
|
188
|
+
console.log(chalk.yellow(" ā No video element detected after click"));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Small delay for any animations/loading
|
|
192
|
+
await session.page.waitForTimeout(1000);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(chalk.yellow(" ā No clickable preview found"));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Analyze page structure
|
|
199
|
+
const analysis = await session.page.evaluate(() => {
|
|
200
|
+
const result: Record<string, unknown> = {};
|
|
201
|
+
|
|
202
|
+
// Find all iframes (potential video embeds)
|
|
203
|
+
const iframes = document.querySelectorAll("iframe");
|
|
204
|
+
result.iframes = Array.from(iframes).map((iframe) => ({
|
|
205
|
+
src: iframe.src,
|
|
206
|
+
id: iframe.id,
|
|
207
|
+
className: iframe.className,
|
|
208
|
+
width: iframe.width,
|
|
209
|
+
height: iframe.height,
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
// Find all video elements
|
|
213
|
+
const videos = document.querySelectorAll("video");
|
|
214
|
+
result.videos = Array.from(videos).map((video) => ({
|
|
215
|
+
src: video.src,
|
|
216
|
+
poster: video.poster,
|
|
217
|
+
className: video.className,
|
|
218
|
+
sources: Array.from(video.querySelectorAll("source")).map((s) => ({
|
|
219
|
+
src: s.src,
|
|
220
|
+
type: s.type,
|
|
221
|
+
})),
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
// Find elements with video-related classes
|
|
225
|
+
const videoRelated = document.querySelectorAll(
|
|
226
|
+
'[class*="video"], [class*="player"], [class*="wistia"], [class*="vimeo"], [class*="embed"], [class*="media"]'
|
|
227
|
+
);
|
|
228
|
+
result.videoRelatedElements = Array.from(videoRelated).map((el) => ({
|
|
229
|
+
tagName: el.tagName,
|
|
230
|
+
className: el.className,
|
|
231
|
+
id: el.id,
|
|
232
|
+
dataAttributes: Object.fromEntries(
|
|
233
|
+
Array.from(el.attributes)
|
|
234
|
+
.filter((attr) => attr.name.startsWith("data-"))
|
|
235
|
+
.map((attr) => [attr.name, attr.value])
|
|
236
|
+
),
|
|
237
|
+
// Include src attributes that might have video URLs
|
|
238
|
+
src: el.getAttribute("src"),
|
|
239
|
+
href: el.getAttribute("href"),
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
// Look for any script tags that might contain video configuration
|
|
243
|
+
const scripts = document.querySelectorAll("script");
|
|
244
|
+
const videoScripts: string[] = [];
|
|
245
|
+
scripts.forEach((script) => {
|
|
246
|
+
const content = script.textContent || "";
|
|
247
|
+
if (
|
|
248
|
+
content.includes("vimeo") ||
|
|
249
|
+
content.includes("wistia") ||
|
|
250
|
+
content.includes("video") ||
|
|
251
|
+
content.includes("player") ||
|
|
252
|
+
content.includes("mux") ||
|
|
253
|
+
content.includes("cloudflare")
|
|
254
|
+
) {
|
|
255
|
+
// Extract just the relevant parts
|
|
256
|
+
const matches = content.match(/(https?:\/\/[^\s"']+\.(mp4|m3u8|webm|mov)[^\s"']*)/gi);
|
|
257
|
+
if (matches) {
|
|
258
|
+
videoScripts.push(...matches);
|
|
259
|
+
}
|
|
260
|
+
// Also look for video IDs
|
|
261
|
+
const idMatches = content.match(/"(video[_-]?id|videoId|id)":\s*"([^"]+)"/gi);
|
|
262
|
+
if (idMatches) {
|
|
263
|
+
videoScripts.push(...idMatches);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
result.videoScripts = [...new Set(videoScripts)];
|
|
268
|
+
|
|
269
|
+
// Find navigation/sidebar elements (for lessons)
|
|
270
|
+
const navElements = document.querySelectorAll(
|
|
271
|
+
'nav, [class*="sidebar"], [class*="nav"], [class*="menu"], [class*="lesson"]'
|
|
272
|
+
);
|
|
273
|
+
result.navigationElements = Array.from(navElements)
|
|
274
|
+
.slice(0, 10)
|
|
275
|
+
.map((el) => ({
|
|
276
|
+
tagName: el.tagName,
|
|
277
|
+
className: el.className,
|
|
278
|
+
id: el.id,
|
|
279
|
+
childCount: el.children.length,
|
|
280
|
+
links: Array.from(el.querySelectorAll("a")).map((a) => ({
|
|
281
|
+
href: a.href,
|
|
282
|
+
text: a.textContent?.trim().substring(0, 50),
|
|
283
|
+
})),
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
// Find main content area
|
|
287
|
+
const contentAreas = document.querySelectorAll(
|
|
288
|
+
'main, article, [class*="content"], [class*="post"], [class*="body"]'
|
|
289
|
+
);
|
|
290
|
+
result.contentAreas = Array.from(contentAreas)
|
|
291
|
+
.slice(0, 5)
|
|
292
|
+
.map((el) => ({
|
|
293
|
+
tagName: el.tagName,
|
|
294
|
+
className: el.className,
|
|
295
|
+
id: el.id,
|
|
296
|
+
textLength: el.textContent?.length ?? 0,
|
|
297
|
+
hasVideo: el.querySelector("video, iframe") !== null,
|
|
298
|
+
}));
|
|
299
|
+
|
|
300
|
+
// Find all links to /classroom/
|
|
301
|
+
const classroomLinks = document.querySelectorAll('a[href*="/classroom/"]');
|
|
302
|
+
result.classroomLinks = Array.from(classroomLinks).map((a) => ({
|
|
303
|
+
href: (a as HTMLAnchorElement).href,
|
|
304
|
+
text: a.textContent?.trim().substring(0, 100),
|
|
305
|
+
className: a.className,
|
|
306
|
+
parentClass: a.parentElement?.className,
|
|
307
|
+
}));
|
|
308
|
+
|
|
309
|
+
// Get page structure overview
|
|
310
|
+
const getAllClasses = (el: Element, depth = 0): string[] => {
|
|
311
|
+
if (depth > 3) return [];
|
|
312
|
+
const classes: string[] = [];
|
|
313
|
+
if (el.className && typeof el.className === "string") {
|
|
314
|
+
classes.push(`${" ".repeat(depth)}${el.tagName}.${el.className.split(" ").join(".")}`);
|
|
315
|
+
}
|
|
316
|
+
Array.from(el.children).forEach((child) => {
|
|
317
|
+
classes.push(...getAllClasses(child, depth + 1));
|
|
318
|
+
});
|
|
319
|
+
return classes;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
result.bodyStructure = getAllClasses(document.body).slice(0, 100);
|
|
323
|
+
|
|
324
|
+
return result;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Output analysis
|
|
328
|
+
console.log(chalk.cyan("\nš¬ Video Sources:\n"));
|
|
329
|
+
if ((analysis.iframes as { src: string }[]).length > 0) {
|
|
330
|
+
console.log(chalk.yellow(" Iframes:"));
|
|
331
|
+
for (const iframe of analysis.iframes as { src: string; className: string }[]) {
|
|
332
|
+
console.log(` - ${iframe.src}`);
|
|
333
|
+
console.log(chalk.gray(` class: ${iframe.className}`));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if ((analysis.videos as { src: string }[]).length > 0) {
|
|
337
|
+
console.log(chalk.yellow("\n Video elements:"));
|
|
338
|
+
for (const video of analysis.videos as {
|
|
339
|
+
src: string;
|
|
340
|
+
sources: { src: string }[];
|
|
341
|
+
}[]) {
|
|
342
|
+
console.log(` - ${video.src || "(no src)"}`);
|
|
343
|
+
for (const source of video.sources) {
|
|
344
|
+
console.log(` source: ${source.src}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Show video URLs found in scripts
|
|
350
|
+
const videoScripts = analysis.videoScripts as string[];
|
|
351
|
+
if (videoScripts.length > 0) {
|
|
352
|
+
console.log(chalk.yellow("\n Video URLs from scripts:"));
|
|
353
|
+
for (const url of videoScripts.slice(0, 10)) {
|
|
354
|
+
console.log(chalk.green(` - ${url}`));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if ((analysis.videoRelatedElements as { className: string }[]).length > 0) {
|
|
359
|
+
console.log(chalk.yellow("\n Video-related elements:"));
|
|
360
|
+
for (const el of (
|
|
361
|
+
analysis.videoRelatedElements as {
|
|
362
|
+
tagName: string;
|
|
363
|
+
className: string;
|
|
364
|
+
dataAttributes: Record<string, string>;
|
|
365
|
+
src?: string;
|
|
366
|
+
}[]
|
|
367
|
+
).slice(0, 10)) {
|
|
368
|
+
console.log(` - <${el.tagName.toLowerCase()}> class="${el.className}"`);
|
|
369
|
+
if (el.src) {
|
|
370
|
+
console.log(chalk.green(` src: ${el.src}`));
|
|
371
|
+
}
|
|
372
|
+
if (Object.keys(el.dataAttributes).length > 0) {
|
|
373
|
+
console.log(chalk.gray(` data: ${JSON.stringify(el.dataAttributes)}`));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.log(chalk.cyan("\nš Classroom Links:\n"));
|
|
379
|
+
for (const link of (
|
|
380
|
+
analysis.classroomLinks as { href: string; text: string; className: string }[]
|
|
381
|
+
).slice(0, 15)) {
|
|
382
|
+
console.log(` - ${link.text || "(no text)"}`);
|
|
383
|
+
console.log(chalk.gray(` ${link.href}`));
|
|
384
|
+
console.log(chalk.gray(` class: ${link.className}`));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
console.log(chalk.cyan("\nš§ Navigation Elements:\n"));
|
|
388
|
+
for (const nav of analysis.navigationElements as {
|
|
389
|
+
tagName: string;
|
|
390
|
+
className: string;
|
|
391
|
+
links: { href: string; text: string }[];
|
|
392
|
+
}[]) {
|
|
393
|
+
console.log(` <${nav.tagName.toLowerCase()}> class="${nav.className}"`);
|
|
394
|
+
if (nav.links.length > 0) {
|
|
395
|
+
console.log(chalk.gray(` Links: ${nav.links.length}`));
|
|
396
|
+
for (const link of nav.links.slice(0, 5)) {
|
|
397
|
+
console.log(chalk.gray(` - ${link.text}: ${link.href}`));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Save full analysis to file if requested
|
|
403
|
+
if (options.output || options.full) {
|
|
404
|
+
const outputDir = expandPath(options.output ?? config.outputDir);
|
|
405
|
+
await ensureDir(outputDir);
|
|
406
|
+
|
|
407
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
408
|
+
const filename = `inspect-${timestamp}.json`;
|
|
409
|
+
const filepath = join(outputDir, filename);
|
|
410
|
+
|
|
411
|
+
await outputFile(filepath, JSON.stringify(analysis, null, 2));
|
|
412
|
+
console.log(chalk.green(`\nš Full analysis saved to: ${filepath}\n`));
|
|
413
|
+
|
|
414
|
+
// Also save HTML
|
|
415
|
+
if (options.full) {
|
|
416
|
+
const html = await session.page.content();
|
|
417
|
+
const htmlPath = join(outputDir, `inspect-${timestamp}.html`);
|
|
418
|
+
await outputFile(htmlPath, html);
|
|
419
|
+
console.log(chalk.green(`š HTML saved to: ${htmlPath}\n`));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Show network requests that looked like video
|
|
424
|
+
if (videoRequests.length > 0) {
|
|
425
|
+
console.log(chalk.cyan("\nš” Video-related Network Requests:\n"));
|
|
426
|
+
const seen = new Set<string>();
|
|
427
|
+
for (const req of videoRequests) {
|
|
428
|
+
if (seen.has(req.url)) continue;
|
|
429
|
+
seen.add(req.url);
|
|
430
|
+
console.log(chalk.green(` - ${req.url.substring(0, 120)}`));
|
|
431
|
+
console.log(chalk.gray(` type: ${req.resourceType}`));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log(chalk.gray("\nš” Tips:"));
|
|
436
|
+
console.log(chalk.gray(" - Use --click to trigger lazy-loaded video players"));
|
|
437
|
+
console.log(chalk.gray(" - Use --full to save complete HTML for offline analysis\n"));
|
|
438
|
+
} finally {
|
|
439
|
+
await browser.close();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import {
|
|
3
|
+
clearSession,
|
|
4
|
+
getAuthenticatedSession,
|
|
5
|
+
hasValidSession,
|
|
6
|
+
isSkoolLoginPage,
|
|
7
|
+
} from "../../shared/auth.js";
|
|
8
|
+
|
|
9
|
+
const SKOOL_DOMAIN = "www.skool.com";
|
|
10
|
+
const SKOOL_LOGIN_URL = "https://www.skool.com/login";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handles the login command.
|
|
14
|
+
* Opens a browser for the user to log in manually.
|
|
15
|
+
*/
|
|
16
|
+
export async function loginCommand(options: { force?: boolean }): Promise<void> {
|
|
17
|
+
console.log(chalk.blue("\nš Skool.com Login\n"));
|
|
18
|
+
|
|
19
|
+
if ((await hasValidSession(SKOOL_DOMAIN)) && !options.force) {
|
|
20
|
+
console.log(chalk.yellow("ā ļø You already have an active session."));
|
|
21
|
+
console.log(chalk.gray(" Use --force to re-login anyway.\n"));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (options.force) {
|
|
26
|
+
await clearSession(SKOOL_DOMAIN);
|
|
27
|
+
console.log(chalk.gray(" Cleared existing session.\n"));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const { browser } = await getAuthenticatedSession(
|
|
32
|
+
{
|
|
33
|
+
domain: SKOOL_DOMAIN,
|
|
34
|
+
loginUrl: SKOOL_LOGIN_URL,
|
|
35
|
+
isLoginPage: isSkoolLoginPage,
|
|
36
|
+
},
|
|
37
|
+
{ headless: false }
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Close the browser after successful login
|
|
41
|
+
await browser.close();
|
|
42
|
+
|
|
43
|
+
console.log(chalk.green("ā
Login successful!"));
|
|
44
|
+
console.log(chalk.gray(" Your session has been saved.\n"));
|
|
45
|
+
console.log(chalk.gray(" You can now use: offcourse sync <url>\n"));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof Error && error.message.includes("Timeout")) {
|
|
48
|
+
console.log(chalk.red("\nā Login timed out."));
|
|
49
|
+
console.log(chalk.gray(" Please try again and complete the login within 5 minutes.\n"));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(chalk.red("\nā Login failed:"), error);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handles the logout command.
|
|
59
|
+
*/
|
|
60
|
+
export async function logoutCommand(): Promise<void> {
|
|
61
|
+
console.log(chalk.blue("\nš Logging out...\n"));
|
|
62
|
+
|
|
63
|
+
if (await clearSession(SKOOL_DOMAIN)) {
|
|
64
|
+
console.log(chalk.green("ā
Session cleared successfully.\n"));
|
|
65
|
+
} else {
|
|
66
|
+
console.log(chalk.yellow("ā ļø No active session found.\n"));
|
|
67
|
+
}
|
|
68
|
+
}
|