skool-cli 1.0.2 → 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/package.json +1 -1
- package/CLAUDE.md +0 -104
- package/dist/commands/debug-api.d.ts +0 -3
- package/dist/commands/debug-api.d.ts.map +0 -1
- package/dist/commands/debug-api.js +0 -112
- package/dist/commands/debug-api.js.map +0 -1
- package/dist/commands/debug-manual.d.ts +0 -3
- package/dist/commands/debug-manual.d.ts.map +0 -1
- package/dist/commands/debug-manual.js +0 -66
- package/dist/commands/debug-manual.js.map +0 -1
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.d.ts.map +0 -1
- package/dist/commands/debug.js +0 -114
- package/dist/commands/debug.js.map +0 -1
- package/dist/commands/test-api.d.ts +0 -3
- package/dist/commands/test-api.d.ts.map +0 -1
- package/dist/commands/test-api.js +0 -143
- package/dist/commands/test-api.js.map +0 -1
- package/planning.md +0 -135
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skool-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "CLI and programmatic API for Skool.com automation — create lessons, posts, and manage communities via browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core/skool-client.js",
|
package/CLAUDE.md
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md - skool-cli
|
|
2
|
-
|
|
3
|
-
## Sobre este proyecto
|
|
4
|
-
|
|
5
|
-
CLI, Skill y MCP Server para automatizar Skool.com. Publicado en npm como `skool-cli` (v1.0.1) y en GitHub como `unikprompt/skool-cli`.
|
|
6
|
-
|
|
7
|
-
Skool no tiene API publica. Usamos Playwright para auth y la API interna `api2.skool.com` (descubierta via interceptacion de red) para operaciones de contenido.
|
|
8
|
-
|
|
9
|
-
## Links
|
|
10
|
-
|
|
11
|
-
- **npm**: npmjs.com/package/skool-cli
|
|
12
|
-
- **GitHub**: github.com/unikprompt/skool-cli
|
|
13
|
-
- **Skool group**: skool.com/operadores-aumentados
|
|
14
|
-
|
|
15
|
-
## Estructura
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
skool-cli/
|
|
19
|
-
src/
|
|
20
|
-
cli.ts # Entry point CLI (Commander.js)
|
|
21
|
-
mcp/server.ts # Entry point MCP Server
|
|
22
|
-
commands/ # 10 comandos CLI
|
|
23
|
-
core/
|
|
24
|
-
skool-client.ts # API publica (high-level)
|
|
25
|
-
skool-api.ts # HTTP client + HTML-to-TipTap converter
|
|
26
|
-
page-ops.ts # Playwright browser ops (nav, posts, auth)
|
|
27
|
-
browser-manager.ts # Playwright lifecycle + session persistence
|
|
28
|
-
html-generator.ts # Markdown/JSON → HTML (port de Content-Pipeline)
|
|
29
|
-
config.ts # Constantes y env vars
|
|
30
|
-
types.ts # TypeScript interfaces
|
|
31
|
-
skill/SKILL.md # Claude Code skill (alternativa liviana a MCP)
|
|
32
|
-
dist/ # Compiled JS (npm publish from here)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Comandos
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
skool login -e EMAIL -p PASSWORD # Auth (guarda cookies en ~/.skool-cli/)
|
|
39
|
-
skool whoami -g GROUP # Verifica sesion
|
|
40
|
-
skool create-lesson -g GROUP --course COURSE -t TITULO --markdown "contenido"
|
|
41
|
-
skool create-lesson -g GROUP --course COURSE --folder-id ID -t TITULO -f archivo.md
|
|
42
|
-
skool create-folder -g GROUP --course COURSE -t "Nombre Modulo"
|
|
43
|
-
skool delete-lesson --id PAGE_ID
|
|
44
|
-
skool list-lessons -g GROUP --course COURSE
|
|
45
|
-
skool create-post -g GROUP -t TITULO -b "body" -c CATEGORIA
|
|
46
|
-
skool get-posts -g GROUP
|
|
47
|
-
skool get-categories -g GROUP
|
|
48
|
-
skool get-members -g GROUP
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## API Interna de Skool (api2.skool.com)
|
|
52
|
-
|
|
53
|
-
| Endpoint | Metodo | Que hace |
|
|
54
|
-
|----------|--------|----------|
|
|
55
|
-
| `/courses` | POST | Crea pagina (`unit_type: "module"`) o folder (`unit_type: "set"`) |
|
|
56
|
-
| `/courses/{id}` | PUT | Actualiza titulo + contenido (`desc: "[v2]" + TipTap JSON`) |
|
|
57
|
-
| `/courses/{id}` | DELETE | Elimina pagina o folder |
|
|
58
|
-
|
|
59
|
-
**Auth**: Cookies del browser (`auth_token` JWT).
|
|
60
|
-
|
|
61
|
-
**IDs necesarios**: `group_id` (de assets URL), `user_id` (de JWT payload), `root_id` (interceptando POST response al crear pagina temporal).
|
|
62
|
-
|
|
63
|
-
**Formato de contenido**: TipTap JSON con prefijo `[v2]`. Ejemplo:
|
|
64
|
-
```json
|
|
65
|
-
"[v2][{\"type\":\"heading\",\"attrs\":{\"level\":2},\"content\":[{\"type\":\"text\",\"text\":\"Titulo\"}]},{\"type\":\"paragraph\",\"content\":[{\"type\":\"text\",\"text\":\"Contenido con \"},{\"type\":\"text\",\"text\":\"bold\",\"marks\":[{\"type\":\"bold\"}]}]}]"
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Build y publish
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
npm run build # Compila TypeScript
|
|
72
|
-
npm version patch # Bump version
|
|
73
|
-
npm publish --access public # Publica en npm
|
|
74
|
-
git push origin main # Push a GitHub
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**npm account**: `unikprompt` (token en `~/.npmrc`)
|
|
78
|
-
|
|
79
|
-
## Env vars
|
|
80
|
-
|
|
81
|
-
| Variable | Default | Uso |
|
|
82
|
-
|----------|---------|-----|
|
|
83
|
-
| `SKOOL_EMAIL` | - | Email default para login |
|
|
84
|
-
| `SKOOL_PASSWORD` | - | Password default |
|
|
85
|
-
| `SKOOL_GROUP` | - | Group slug default |
|
|
86
|
-
| `SKOOL_CLI_HEADLESS` | `true` | `false` para ver el browser |
|
|
87
|
-
| `SKOOL_CLI_DATA_DIR` | `~/.skool-cli/` | Donde se guardan cookies |
|
|
88
|
-
| `SKOOL_CLI_TIMEOUT` | `30000` | Timeout en ms |
|
|
89
|
-
|
|
90
|
-
## Decisiones tecnicas
|
|
91
|
-
|
|
92
|
-
- **API directa sobre UI automation**: Intentamos 10+ veces automatizar el editor TipTap via Playwright (pencil icon click, setContent, dirty trigger). React synthetic events no responden a dispatchEvent ni a clicks de Playwright en SVGs. La solucion fue descubrir la API interna via interceptacion de red.
|
|
93
|
-
- **Playwright solo para auth**: Login requiere browser real (CAPTCHA, WAF). Despues de auth, todas las operaciones son HTTP puro.
|
|
94
|
-
- **TipTap JSON, no HTML**: Skool almacena contenido como `[v2]` + TipTap JSON, no HTML. El `htmlToSkoolDesc()` en skool-api.ts convierte HTML a este formato.
|
|
95
|
-
- **Session en disco**: `~/.skool-cli/auth-state.json` persiste cookies entre invocaciones del CLI.
|
|
96
|
-
|
|
97
|
-
## Pendiente
|
|
98
|
-
|
|
99
|
-
- Fix `--folder` por nombre (endpoint GET para listar items no descubierto, fallback via __NEXT_DATA__)
|
|
100
|
-
- Limpiar dist/ de archivos debug viejos (debug.js, test-api.js siguen en dist/ de v1.0.1)
|
|
101
|
-
- Mejorar parser de inline marks (espacios entre bold/italic y texto adyacente)
|
|
102
|
-
- Soporte para editar lecciones existentes (PUT con ID conocido)
|
|
103
|
-
- Soporte para mover lecciones entre folders
|
|
104
|
-
- Tests automatizados
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-api.d.ts","sourceRoot":"","sources":["../../src/commands/debug-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,eAAe,SAsHxB,CAAC"}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { BrowserManager } from "../core/browser-manager.js";
|
|
3
|
-
import { PageOps } from "../core/page-ops.js";
|
|
4
|
-
import { writeFileSync } from "node:fs";
|
|
5
|
-
export const debugApiCommand = new Command("debug-api")
|
|
6
|
-
.description("Intercept Skool API calls during classroom operations")
|
|
7
|
-
.requiredOption("-g, --group <slug>", "Skool group slug", process.env.SKOOL_GROUP)
|
|
8
|
-
.option("--course <name>", "Course name", "Fundamentos de IA y Claude")
|
|
9
|
-
.action(async (opts) => {
|
|
10
|
-
const browser = new BrowserManager();
|
|
11
|
-
const ops = new PageOps(browser);
|
|
12
|
-
const apiLog = [];
|
|
13
|
-
try {
|
|
14
|
-
const page = await browser.getPage();
|
|
15
|
-
// Intercept ALL requests and responses
|
|
16
|
-
page.on("request", (req) => {
|
|
17
|
-
const url = req.url();
|
|
18
|
-
const method = req.method();
|
|
19
|
-
// Only log non-static requests (API calls, not images/css/js)
|
|
20
|
-
if (url.includes("/api/") ||
|
|
21
|
-
url.includes("graphql") ||
|
|
22
|
-
(method !== "GET" && !url.includes(".js") && !url.includes(".css") && !url.includes(".png") && !url.includes(".woff"))) {
|
|
23
|
-
const postData = req.postData();
|
|
24
|
-
const line = `>>> ${method} ${url}${postData ? `\n BODY: ${postData.slice(0, 500)}` : ""}`;
|
|
25
|
-
apiLog.push(line);
|
|
26
|
-
console.log(line);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
page.on("response", async (res) => {
|
|
30
|
-
const url = res.url();
|
|
31
|
-
const status = res.status();
|
|
32
|
-
if (url.includes("/api/") ||
|
|
33
|
-
url.includes("graphql") ||
|
|
34
|
-
(status >= 200 && status < 400 && !url.includes(".js") && !url.includes(".css") && !url.includes(".png") && !url.includes(".woff") && res.request().method() !== "GET")) {
|
|
35
|
-
let body = "";
|
|
36
|
-
try {
|
|
37
|
-
body = await res.text();
|
|
38
|
-
if (body.length > 1000)
|
|
39
|
-
body = body.slice(0, 1000) + "...";
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
body = "(could not read body)";
|
|
43
|
-
}
|
|
44
|
-
const line = `<<< ${status} ${url}\n RESPONSE: ${body}`;
|
|
45
|
-
apiLog.push(line);
|
|
46
|
-
console.log(line);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
// Also capture ALL fetch/XHR by intercepting at the page level
|
|
50
|
-
await page.addInitScript(() => {
|
|
51
|
-
const origFetch = window.fetch;
|
|
52
|
-
window.fetch = async (...args) => {
|
|
53
|
-
const url = typeof args[0] === "string" ? args[0] : args[0].url;
|
|
54
|
-
const opts = args[1];
|
|
55
|
-
console.log(`[FETCH] ${opts?.method || "GET"} ${url}`);
|
|
56
|
-
if (opts?.body) {
|
|
57
|
-
console.log(`[FETCH BODY] ${String(opts.body).slice(0, 500)}`);
|
|
58
|
-
}
|
|
59
|
-
return origFetch.apply(window, args);
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
console.log("\n=== Step 1: Navigate to classroom ===\n");
|
|
63
|
-
await ops.gotoClassroom(opts.group);
|
|
64
|
-
console.log("\n=== Step 2: Enter course ===\n");
|
|
65
|
-
await page.getByText(opts.course, { exact: false }).first().click();
|
|
66
|
-
await page.waitForTimeout(3000);
|
|
67
|
-
console.log("\n=== Step 3: Click '...' menu ===\n");
|
|
68
|
-
const courseTopArea = page.locator('div[class*="CourseMenuTop"]').first();
|
|
69
|
-
await courseTopArea.hover();
|
|
70
|
-
await page.waitForTimeout(500);
|
|
71
|
-
// Click "..." via JS
|
|
72
|
-
await page.evaluate(() => {
|
|
73
|
-
const topArea = document.querySelector('div[class*="CourseMenuTop"]');
|
|
74
|
-
if (!topArea)
|
|
75
|
-
return;
|
|
76
|
-
topArea.querySelectorAll("div, button, span, svg").forEach((el) => {
|
|
77
|
-
const rect = el.getBoundingClientRect();
|
|
78
|
-
const text = el.textContent?.trim() || "";
|
|
79
|
-
if (rect.width > 10 && rect.width < 50 && rect.height > 10 && rect.height < 50 && text.length < 4) {
|
|
80
|
-
el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
await page.waitForTimeout(800);
|
|
85
|
-
console.log("\n=== Step 4: Click 'Add page' ===\n");
|
|
86
|
-
try {
|
|
87
|
-
await page.getByText("Add page", { exact: true }).click({ timeout: 5000 });
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
await page.getByText("Add page", { exact: true }).click({ force: true, timeout: 5000 });
|
|
91
|
-
}
|
|
92
|
-
await page.waitForTimeout(5000);
|
|
93
|
-
console.log("\n=== Step 5: Capture current URL and page state ===\n");
|
|
94
|
-
console.log(`Current URL: ${page.url()}`);
|
|
95
|
-
// Also capture all cookies for reference
|
|
96
|
-
const context = page.context();
|
|
97
|
-
const cookies = await context.cookies();
|
|
98
|
-
const skoolCookies = cookies
|
|
99
|
-
.filter((c) => c.domain.includes("skool"))
|
|
100
|
-
.map((c) => `${c.name}=${c.value.slice(0, 20)}...`)
|
|
101
|
-
.join("; ");
|
|
102
|
-
console.log(`Skool cookies: ${skoolCookies}`);
|
|
103
|
-
// Save log
|
|
104
|
-
const logPath = "/tmp/skool-api-log.txt";
|
|
105
|
-
writeFileSync(logPath, apiLog.join("\n\n"));
|
|
106
|
-
console.log(`\nAPI log saved to: ${logPath} (${apiLog.length} entries)`);
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
await browser.close();
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
//# sourceMappingURL=debug-api.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-api.js","sourceRoot":"","sources":["../../src/commands/debug-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-manual.d.ts","sourceRoot":"","sources":["../../src/commands/debug-manual.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,kBAAkB,SA8D3B,CAAC"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { BrowserManager } from "../core/browser-manager.js";
|
|
3
|
-
import { PageOps } from "../core/page-ops.js";
|
|
4
|
-
import { writeFileSync } from "node:fs";
|
|
5
|
-
import { createInterface } from "node:readline";
|
|
6
|
-
export const debugManualCommand = new Command("debug-manual")
|
|
7
|
-
.description("Open browser with API interception — waits for you to interact manually")
|
|
8
|
-
.requiredOption("-g, --group <slug>", "Skool group slug", process.env.SKOOL_GROUP)
|
|
9
|
-
.action(async (opts) => {
|
|
10
|
-
const browser = new BrowserManager();
|
|
11
|
-
const ops = new PageOps(browser);
|
|
12
|
-
const apiLog = [];
|
|
13
|
-
try {
|
|
14
|
-
const page = await browser.getPage();
|
|
15
|
-
// Intercept API calls
|
|
16
|
-
page.on("request", (req) => {
|
|
17
|
-
const url = req.url();
|
|
18
|
-
const method = req.method();
|
|
19
|
-
if (url.includes("api2.skool.com")) {
|
|
20
|
-
const body = req.postData();
|
|
21
|
-
const line = `>>> ${method} ${url}\n BODY: ${body || "(none)"}`;
|
|
22
|
-
apiLog.push(line);
|
|
23
|
-
console.log(line);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
page.on("response", async (res) => {
|
|
27
|
-
const url = res.url();
|
|
28
|
-
if (url.includes("api2.skool.com")) {
|
|
29
|
-
let body = "";
|
|
30
|
-
try {
|
|
31
|
-
body = await res.text();
|
|
32
|
-
if (body.length > 2000)
|
|
33
|
-
body = body.slice(0, 2000) + "...";
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
body = "(unreadable)";
|
|
37
|
-
}
|
|
38
|
-
const line = `<<< ${res.status()} ${url}\n RESPONSE: ${body}`;
|
|
39
|
-
apiLog.push(line);
|
|
40
|
-
console.log(line);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
// Navigate to classroom
|
|
44
|
-
console.log("Opening classroom...\n");
|
|
45
|
-
await ops.gotoClassroom(opts.group);
|
|
46
|
-
// Wait for user to interact
|
|
47
|
-
console.log("=======================================================");
|
|
48
|
-
console.log("Browser is open with API interception active.");
|
|
49
|
-
console.log("Go to a lesson, click the pencil, edit, and click SAVE.");
|
|
50
|
-
console.log("All api2.skool.com calls will be logged here.");
|
|
51
|
-
console.log("Press ENTER when done to close the browser.");
|
|
52
|
-
console.log("=======================================================\n");
|
|
53
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
-
await new Promise((resolve) => {
|
|
55
|
-
rl.question("", () => { rl.close(); resolve(); });
|
|
56
|
-
});
|
|
57
|
-
// Save log
|
|
58
|
-
const logPath = "/tmp/skool-api-manual-log.txt";
|
|
59
|
-
writeFileSync(logPath, apiLog.join("\n\n"));
|
|
60
|
-
console.log(`\nAPI log saved to: ${logPath} (${apiLog.length} entries)`);
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
await browser.close();
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
//# sourceMappingURL=debug-manual.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-manual.js","sourceRoot":"","sources":["../../src/commands/debug-manual.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/debug.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/commands/debug.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,YAAY,SA0GrB,CAAC"}
|
package/dist/commands/debug.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { BrowserManager } from "../core/browser-manager.js";
|
|
3
|
-
import { PageOps } from "../core/page-ops.js";
|
|
4
|
-
import { writeFileSync } from "node:fs";
|
|
5
|
-
export const debugCommand = new Command("debug")
|
|
6
|
-
.description("Debug: take screenshot and dump page structure")
|
|
7
|
-
.requiredOption("-g, --group <slug>", "Skool group slug", process.env.SKOOL_GROUP)
|
|
8
|
-
.option("-p, --page <type>", "Page to debug: community, classroom, members", "community")
|
|
9
|
-
.action(async (opts) => {
|
|
10
|
-
const browser = new BrowserManager();
|
|
11
|
-
const ops = new PageOps(browser);
|
|
12
|
-
try {
|
|
13
|
-
const pageType = opts.page;
|
|
14
|
-
if (pageType === "classroom") {
|
|
15
|
-
await ops.gotoClassroom(opts.group);
|
|
16
|
-
}
|
|
17
|
-
else if (pageType === "classroom-course") {
|
|
18
|
-
await ops.gotoClassroom(opts.group);
|
|
19
|
-
const p = await browser.getPage();
|
|
20
|
-
// Click first course card
|
|
21
|
-
const courseCard = p.getByText("Fundamentos de IA y Claude", { exact: false });
|
|
22
|
-
await courseCard.first().click();
|
|
23
|
-
await p.waitForTimeout(3000);
|
|
24
|
-
}
|
|
25
|
-
else if (pageType === "classroom-module") {
|
|
26
|
-
await ops.gotoClassroom(opts.group);
|
|
27
|
-
const p = await browser.getPage();
|
|
28
|
-
const courseCard = p.getByText("Fundamentos de IA y Claude", { exact: false });
|
|
29
|
-
await courseCard.first().click();
|
|
30
|
-
await p.waitForTimeout(3000);
|
|
31
|
-
// Click the module to expand it
|
|
32
|
-
const mod = p.locator('div[class*="MenuItemTitle"]').filter({ hasText: "Fundamentos de IA" }).first();
|
|
33
|
-
await mod.click();
|
|
34
|
-
await p.waitForTimeout(2000);
|
|
35
|
-
// Screenshot after expanding
|
|
36
|
-
await p.screenshot({ path: "/tmp/skool-debug-module-expanded.png", fullPage: false });
|
|
37
|
-
console.log("Module expanded screenshot: /tmp/skool-debug-module-expanded.png");
|
|
38
|
-
}
|
|
39
|
-
else if (pageType === "classroom-hover") {
|
|
40
|
-
await ops.gotoClassroom(opts.group);
|
|
41
|
-
const p = await browser.getPage();
|
|
42
|
-
const courseCard = p.getByText("Fundamentos de IA y Claude", { exact: false });
|
|
43
|
-
await courseCard.first().click();
|
|
44
|
-
await p.waitForTimeout(3000);
|
|
45
|
-
// Hover over "Fundamentos de IA" module
|
|
46
|
-
const mod = p.locator('div[class*="MenuItemWrapper"]').filter({ hasText: "Fundamentos de IA" }).first();
|
|
47
|
-
await mod.hover();
|
|
48
|
-
await p.waitForTimeout(1000);
|
|
49
|
-
// Take screenshot after hover
|
|
50
|
-
await p.screenshot({ path: "/tmp/skool-debug-hover.png", fullPage: false });
|
|
51
|
-
console.log("Hover screenshot: /tmp/skool-debug-hover.png");
|
|
52
|
-
// Now right-click the module
|
|
53
|
-
await mod.click({ button: "right" });
|
|
54
|
-
await p.waitForTimeout(1000);
|
|
55
|
-
await p.screenshot({ path: "/tmp/skool-debug-rightclick.png", fullPage: false });
|
|
56
|
-
console.log("Right-click screenshot: /tmp/skool-debug-rightclick.png");
|
|
57
|
-
}
|
|
58
|
-
else if (pageType === "members") {
|
|
59
|
-
await ops.gotoMembers(opts.group);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
await ops.gotoCommunity(opts.group);
|
|
63
|
-
}
|
|
64
|
-
const page = await browser.getPage();
|
|
65
|
-
// If community, also click "Write something" to open post form
|
|
66
|
-
if (pageType === "post-form") {
|
|
67
|
-
await ops.gotoCommunity(opts.group);
|
|
68
|
-
const writeTrigger = page.locator('div[class*="CreatePost"], [placeholder*="Write"], button:has-text("Write")');
|
|
69
|
-
await writeTrigger.first().click({ timeout: 10000 }).catch(() => {
|
|
70
|
-
// Try alternative: text-based
|
|
71
|
-
return page.getByText("Write something").click({ timeout: 5000 });
|
|
72
|
-
});
|
|
73
|
-
await page.waitForTimeout(2000);
|
|
74
|
-
}
|
|
75
|
-
// Screenshot
|
|
76
|
-
const ssPath = `/tmp/skool-debug-${pageType}.png`;
|
|
77
|
-
await page.screenshot({ path: ssPath, fullPage: false });
|
|
78
|
-
console.log(`Screenshot: ${ssPath}`);
|
|
79
|
-
// Dump relevant HTML structure (not full page, just key areas)
|
|
80
|
-
const structure = await page.evaluate(() => {
|
|
81
|
-
const result = [];
|
|
82
|
-
// Get ALL interactive elements and text nodes in sidebar area
|
|
83
|
-
const allElements = document.querySelectorAll("button, a, input, textarea, [contenteditable], [role], div[class*='Module'], div[class*='Folder'], div[class*='Sidebar'], div[class*='sidebar'], div[class*='Nav'], div[class*='Course'], span[class*='title'], div[class*='menu'], div[class*='Menu']");
|
|
84
|
-
allElements.forEach((el) => {
|
|
85
|
-
const text = el.textContent?.trim().slice(0, 60);
|
|
86
|
-
const tag = el.tagName;
|
|
87
|
-
const cls = el.className?.toString().slice(0, 100) || "";
|
|
88
|
-
const role = el.getAttribute("role") || "";
|
|
89
|
-
const href = el.getAttribute("href") || "";
|
|
90
|
-
const aria = el.getAttribute("aria-label") || "";
|
|
91
|
-
if (text || role || aria) {
|
|
92
|
-
let line = `${tag}: "${text || ""}"`;
|
|
93
|
-
if (cls)
|
|
94
|
-
line += ` class="${cls}"`;
|
|
95
|
-
if (role)
|
|
96
|
-
line += ` role="${role}"`;
|
|
97
|
-
if (href)
|
|
98
|
-
line += ` href="${href}"`;
|
|
99
|
-
if (aria)
|
|
100
|
-
line += ` aria="${aria}"`;
|
|
101
|
-
result.push(line);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
return result;
|
|
105
|
-
});
|
|
106
|
-
const dumpPath = `/tmp/skool-debug-${pageType}.txt`;
|
|
107
|
-
writeFileSync(dumpPath, structure.join("\n"));
|
|
108
|
-
console.log(`Structure dump: ${dumpPath} (${structure.length} elements)`);
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
await browser.close();
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
//# sourceMappingURL=debug.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/commands/debug.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-api.d.ts","sourceRoot":"","sources":["../../src/commands/test-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,cAAc,SAqJvB,CAAC"}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { BrowserManager } from "../core/browser-manager.js";
|
|
3
|
-
import { SkoolApi } from "../core/skool-api.js";
|
|
4
|
-
import { PageOps } from "../core/page-ops.js";
|
|
5
|
-
export const testApiCommand = new Command("test-api")
|
|
6
|
-
.description("Test direct API creation of a lesson")
|
|
7
|
-
.requiredOption("-g, --group <slug>", "Skool group slug", process.env.SKOOL_GROUP)
|
|
8
|
-
.option("--course <name>", "Course name", "Fundamentos de IA y Claude")
|
|
9
|
-
.action(async (opts) => {
|
|
10
|
-
const browser = new BrowserManager();
|
|
11
|
-
const api = new SkoolApi(browser);
|
|
12
|
-
const ops = new PageOps(browser);
|
|
13
|
-
try {
|
|
14
|
-
const page = await browser.getPage();
|
|
15
|
-
// Set up request interception BEFORE navigating
|
|
16
|
-
let groupId = "";
|
|
17
|
-
let userId = "";
|
|
18
|
-
let rootId = "";
|
|
19
|
-
// Intercept API calls to capture IDs
|
|
20
|
-
page.on("request", (req) => {
|
|
21
|
-
const body = req.postData();
|
|
22
|
-
if (!body)
|
|
23
|
-
return;
|
|
24
|
-
// Capture group_id from telemetry
|
|
25
|
-
const gMatch = body.match(/"group_id"\s*:\s*"([a-f0-9]{32})"/);
|
|
26
|
-
if (gMatch && !groupId)
|
|
27
|
-
groupId = gMatch[1];
|
|
28
|
-
const uMatch = body.match(/"member_id"\s*:\s*"([a-f0-9]{32})"/);
|
|
29
|
-
if (uMatch && !userId)
|
|
30
|
-
userId = uMatch[1];
|
|
31
|
-
});
|
|
32
|
-
// Also capture API calls to api2.skool.com for root_id
|
|
33
|
-
page.on("request", (req) => {
|
|
34
|
-
const url = req.url();
|
|
35
|
-
const body = req.postData();
|
|
36
|
-
if (url.includes("api2.skool.com") && body) {
|
|
37
|
-
const rMatch = body.match(/"root_id"\s*:\s*"([a-f0-9]{32})"/);
|
|
38
|
-
if (rMatch && !rootId)
|
|
39
|
-
rootId = rMatch[1];
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
console.log("Step 1: Navigate to classroom...");
|
|
43
|
-
await ops.gotoClassroom(opts.group);
|
|
44
|
-
console.log("Step 2: Enter course...");
|
|
45
|
-
await page.getByText(opts.course, { exact: false }).first().click();
|
|
46
|
-
await page.waitForTimeout(3000);
|
|
47
|
-
// Extract group_id from the page URL/assets (the group ID is in the OG image URL)
|
|
48
|
-
if (!groupId) {
|
|
49
|
-
const metaContent = await page.evaluate(() => {
|
|
50
|
-
const str = document.documentElement.innerHTML;
|
|
51
|
-
const match = str.match(/01fe[a-f0-9]{28}/);
|
|
52
|
-
return match ? match[0] : "";
|
|
53
|
-
});
|
|
54
|
-
if (metaContent)
|
|
55
|
-
groupId = metaContent;
|
|
56
|
-
}
|
|
57
|
-
console.log(`\nAfter navigation - groupId: ${groupId}, userId: ${userId}, rootId: ${rootId}`);
|
|
58
|
-
// If we don't have rootId yet, we need to trigger an API call
|
|
59
|
-
// Do the "Add page" action to capture the root_id from the POST to api2.skool.com
|
|
60
|
-
if (!rootId) {
|
|
61
|
-
console.log("\nStep 3: Triggering Add page to discover root_id...");
|
|
62
|
-
// Set up listener for the api2.skool.com/courses POST
|
|
63
|
-
const rootIdPromise = new Promise((resolve) => {
|
|
64
|
-
const timeout = setTimeout(() => resolve(""), 15000);
|
|
65
|
-
page.on("response", async (res) => {
|
|
66
|
-
if (res.url().includes("api2.skool.com/courses") && res.request().method() === "POST") {
|
|
67
|
-
try {
|
|
68
|
-
const data = await res.json();
|
|
69
|
-
const rid = data.root_id || "";
|
|
70
|
-
if (rid) {
|
|
71
|
-
clearTimeout(timeout);
|
|
72
|
-
resolve(rid);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch { /* ignore */ }
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
// Click "..." and "Add page"
|
|
80
|
-
const courseTopArea = page.locator('div[class*="CourseMenuTop"]').first();
|
|
81
|
-
await courseTopArea.hover();
|
|
82
|
-
await page.waitForTimeout(500);
|
|
83
|
-
await page.evaluate(() => {
|
|
84
|
-
const topArea = document.querySelector('div[class*="CourseMenuTop"]');
|
|
85
|
-
if (!topArea)
|
|
86
|
-
return;
|
|
87
|
-
topArea.querySelectorAll("div, button, span, svg").forEach((el) => {
|
|
88
|
-
const rect = el.getBoundingClientRect();
|
|
89
|
-
const text = el.textContent?.trim() || "";
|
|
90
|
-
if (rect.width > 10 && rect.width < 50 && rect.height > 10 && rect.height < 50 && text.length < 4) {
|
|
91
|
-
el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
await page.waitForTimeout(800);
|
|
96
|
-
try {
|
|
97
|
-
await page.getByText("Add page", { exact: true }).click({ timeout: 5000 });
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
await page.getByText("Add page", { exact: true }).click({ force: true, timeout: 5000 });
|
|
101
|
-
}
|
|
102
|
-
rootId = await rootIdPromise;
|
|
103
|
-
console.log(`Captured root_id: ${rootId}`);
|
|
104
|
-
// Also get user_id from the create request
|
|
105
|
-
// We already have it from telemetry
|
|
106
|
-
}
|
|
107
|
-
// ALWAYS extract userId from the auth_token JWT (member_id from telemetry is NOT the same)
|
|
108
|
-
const cookies = await page.context().cookies();
|
|
109
|
-
const authCookie = cookies.find(c => c.name === "auth_token");
|
|
110
|
-
if (authCookie) {
|
|
111
|
-
try {
|
|
112
|
-
const payload = JSON.parse(Buffer.from(authCookie.value.split(".")[1], "base64").toString());
|
|
113
|
-
userId = payload.sub || payload.user_id || payload.id || "";
|
|
114
|
-
console.log(`userId from JWT: ${userId}`);
|
|
115
|
-
console.log(`JWT payload keys: ${Object.keys(payload).join(", ")}`);
|
|
116
|
-
}
|
|
117
|
-
catch { /* ignore */ }
|
|
118
|
-
}
|
|
119
|
-
console.log(`\nDiscovered IDs:`);
|
|
120
|
-
console.log(` group_id: ${groupId}`);
|
|
121
|
-
console.log(` user_id: ${userId}`);
|
|
122
|
-
console.log(` root_id: ${rootId}`);
|
|
123
|
-
if (!groupId || !userId || !rootId) {
|
|
124
|
-
console.error("\nCould not discover all required IDs.");
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
// Now test: create a page via direct API with the correct root_id
|
|
128
|
-
console.log("\nStep 4: Creating test page via API...");
|
|
129
|
-
const result = await api.createPage({
|
|
130
|
-
groupId,
|
|
131
|
-
userId,
|
|
132
|
-
parentId: rootId, // Top-level page in the course
|
|
133
|
-
rootId,
|
|
134
|
-
title: "API Test Page",
|
|
135
|
-
content: "<h2>Hola desde la API</h2><p>Esta pagina fue creada directamente via api2.skool.com</p><ul><li>Punto 1</li><li>Punto 2</li></ul>",
|
|
136
|
-
});
|
|
137
|
-
console.log(`\nResult: ${JSON.stringify(result, null, 2)}`);
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
await browser.close();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
//# sourceMappingURL=test-api.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-api.js","sourceRoot":"","sources":["../../src/commands/test-api.ts"],"names":[],"mappings":"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"}
|
package/planning.md
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
# skool-cli - Planning Document
|
|
2
|
-
|
|
3
|
-
## Vision Original (2026-04-01)
|
|
4
|
-
|
|
5
|
-
Construir la primera herramienta de automatizacion para Skool.com publicable en npm. Skool no tiene API publica, lo que representa una oportunidad: ser el unico CLI/MCP funcional para Skool en todo el ecosistema.
|
|
6
|
-
|
|
7
|
-
### Modelo de negocio: Picos y Palas
|
|
8
|
-
|
|
9
|
-
La herramienta gratuita en npm/GitHub alimenta el funnel de UnikPrompt:
|
|
10
|
-
```
|
|
11
|
-
GitHub/npm (descubrimiento gratuito)
|
|
12
|
-
→ TikTok/Instagram (demos en accion)
|
|
13
|
-
→ Facebook Group "Claude Code en Espanol" (soporte gratuito)
|
|
14
|
-
→ Skool "Operadores Aumentados" (comunidad premium)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### Comparacion de enfoques
|
|
18
|
-
|
|
19
|
-
Se evaluaron 3 opciones:
|
|
20
|
-
|
|
21
|
-
| Criterio | MCP Server solo | CLI solo | CLI + MCP (elegido) |
|
|
22
|
-
|----------|----------------|----------|---------------------|
|
|
23
|
-
| Audiencia | Solo MCP clients | Todos con terminal | Todos |
|
|
24
|
-
| Scriptable | No | Si | Si |
|
|
25
|
-
| AI integration | Nativa (tools) | Via bash | Ambos |
|
|
26
|
-
| Paquetes npm | 1 | 1 | 2 |
|
|
27
|
-
|
|
28
|
-
**Decision: CLI + MCP + Skill** (maximo alcance, una sola base de codigo)
|
|
29
|
-
|
|
30
|
-
Inspirado en opencli (github.com/jackwener/opencli, 10.6K stars) como modelo de CLI universal.
|
|
31
|
-
|
|
32
|
-
## API Interna Descubierta
|
|
33
|
-
|
|
34
|
-
Skool usa `api2.skool.com` como backend. Descubierto via interceptacion de red con Playwright `page.on('request')` y `page.on('response')`.
|
|
35
|
-
|
|
36
|
-
### Endpoints
|
|
37
|
-
|
|
38
|
-
| Endpoint | Metodo | Uso |
|
|
39
|
-
|----------|--------|-----|
|
|
40
|
-
| `/courses` | POST | Crear pagina (`unit_type: "module"`) o folder (`unit_type: "set"`) |
|
|
41
|
-
| `/courses/{id}` | PUT | Actualizar titulo + contenido |
|
|
42
|
-
| `/courses/{id}` | DELETE | Eliminar pagina o folder |
|
|
43
|
-
|
|
44
|
-
### Formato de contenido
|
|
45
|
-
|
|
46
|
-
Skool almacena contenido como TipTap JSON con prefijo `[v2]`:
|
|
47
|
-
```json
|
|
48
|
-
{
|
|
49
|
-
"title": "Titulo",
|
|
50
|
-
"desc": "[v2][{\"type\":\"heading\",\"attrs\":{\"level\":2},\"content\":[{\"type\":\"text\",\"text\":\"Mi Heading\"}]},{\"type\":\"paragraph\",\"content\":[{\"type\":\"text\",\"text\":\"Texto normal \"},{\"type\":\"text\",\"text\":\"bold\",\"marks\":[{\"type\":\"bold\"}]}]}]"
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Autenticacion
|
|
55
|
-
|
|
56
|
-
- Login via Playwright (browser headless)
|
|
57
|
-
- Cookies persistidas en `~/.skool-cli/auth-state.json`
|
|
58
|
-
- `user_id` extraido del JWT `auth_token` cookie (campo `user_id` del payload)
|
|
59
|
-
- `group_id` extraido de assets URL (patron `assets.skool.com/f/{group_id}`)
|
|
60
|
-
- `root_id` descubierto interceptando el POST response al crear pagina temporal
|
|
61
|
-
|
|
62
|
-
## Decisiones Tecnicas
|
|
63
|
-
|
|
64
|
-
### Por que API directa sobre Playwright UI automation
|
|
65
|
-
|
|
66
|
-
Se intento 10+ veces automatizar el editor TipTap de Skool via Playwright:
|
|
67
|
-
1. `dispatchEvent(new MouseEvent(...))` no activa React synthetic events
|
|
68
|
-
2. Click real de Playwright en SVGs (pencil icon) es inconsistente
|
|
69
|
-
3. Incluso detectando "edit mode" (SAVE button), titulo y contenido no se llenaban
|
|
70
|
-
4. 6 workarounds de produccion necesarios (dirty trigger, overlay fix, ref volatility, SPA timing, HTML conversion, title limit)
|
|
71
|
-
|
|
72
|
-
La API directa elimina toda esa complejidad: un POST crea la pagina, un PUT le pone contenido.
|
|
73
|
-
|
|
74
|
-
### Stack tecnologico
|
|
75
|
-
|
|
76
|
-
- TypeScript + Commander.js (CLI)
|
|
77
|
-
- Playwright (solo para login, una vez)
|
|
78
|
-
- MCP SDK + Zod (MCP server)
|
|
79
|
-
- Node.js >= 18
|
|
80
|
-
|
|
81
|
-
## Cronologia
|
|
82
|
-
|
|
83
|
-
| Fecha | Hito |
|
|
84
|
-
|-------|------|
|
|
85
|
-
| 2026-04-01 | Inicio: evaluacion MCP vs CLI vs opencli |
|
|
86
|
-
| 2026-04-01 | Scaffolding del proyecto, setup TypeScript + Commander |
|
|
87
|
-
| 2026-04-01 | Login funcional, get-posts, get-categories, get-members |
|
|
88
|
-
| 2026-04-01 | create-post funcional con categoria |
|
|
89
|
-
| 2026-04-01/02 | 10+ intentos de UI automation para create-lesson (fallido) |
|
|
90
|
-
| 2026-04-02 | Pivot a API-first: interceptacion de red |
|
|
91
|
-
| 2026-04-03 | Descubrimiento de api2.skool.com/courses |
|
|
92
|
-
| 2026-04-03 | create-lesson funcional via API (titulo + contenido) |
|
|
93
|
-
| 2026-04-03 | Descubrimiento de folders (unit_type "set") |
|
|
94
|
-
| 2026-04-03 | create-folder + create-lesson --folder-id funcional |
|
|
95
|
-
| 2026-04-04 | Cleanup, delete-lesson, list-lessons |
|
|
96
|
-
| 2026-04-04 | npm publish v1.0.0 y v1.0.1 (npmjs.com/package/skool-cli) |
|
|
97
|
-
| 2026-04-04 | MCP server wrapper (10 tools) |
|
|
98
|
-
| 2026-04-04 | Claude Code Skill (skill/SKILL.md) |
|
|
99
|
-
| 2026-04-04 | GitHub repo (github.com/unikprompt/skool-cli) |
|
|
100
|
-
| 2026-04-04 | Integracion con Content-Pipeline (reemplazo de Playwright) |
|
|
101
|
-
|
|
102
|
-
## Estado Actual (v1.0.1)
|
|
103
|
-
|
|
104
|
-
### 10 Comandos
|
|
105
|
-
|
|
106
|
-
| Comando | Estado |
|
|
107
|
-
|---------|--------|
|
|
108
|
-
| `login` | OK |
|
|
109
|
-
| `whoami` | OK |
|
|
110
|
-
| `create-lesson` | OK (con --folder-id, --course, --file, --markdown) |
|
|
111
|
-
| `create-folder` | OK |
|
|
112
|
-
| `delete-lesson` | OK |
|
|
113
|
-
| `list-lessons` | OK (via sidebar DOM) |
|
|
114
|
-
| `create-post` | OK (con --category) |
|
|
115
|
-
| `get-posts` | OK |
|
|
116
|
-
| `get-categories` | OK |
|
|
117
|
-
| `get-members` | OK |
|
|
118
|
-
|
|
119
|
-
### 3 Canales de distribucion
|
|
120
|
-
|
|
121
|
-
| Canal | URL |
|
|
122
|
-
|-------|-----|
|
|
123
|
-
| npm | npmjs.com/package/skool-cli |
|
|
124
|
-
| GitHub | github.com/unikprompt/skool-cli |
|
|
125
|
-
| Skill | skill/SKILL.md (para Claude Code) |
|
|
126
|
-
|
|
127
|
-
## Pendiente
|
|
128
|
-
|
|
129
|
-
- Fix `--folder` por nombre (endpoint GET no descubierto, fallback via __NEXT_DATA__)
|
|
130
|
-
- Limpiar dist/ de archivos debug viejos del build anterior
|
|
131
|
-
- Mejorar parser de inline marks (espacios entre bold/italic y texto adyacente)
|
|
132
|
-
- Soporte para editar lecciones existentes (PUT con ID conocido)
|
|
133
|
-
- Soporte para mover lecciones entre folders
|
|
134
|
-
- Tests automatizados
|
|
135
|
-
- Agregar a landing page unikprompt.com
|