@clipdone/cli 0.1.0 → 0.1.2

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;
@@ -239,16 +241,25 @@ function clearStoredAuth() {
239
241
  delete config.token;
240
242
  delete config.tokenExpiresAt;
241
243
  delete config.oauth;
244
+ delete config.oauthClient;
245
+ writeConfig(config);
246
+ }
247
+ function clearStoredOAuthClientRegistration() {
248
+ const config = readConfig();
249
+ if (!config.oauthClient) return;
250
+ delete config.oauthClient;
242
251
  writeConfig(config);
243
252
  }
244
253
  function readStoredOAuthClientRegistration(scope, resource) {
245
254
  const registration = readConfig().oauthClient;
246
255
  if (!registration || typeof registration !== "object") return null;
256
+ const version = Number(registration.version);
247
257
  const clientId = normalizeString(registration.clientId);
248
258
  const apiUrl = normalizeString(registration.apiUrl);
249
259
  const storedScope = normalizeString(registration.scope);
250
260
  const storedResource = normalizeString(registration.resource);
251
261
  if (!clientId || !apiUrl || !storedScope || !storedResource) return null;
262
+ if (version !== OAUTH_CLIENT_REGISTRATION_VERSION) return null;
252
263
  if (apiUrl !== apiBase()) return null;
253
264
  if (storedScope !== normalizeString(scope)) return null;
254
265
  if (storedResource !== normalizeString(resource)) return null;
@@ -261,6 +272,7 @@ function writeStoredOAuthClientRegistration(clientId, scope, resource) {
261
272
  if (!normalizedClientId || !normalizedScope || !normalizedResource) return;
262
273
  const config = readConfig();
263
274
  config.oauthClient = {
275
+ version: OAUTH_CLIENT_REGISTRATION_VERSION,
264
276
  clientId: normalizedClientId,
265
277
  apiUrl: apiBase(),
266
278
  scope: normalizedScope,
@@ -525,7 +537,7 @@ async function oauthRegisterClient({ scope }) {
525
537
  },
526
538
  body: JSON.stringify({
527
539
  client_name: "ClipDone CLI",
528
- redirect_uris: ["http://127.0.0.1/callback"],
540
+ redirect_uris: OAUTH_LOOPBACK_PORTS.map((port) => `http://127.0.0.1:${port}/callback`),
529
541
  grant_types: ["authorization_code", "refresh_token"],
530
542
  response_types: ["code"],
531
543
  token_endpoint_auth_method: "none",
@@ -553,10 +565,14 @@ async function oauthRegisterClient({ scope }) {
553
565
  return { clientId };
554
566
  }
555
567
  async function resolveOAuthClient(authConfig) {
568
+ const activeSession = readStoredOAuthSession();
556
569
  const cached = readStoredOAuthClientRegistration(authConfig?.scope, authConfig?.resource);
557
- if (cached) {
570
+ if (cached && activeSession?.clientId === cached.clientId) {
558
571
  return cached;
559
572
  }
573
+ if (cached) {
574
+ clearStoredOAuthClientRegistration();
575
+ }
560
576
  const registered = await oauthRegisterClient({ scope: authConfig.scope });
561
577
  writeStoredOAuthClientRegistration(registered.clientId, authConfig.scope, authConfig.resource);
562
578
  return registered;
@@ -984,10 +1000,10 @@ function printHelp() {
984
1000
  writeLine("Usage: clipdone <command> [options]");
985
1001
  writeLine();
986
1002
  writeLine("Preferred commands:");
987
- writeLine(" auth login Authorize this CLI in your browser");
1003
+ writeLine(" login Authorize this CLI in your browser");
988
1004
  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");
1005
+ writeLine(" logout Remove the local login only");
1006
+ writeLine(" revoke Revoke the current CLI login");
991
1007
  writeLine(" Login options: --no-open --print-auth-url");
992
1008
  writeLine(" projects List projects");
993
1009
  writeLine(" projects create [--name <name>] Create a project");
@@ -1024,7 +1040,7 @@ function printHelp() {
1024
1040
  writeLine(' clipdone projects <id> upload "/path/with spaces/video (1).mp4" --type footage');
1025
1041
  writeLine(" clipdone projects <id> process");
1026
1042
  writeLine(" clipdone projects <id> status");
1027
- writeLine(" clipdone auth login --no-open");
1043
+ writeLine(" clipdone login --no-open");
1028
1044
  writeLine();
1029
1045
  writeLine("Useful options:");
1030
1046
  writeLine(" --help Show command help");
@@ -1097,17 +1113,36 @@ async function generateCodeChallenge(verifier) {
1097
1113
  const digest = await webcrypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
1098
1114
  return base64UrlEncode(new Uint8Array(digest));
1099
1115
  }
1116
+ async function listenOnLoopbackPort() {
1117
+ const errors = [];
1118
+ for (const port of OAUTH_LOOPBACK_PORTS) {
1119
+ const server = http.createServer();
1120
+ try {
1121
+ await new Promise((resolve, reject) => {
1122
+ server.once("error", reject);
1123
+ server.listen(port, "127.0.0.1", () => resolve());
1124
+ });
1125
+ return { server, port };
1126
+ } catch (error) {
1127
+ server.close();
1128
+ const code = typeof error?.code === "string" ? error.code : null;
1129
+ if (code === "EADDRINUSE" || code === "EACCES") {
1130
+ errors.push(`${port}:${code}`);
1131
+ continue;
1132
+ }
1133
+ throw error;
1134
+ }
1135
+ }
1136
+ const detail = errors.length > 0 ? ` Tried ${errors.join(", ")}.` : "";
1137
+ throw new Error(`Could not reserve a local callback port for ClipDone login.${detail}`);
1138
+ }
1100
1139
  async function login(args) {
1101
1140
  const authConfig = { resource: API_OAUTH_RESOURCE, scope: API_OAUTH_SCOPES.join(" ") };
1102
1141
  const oauthClient = await resolveOAuthClient(authConfig);
1103
1142
  const state = randomState();
1104
1143
  const codeVerifier = generateCodeVerifier();
1105
1144
  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
- });
1145
+ const { server, port } = await listenOnLoopbackPort();
1111
1146
  const redirectUri = `http://127.0.0.1:${port}/callback`;
1112
1147
  const authUrl = new URL(OAUTH_AUTHORIZE_PATH, appBase());
1113
1148
  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.2",
4
4
  "description": "Command line access to ClipDone projects and exports",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",