@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 CHANGED
@@ -8,13 +8,13 @@ Install globally for normal use:
8
8
 
9
9
  ```bash
10
10
  npm install -g @clipdone/cli
11
- clipdone auth login
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 auth login
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 auth login
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 auth login --no-open
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 auth logout
42
- clipdone auth revoke
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: ["http://127.0.0.1/callback"],
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(" auth login Authorize this CLI in your browser");
992
+ writeLine(" login Authorize this CLI in your browser");
988
993
  writeLine(" me Show the current CLI login");
989
- writeLine(" auth logout Remove the local login only");
990
- writeLine(" auth revoke Revoke the current CLI login");
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 auth login --no-open");
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 = http.createServer();
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipdone/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Command line access to ClipDone projects and exports",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",