libroadcast-cli 1.11.1 → 2.0.1
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 +15 -3
- package/dist/cmd/login.js +94 -0
- package/dist/index.js +5 -1
- package/dist/utils/commandHandler.js +39 -22
- package/dist/utils/credentials.js +64 -0
- package/dist/utils/help.js +18 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,10 +7,9 @@ npm install -g libroadcast-cli
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
export LICHESS_DOMAIN=http://localhost:8080/ # optional
|
|
10
|
+
libroadcast login
|
|
12
11
|
|
|
13
|
-
libroadcast
|
|
12
|
+
libroadcast <command> [options]
|
|
14
13
|
```
|
|
15
14
|
|
|
16
15
|
```bash
|
|
@@ -18,6 +17,12 @@ Usage: <command> [options]
|
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
Commands:
|
|
20
|
+
login [--logout]
|
|
21
|
+
Save your Lichess token and domain for future use.
|
|
22
|
+
This allows you to use the CLI without setting environment variables.
|
|
23
|
+
Options:
|
|
24
|
+
--logout (-lo) Clear saved credentials and log out.
|
|
25
|
+
|
|
21
26
|
delay <broadcastId> <delayInSeconds> [--onlyDelay] [--noDelay] [--rounds <roundsToFix>]
|
|
22
27
|
Sets the delay for all rounds in the specified broadcast.
|
|
23
28
|
Note: The delay is specified in seconds. (max 3600 seconds = 1 hour)
|
|
@@ -67,6 +72,12 @@ Commands:
|
|
|
67
72
|
|
|
68
73
|
|
|
69
74
|
Examples:
|
|
75
|
+
# Login with your Lichess token (interactive)
|
|
76
|
+
$ login
|
|
77
|
+
# Login with token as argument
|
|
78
|
+
$ login lip_yourtoken https://lichess.org
|
|
79
|
+
# Logout and clear saved credentials
|
|
80
|
+
$ login --logout
|
|
70
81
|
# Set a 5-minute delay without changing start time
|
|
71
82
|
$ delay bcast123 300 --onlyDelay
|
|
72
83
|
# Set source PGN URL with round and slice filters
|
|
@@ -79,4 +90,5 @@ Examples:
|
|
|
79
90
|
$ fixSchedule bcast123 15m --rounds 1-4,8+
|
|
80
91
|
# Set startsAfterPrevious to true for all rounds in a broadcast
|
|
81
92
|
$ startsPrevious bcast123 true
|
|
93
|
+
|
|
82
94
|
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loginCommand = void 0;
|
|
7
|
+
const node_process_1 = require("node:process");
|
|
8
|
+
const node_readline_1 = require("node:readline");
|
|
9
|
+
const credentials_1 = require("../utils/credentials");
|
|
10
|
+
const colors_1 = __importDefault(require("../utils/colors"));
|
|
11
|
+
const loginCommand = async (args) => {
|
|
12
|
+
if (args.includes("--logout") || args.includes("-lo")) {
|
|
13
|
+
try {
|
|
14
|
+
(0, credentials_1.clearCredentials)();
|
|
15
|
+
console.log(colors_1.default.green("✓ Credentials cleared successfully. You are now logged out."));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error(colors_1.default.red("Error clearing credentials:"), error);
|
|
20
|
+
(0, node_process_1.exit)(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const positionalArgs = args.filter((arg) => !arg.startsWith("-"));
|
|
25
|
+
let token = positionalArgs[0];
|
|
26
|
+
let domain = positionalArgs[1] || "https://lichess.org";
|
|
27
|
+
if (!token) {
|
|
28
|
+
const readline = (0, node_readline_1.createInterface)({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
});
|
|
32
|
+
const question = (prompt) => {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
readline.question(prompt, (answer) => {
|
|
35
|
+
resolve(answer);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
console.log(colors_1.default.whiteBold("Lichess Token Login"));
|
|
40
|
+
console.log(colors_1.default.blue("Please enter your Lichess token \n" +
|
|
41
|
+
"You can generate one at https://lichess.org/account/oauth/token/create?scopes[]=study:write&scopes[]=study:read&scopes[]=web:mod&description=Broadcast+CLI"));
|
|
42
|
+
console.log("");
|
|
43
|
+
token = await question(colors_1.default.whiteBold("Lichess Token: "));
|
|
44
|
+
if (!token?.trim()) {
|
|
45
|
+
console.error(colors_1.default.red("Error: Token cannot be empty."));
|
|
46
|
+
readline.close();
|
|
47
|
+
(0, node_process_1.exit)(1);
|
|
48
|
+
}
|
|
49
|
+
if (!token.startsWith("lip_")) {
|
|
50
|
+
console.error(colors_1.default.red("Error: Invalid token format. Token must start with 'lip_'. Please check your token."));
|
|
51
|
+
readline.close();
|
|
52
|
+
(0, node_process_1.exit)(1);
|
|
53
|
+
}
|
|
54
|
+
domain =
|
|
55
|
+
(await question(colors_1.default.whiteBold("Lichess Domain (default: https://lichess.org): "))) || "https://lichess.org";
|
|
56
|
+
readline.close();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (!token.startsWith("lip_")) {
|
|
60
|
+
console.error(colors_1.default.red("Error: Invalid token format. Token must start with 'lip_'. Please check your token."));
|
|
61
|
+
(0, node_process_1.exit)(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
domain = domain.replace(/\/$/, "");
|
|
65
|
+
const skipValidation = args.includes("--skip-validation");
|
|
66
|
+
let scopes = [];
|
|
67
|
+
if (!skipValidation) {
|
|
68
|
+
console.log(colors_1.default.blue("Validating token and fetching scopes..."));
|
|
69
|
+
try {
|
|
70
|
+
scopes = await (0, credentials_1.fetchTokenScopes)(token, domain);
|
|
71
|
+
console.log(colors_1.default.green(`✓ Token valid with scopes: ${scopes.join(", ")}`));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(colors_1.default.red(`Error: Failed to validate token: ${error instanceof Error ? error.message : String(error)}`));
|
|
75
|
+
(0, node_process_1.exit)(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(colors_1.default.blue("Skipping token validation..."));
|
|
80
|
+
}
|
|
81
|
+
(0, credentials_1.saveCredentials)({
|
|
82
|
+
lichessToken: token,
|
|
83
|
+
lichessDomain: domain,
|
|
84
|
+
scopes: scopes,
|
|
85
|
+
});
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log(colors_1.default.green("✓ Credentials saved successfully! You can now use the CLI without setting environment variables."));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error(colors_1.default.red("Error during login:"), error);
|
|
91
|
+
(0, node_process_1.exit)(1);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
exports.loginCommand = loginCommand;
|
package/dist/index.js
CHANGED
|
@@ -30,8 +30,12 @@ const colors_1 = __importDefault(require("./utils/colors"));
|
|
|
30
30
|
console.error(`${colors_1.default.red("Error:")} Command handler not found.`);
|
|
31
31
|
(0, node_process_1.exit)(1);
|
|
32
32
|
}
|
|
33
|
+
if (cmd === commandHandler_1.Command.Login) {
|
|
34
|
+
await handler(commandHandler_1.args);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
33
37
|
if (!commandHandler_1.LICHESS_TOKEN?.trim() || !commandHandler_1.LICHESS_TOKEN.startsWith("lip_")) {
|
|
34
|
-
console.error(`${colors_1.default.
|
|
38
|
+
console.error(`${colors_1.default.blue("Use the 'login' command to save your credentials: ")}${colors_1.default.whiteBold("libroadcast login")}`);
|
|
35
39
|
(0, node_process_1.exit)(1);
|
|
36
40
|
}
|
|
37
41
|
await handler(commandHandler_1.args);
|
|
@@ -14,11 +14,25 @@ const setLichessGames_1 = require("../cmd/setLichessGames");
|
|
|
14
14
|
const fixSchedule_1 = require("../cmd/fixSchedule");
|
|
15
15
|
const startsPrevious_1 = require("../cmd/startsPrevious");
|
|
16
16
|
const period_1 = require("../cmd/period");
|
|
17
|
-
|
|
18
|
-
const
|
|
17
|
+
const login_1 = require("../cmd/login");
|
|
18
|
+
const credentials_1 = require("./credentials");
|
|
19
|
+
const getToken = () => {
|
|
20
|
+
const stored = (0, credentials_1.getStoredCredentials)();
|
|
21
|
+
return stored?.lichessToken;
|
|
22
|
+
};
|
|
23
|
+
const getDomain = () => {
|
|
24
|
+
const stored = (0, credentials_1.getStoredCredentials)();
|
|
25
|
+
if (stored?.lichessDomain) {
|
|
26
|
+
return stored.lichessDomain.replace(/\/$/, "") + "/";
|
|
27
|
+
}
|
|
28
|
+
return "https://lichess.org/";
|
|
29
|
+
};
|
|
30
|
+
exports.LICHESS_TOKEN = getToken();
|
|
31
|
+
const LICHESS_DOMAIN = getDomain();
|
|
19
32
|
exports.args = node_process_1.argv.slice(2);
|
|
20
33
|
var Command;
|
|
21
34
|
(function (Command) {
|
|
35
|
+
Command["Login"] = "login";
|
|
22
36
|
Command["Delay"] = "delay";
|
|
23
37
|
Command["SetPGN"] = "setPGN";
|
|
24
38
|
Command["SetPGNMulti"] = "setPGNMulti";
|
|
@@ -28,6 +42,7 @@ var Command;
|
|
|
28
42
|
Command["Period"] = "period";
|
|
29
43
|
})(Command || (exports.Command = Command = {}));
|
|
30
44
|
exports.commands = new Map([
|
|
45
|
+
[Command.Login, login_1.loginCommand],
|
|
31
46
|
[Command.Delay, delay_1.delayCommand],
|
|
32
47
|
[Command.SetPGN, setPGN_1.setPGNCommand],
|
|
33
48
|
[Command.SetPGNMulti, setPGNMulti_1.setPGNMultiCommand],
|
|
@@ -101,25 +116,27 @@ const checkTokenScopes = async (modRequired) => {
|
|
|
101
116
|
const requiredScopes = ["study:read", "study:write"];
|
|
102
117
|
if (modRequired)
|
|
103
118
|
requiredScopes.push("web:mod");
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
const stored = (0, credentials_1.getStoredCredentials)();
|
|
120
|
+
let scopes = [];
|
|
121
|
+
if (stored?.scopes) {
|
|
122
|
+
scopes = stored.scopes;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const response = await exports.client.POST("/api/token/test", {
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "text/plain",
|
|
128
|
+
},
|
|
129
|
+
body: exports.LICHESS_TOKEN,
|
|
130
|
+
bodySerializer: (body) => body,
|
|
131
|
+
});
|
|
132
|
+
const data = await response.data;
|
|
133
|
+
const scopesStr = data?.[exports.LICHESS_TOKEN]?.scopes;
|
|
134
|
+
scopes = scopesStr ? scopesStr.split(",").map((s) => s.trim()) : [];
|
|
135
|
+
}
|
|
136
|
+
const missingScopes = requiredScopes.filter((scope) => !scopes.includes(scope));
|
|
137
|
+
if (missingScopes.length > 0) {
|
|
138
|
+
console.error(colors_1.default.red(`Error: Missing required token scopes: ${missingScopes.join(", ")}`));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
124
141
|
};
|
|
125
142
|
exports.checkTokenScopes = checkTokenScopes;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchTokenScopes = exports.clearCredentials = exports.saveCredentials = exports.getStoredCredentials = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const CONFIG_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".libroadcast");
|
|
8
|
+
const CREDENTIALS_FILE = (0, node_path_1.join)(CONFIG_DIR, "credentials.json");
|
|
9
|
+
const ensureConfigDir = () => {
|
|
10
|
+
if (!(0, node_fs_1.existsSync)(CONFIG_DIR)) {
|
|
11
|
+
(0, node_fs_1.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const getStoredCredentials = () => {
|
|
15
|
+
try {
|
|
16
|
+
if (!(0, node_fs_1.existsSync)(CREDENTIALS_FILE)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const content = (0, node_fs_1.readFileSync)(CREDENTIALS_FILE, "utf-8");
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.getStoredCredentials = getStoredCredentials;
|
|
27
|
+
const saveCredentials = (credentials) => {
|
|
28
|
+
ensureConfigDir();
|
|
29
|
+
(0, node_fs_1.writeFileSync)(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), "utf-8");
|
|
30
|
+
};
|
|
31
|
+
exports.saveCredentials = saveCredentials;
|
|
32
|
+
const clearCredentials = () => {
|
|
33
|
+
try {
|
|
34
|
+
if ((0, node_fs_1.existsSync)(CREDENTIALS_FILE)) {
|
|
35
|
+
(0, node_fs_1.unlinkSync)(CREDENTIALS_FILE);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
exports.clearCredentials = clearCredentials;
|
|
42
|
+
const fetchTokenScopes = async (token, domain) => {
|
|
43
|
+
try {
|
|
44
|
+
const normalizedDomain = domain.replace(/\/$/, "") + "/";
|
|
45
|
+
const url = `${normalizedDomain}api/token/test`;
|
|
46
|
+
const response = await fetch(url, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "text/plain",
|
|
50
|
+
},
|
|
51
|
+
body: token,
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`HTTP ${response.status}`);
|
|
55
|
+
}
|
|
56
|
+
const data = (await response.json());
|
|
57
|
+
const scopes = data[token]?.scopes;
|
|
58
|
+
return scopes ? scopes.split(",").map((s) => s.trim()) : [];
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw new Error(`Failed to fetch token scopes: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
exports.fetchTokenScopes = fetchTokenScopes;
|
package/dist/utils/help.js
CHANGED
|
@@ -6,6 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.includeHelp = exports.showHelp = void 0;
|
|
7
7
|
const commandHandler_1 = require("./commandHandler");
|
|
8
8
|
const colors_1 = __importDefault(require("./colors"));
|
|
9
|
+
const helpLogin = [
|
|
10
|
+
` ${colors_1.default.underItalic("login [--logout]")}`,
|
|
11
|
+
` ${colors_1.default.gray("Save your Lichess token and domain for future use.")}`,
|
|
12
|
+
` ${colors_1.default.gray("This allows you to use the CLI without setting environment variables.")}`,
|
|
13
|
+
` ${colors_1.default.bold("Options:")}`,
|
|
14
|
+
` --logout (-lo) ${colors_1.default.gray("Clear saved credentials and log out.")}`,
|
|
15
|
+
].join("\n");
|
|
9
16
|
const helpDelay = [
|
|
10
17
|
` ${colors_1.default.underItalic("delay <broadcastId> <delayInSeconds> [--onlyDelay] [--noDelay] [--rounds <roundsToFix>]")} `,
|
|
11
18
|
` ${colors_1.default.gray("Sets the delay for all rounds in the specified broadcast.")}`,
|
|
@@ -65,6 +72,8 @@ const msg = [
|
|
|
65
72
|
``,
|
|
66
73
|
``,
|
|
67
74
|
`${colors_1.default.boldYellow("Commands:")}`,
|
|
75
|
+
helpLogin,
|
|
76
|
+
``,
|
|
68
77
|
helpDelay,
|
|
69
78
|
``,
|
|
70
79
|
helpSetPGN,
|
|
@@ -81,21 +90,28 @@ const msg = [
|
|
|
81
90
|
``,
|
|
82
91
|
``,
|
|
83
92
|
`${colors_1.default.boldYellow("Examples:")}`,
|
|
93
|
+
` ${colors_1.default.gray("# Login with your Lichess token (interactive)")}`,
|
|
94
|
+
` $ ${colors_1.default.underItalic("login")}`,
|
|
95
|
+
` ${colors_1.default.gray("# Login with token as argument")}`,
|
|
96
|
+
` $ ${colors_1.default.underItalic("login")} ${colors_1.default.italic("lip_yourtoken https://lichess.org")}`,
|
|
97
|
+
` ${colors_1.default.gray("# Logout and clear saved credentials")}`,
|
|
98
|
+
` $ ${colors_1.default.underItalic("login")} ${colors_1.default.italic("--logout")}`,
|
|
84
99
|
` ${colors_1.default.gray("# Set a 5-minute delay without changing start time")}`,
|
|
85
100
|
` $ ${colors_1.default.underItalic("delay")} ${colors_1.default.italic("bcast123 300 --onlyDelay")}`,
|
|
86
101
|
` ${colors_1.default.gray("# Set source PGN URL with round and slice filters")}`,
|
|
87
102
|
` $ ${colors_1.default.underItalic("setPGN")} ${colors_1.default.italic('bcast123 https://example.com/pgns/round-{}/game.pgn --withFilter --slice "1-5,7,9-12"')}`,
|
|
88
|
-
`
|
|
103
|
+
` ${colors_1.default.gray("# Set source PGN URLs for multiple games per round")}`,
|
|
89
104
|
` $ ${colors_1.default.underItalic("setPGNMulti")} ${colors_1.default.italic('bcast123 https://example.com/pgns/round-{r}/game-{g}.pgn 12 --withFilter --onlyGames "1-5,7,9-12"')}`,
|
|
90
105
|
` ${colors_1.default.gray("# Set Lichess games for a broadcast round")}`,
|
|
91
106
|
` $ ${colors_1.default.underItalic("setLichessGames")} ${colors_1.default.italic("round456 gameId1 gameId2 gameId3")}`,
|
|
92
107
|
` ${colors_1.default.gray("# Fix schedule of rounds 1 to 4 and all rounds after 8 by adding 15 minutes")}`,
|
|
93
108
|
` $ ${colors_1.default.underItalic("fixSchedule")} ${colors_1.default.italic("bcast123 15m --rounds 1-4,8+")}`,
|
|
94
|
-
`
|
|
109
|
+
` ${colors_1.default.gray("# Set startsAfterPrevious to true for all rounds in a broadcast")}`,
|
|
95
110
|
` $ ${colors_1.default.underItalic("startsPrevious")} ${colors_1.default.italic("bcast123 true")}`,
|
|
96
111
|
];
|
|
97
112
|
const showHelp = (cmd) => {
|
|
98
113
|
const ranges = {
|
|
114
|
+
[commandHandler_1.Command.Login]: helpLogin,
|
|
99
115
|
[commandHandler_1.Command.Delay]: helpDelay,
|
|
100
116
|
[commandHandler_1.Command.SetPGN]: helpSetPGN,
|
|
101
117
|
[commandHandler_1.Command.SetPGNMulti]: helpSetPGNMulti,
|