@clipdone/cli 0.1.0 → 0.1.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 +6 -6
- package/bin/clipdone.js +34 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ Install globally for normal use:
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g @clipdone/cli
|
|
11
|
-
clipdone
|
|
11
|
+
clipdone login
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
For one-off use without a global install:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npx @clipdone/cli
|
|
17
|
+
npx @clipdone/cli login
|
|
18
18
|
pnpm dlx @clipdone/cli projects
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -25,21 +25,21 @@ The CLI requires Node.js 18 or newer.
|
|
|
25
25
|
Authorize the CLI in your browser:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
clipdone
|
|
28
|
+
clipdone login
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
On remote machines or environments where the CLI cannot open a browser automatically:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
clipdone
|
|
34
|
+
clipdone login --no-open
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Check or remove the current login:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
clipdone me
|
|
41
|
-
clipdone
|
|
42
|
-
clipdone
|
|
41
|
+
clipdone logout
|
|
42
|
+
clipdone revoke
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
`logout` removes only the local token. `revoke` also revokes the current OAuth tokens with ClipDone.
|
package/bin/clipdone.js
CHANGED
|
@@ -148,6 +148,8 @@ var OAUTH_AUTHORIZE_PATH = "/api/auth/oauth2/authorize";
|
|
|
148
148
|
var OAUTH_REGISTER_PATH = "/api/auth/oauth2/register";
|
|
149
149
|
var OAUTH_TOKEN_PATH = "/api/auth/oauth2/token";
|
|
150
150
|
var OAUTH_REVOKE_PATH = "/api/auth/oauth2/revoke";
|
|
151
|
+
var OAUTH_CLIENT_REGISTRATION_VERSION = 2;
|
|
152
|
+
var OAUTH_LOOPBACK_PORTS = [59852, 59853, 59854, 59855, 59856, 59857, 59858, 59859];
|
|
151
153
|
var PRODUCTION_APP_URL = "https://app.clipdone.app";
|
|
152
154
|
var LOCAL_APP_URL = "http://localhost:3000";
|
|
153
155
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -244,11 +246,13 @@ function clearStoredAuth() {
|
|
|
244
246
|
function readStoredOAuthClientRegistration(scope, resource) {
|
|
245
247
|
const registration = readConfig().oauthClient;
|
|
246
248
|
if (!registration || typeof registration !== "object") return null;
|
|
249
|
+
const version = Number(registration.version);
|
|
247
250
|
const clientId = normalizeString(registration.clientId);
|
|
248
251
|
const apiUrl = normalizeString(registration.apiUrl);
|
|
249
252
|
const storedScope = normalizeString(registration.scope);
|
|
250
253
|
const storedResource = normalizeString(registration.resource);
|
|
251
254
|
if (!clientId || !apiUrl || !storedScope || !storedResource) return null;
|
|
255
|
+
if (version !== OAUTH_CLIENT_REGISTRATION_VERSION) return null;
|
|
252
256
|
if (apiUrl !== apiBase()) return null;
|
|
253
257
|
if (storedScope !== normalizeString(scope)) return null;
|
|
254
258
|
if (storedResource !== normalizeString(resource)) return null;
|
|
@@ -261,6 +265,7 @@ function writeStoredOAuthClientRegistration(clientId, scope, resource) {
|
|
|
261
265
|
if (!normalizedClientId || !normalizedScope || !normalizedResource) return;
|
|
262
266
|
const config = readConfig();
|
|
263
267
|
config.oauthClient = {
|
|
268
|
+
version: OAUTH_CLIENT_REGISTRATION_VERSION,
|
|
264
269
|
clientId: normalizedClientId,
|
|
265
270
|
apiUrl: apiBase(),
|
|
266
271
|
scope: normalizedScope,
|
|
@@ -525,7 +530,7 @@ async function oauthRegisterClient({ scope }) {
|
|
|
525
530
|
},
|
|
526
531
|
body: JSON.stringify({
|
|
527
532
|
client_name: "ClipDone CLI",
|
|
528
|
-
redirect_uris:
|
|
533
|
+
redirect_uris: OAUTH_LOOPBACK_PORTS.map((port) => `http://127.0.0.1:${port}/callback`),
|
|
529
534
|
grant_types: ["authorization_code", "refresh_token"],
|
|
530
535
|
response_types: ["code"],
|
|
531
536
|
token_endpoint_auth_method: "none",
|
|
@@ -984,10 +989,10 @@ function printHelp() {
|
|
|
984
989
|
writeLine("Usage: clipdone <command> [options]");
|
|
985
990
|
writeLine();
|
|
986
991
|
writeLine("Preferred commands:");
|
|
987
|
-
writeLine("
|
|
992
|
+
writeLine(" login Authorize this CLI in your browser");
|
|
988
993
|
writeLine(" me Show the current CLI login");
|
|
989
|
-
writeLine("
|
|
990
|
-
writeLine("
|
|
994
|
+
writeLine(" logout Remove the local login only");
|
|
995
|
+
writeLine(" revoke Revoke the current CLI login");
|
|
991
996
|
writeLine(" Login options: --no-open --print-auth-url");
|
|
992
997
|
writeLine(" projects List projects");
|
|
993
998
|
writeLine(" projects create [--name <name>] Create a project");
|
|
@@ -1024,7 +1029,7 @@ function printHelp() {
|
|
|
1024
1029
|
writeLine(' clipdone projects <id> upload "/path/with spaces/video (1).mp4" --type footage');
|
|
1025
1030
|
writeLine(" clipdone projects <id> process");
|
|
1026
1031
|
writeLine(" clipdone projects <id> status");
|
|
1027
|
-
writeLine(" clipdone
|
|
1032
|
+
writeLine(" clipdone login --no-open");
|
|
1028
1033
|
writeLine();
|
|
1029
1034
|
writeLine("Useful options:");
|
|
1030
1035
|
writeLine(" --help Show command help");
|
|
@@ -1097,17 +1102,36 @@ async function generateCodeChallenge(verifier) {
|
|
|
1097
1102
|
const digest = await webcrypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
1098
1103
|
return base64UrlEncode(new Uint8Array(digest));
|
|
1099
1104
|
}
|
|
1105
|
+
async function listenOnLoopbackPort() {
|
|
1106
|
+
const errors = [];
|
|
1107
|
+
for (const port of OAUTH_LOOPBACK_PORTS) {
|
|
1108
|
+
const server = http.createServer();
|
|
1109
|
+
try {
|
|
1110
|
+
await new Promise((resolve, reject) => {
|
|
1111
|
+
server.once("error", reject);
|
|
1112
|
+
server.listen(port, "127.0.0.1", () => resolve());
|
|
1113
|
+
});
|
|
1114
|
+
return { server, port };
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
server.close();
|
|
1117
|
+
const code = typeof error?.code === "string" ? error.code : null;
|
|
1118
|
+
if (code === "EADDRINUSE" || code === "EACCES") {
|
|
1119
|
+
errors.push(`${port}:${code}`);
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
throw error;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
const detail = errors.length > 0 ? ` Tried ${errors.join(", ")}.` : "";
|
|
1126
|
+
throw new Error(`Could not reserve a local callback port for ClipDone login.${detail}`);
|
|
1127
|
+
}
|
|
1100
1128
|
async function login(args) {
|
|
1101
1129
|
const authConfig = { resource: API_OAUTH_RESOURCE, scope: API_OAUTH_SCOPES.join(" ") };
|
|
1102
1130
|
const oauthClient = await resolveOAuthClient(authConfig);
|
|
1103
1131
|
const state = randomState();
|
|
1104
1132
|
const codeVerifier = generateCodeVerifier();
|
|
1105
1133
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
1106
|
-
const server =
|
|
1107
|
-
const port = await new Promise((resolve, reject) => {
|
|
1108
|
-
server.once("error", reject);
|
|
1109
|
-
server.listen(0, "127.0.0.1", () => resolve(server.address().port));
|
|
1110
|
-
});
|
|
1134
|
+
const { server, port } = await listenOnLoopbackPort();
|
|
1111
1135
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
1112
1136
|
const authUrl = new URL(OAUTH_AUTHORIZE_PATH, appBase());
|
|
1113
1137
|
authUrl.searchParams.set("client_id", oauthClient.clientId);
|