@playdrop/playdrop-cli 0.3.10 → 0.3.11

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.
Files changed (38) hide show
  1. package/config/client-meta.json +4 -4
  2. package/dist/commands/browse.js +1 -1
  3. package/dist/commands/capture.js +2 -2
  4. package/dist/commands/comments.js +3 -3
  5. package/dist/commands/create.js +3 -3
  6. package/dist/commands/createRemixContent.js +1 -1
  7. package/dist/commands/creations.js +2 -2
  8. package/dist/commands/credits.js +2 -2
  9. package/dist/commands/detail.js +1 -1
  10. package/dist/commands/dev.js +2 -2
  11. package/dist/commands/feedback.js +1 -1
  12. package/dist/commands/generation.js +2 -2
  13. package/dist/commands/gettingStarted.js +1 -1
  14. package/dist/commands/init.js +1 -1
  15. package/dist/commands/login.d.ts +10 -10
  16. package/dist/commands/login.js +202 -79
  17. package/dist/commands/logout.js +1 -1
  18. package/dist/commands/notifications.js +3 -3
  19. package/dist/commands/versionsBrowse.js +1 -1
  20. package/dist/commands/whoami.js +9 -9
  21. package/dist/index.js +41 -25
  22. package/dist/messages.js +5 -5
  23. package/node_modules/@playdrop/api-client/dist/client.d.ts +13 -2
  24. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  25. package/node_modules/@playdrop/api-client/dist/client.js +21 -0
  26. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +10 -1
  27. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  28. package/node_modules/@playdrop/api-client/dist/domains/auth.js +94 -0
  29. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +2 -2
  30. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  31. package/node_modules/@playdrop/api-client/dist/domains/payments.js +2 -1
  32. package/node_modules/@playdrop/api-client/dist/index.d.ts +16 -3
  33. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  34. package/node_modules/@playdrop/api-client/dist/index.js +37 -4
  35. package/node_modules/@playdrop/config/client-meta.json +4 -4
  36. package/node_modules/@playdrop/types/dist/api.d.ts +177 -0
  37. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  38. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.10",
2
+ "version": "0.3.11",
3
3
  "build": 1,
4
4
  "platforms": {
5
5
  "ios": {
@@ -26,11 +26,11 @@
26
26
  },
27
27
  "clients": {
28
28
  "web": {
29
- "minimumVersion": "0.3.10",
29
+ "minimumVersion": "0.3.11",
30
30
  "minimumBuild": 1
31
31
  },
32
32
  "admin": {
33
- "minimumVersion": "0.3.10",
33
+ "minimumVersion": "0.3.11",
34
34
  "minimumBuild": 1
35
35
  },
36
36
  "apple": {
@@ -38,7 +38,7 @@
38
38
  "minimumBuild": 1
39
39
  },
40
40
  "cli": {
41
- "minimumVersion": "0.3.10"
41
+ "minimumVersion": "0.3.11"
42
42
  }
43
43
  }
44
44
  }
@@ -362,7 +362,7 @@ async function browse(options = {}) {
362
362
  if (apiError.status === 401 || apiError.status === 403) {
363
363
  return {
364
364
  problem: 'Browsing this content requires you to be logged in.',
365
- suggestions: ['Run "playdrop auth login" and retry.'],
365
+ suggestions: ['Run "playdrop login" and retry.'],
366
366
  };
367
367
  }
368
368
  return {
@@ -326,8 +326,8 @@ async function capture(targetArg, options = {}) {
326
326
  }
327
327
  if (error instanceof types_1.ApiError) {
328
328
  (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
329
- 'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
330
- 'Use "playdrop auth whoami" afterwards to confirm your status.'
329
+ 'Run "playdrop login" to refresh your session and ensure the API is reachable.',
330
+ 'Use "playdrop whoami" afterwards to confirm your status.',
331
331
  ], { command: 'project capture' });
332
332
  process.exitCode = 1;
333
333
  return;
@@ -72,7 +72,7 @@ async function browseComments(rawRef, options = {}) {
72
72
  if (apiError.status === 401 || apiError.status === 403) {
73
73
  return {
74
74
  problem: `You do not have access to comments for "${ref.ref}".`,
75
- suggestions: ['Run "playdrop auth login" if this is private content you own.'],
75
+ suggestions: ['Run "playdrop login" if this is private content you own.'],
76
76
  };
77
77
  }
78
78
  return {
@@ -125,7 +125,7 @@ async function addComment(rawRef, options = {}) {
125
125
  if (apiError.status === 401 || apiError.status === 403) {
126
126
  return {
127
127
  problem: 'Adding a comment requires you to be logged in.',
128
- suggestions: ['Run "playdrop auth login" and retry.'],
128
+ suggestions: ['Run "playdrop login" and retry.'],
129
129
  };
130
130
  }
131
131
  if (apiError.status === 404) {
@@ -166,7 +166,7 @@ async function deleteComment(commentId, options = {}) {
166
166
  if (apiError.status === 401 || apiError.status === 403) {
167
167
  return {
168
168
  problem: 'Deleting a comment requires a valid login with permission to manage it.',
169
- suggestions: ['Run "playdrop auth login" and retry.'],
169
+ suggestions: ['Run "playdrop login" and retry.'],
170
170
  };
171
171
  }
172
172
  if (apiError.status === 404) {
@@ -877,7 +877,7 @@ async function create(name, options = {}) {
877
877
  const choices = (0, environment_1.formatEnvironmentList)();
878
878
  (0, messages_1.printErrorWithHelp)(`Environment "${envName}" from your Playdrop config is not supported.`, [
879
879
  `Available environments: ${choices}.`,
880
- 'Run "playdrop auth login --env <env>" to save a supported environment before creating apps.'
880
+ 'Run "playdrop login --env <env>" to save a supported environment before creating apps.'
881
881
  ], { command: 'project create app', includeGeneralHelp: false });
882
882
  process.exitCode = 1;
883
883
  return;
@@ -1227,7 +1227,7 @@ async function create(name, options = {}) {
1227
1227
  return;
1228
1228
  }
1229
1229
  if (error instanceof types_1.ApiError) {
1230
- (0, messages_1.printErrorWithHelp)(`Failed to resolve your creator account (status ${error.status}).`, ['Run "playdrop auth login" to refresh your session, then try again.'], { command: 'project create app' });
1230
+ (0, messages_1.printErrorWithHelp)(`Failed to resolve your creator account (status ${error.status}).`, ['Run "playdrop login" to refresh your session, then try again.'], { command: 'project create app' });
1231
1231
  process.exitCode = 1;
1232
1232
  return;
1233
1233
  }
@@ -1237,7 +1237,7 @@ async function create(name, options = {}) {
1237
1237
  return;
1238
1238
  }
1239
1239
  if (error instanceof Error && error.message === 'missing_creator_username') {
1240
- (0, messages_1.printErrorWithHelp)('The API did not return a creator username.', ['Run "playdrop auth login" again, then retry.'], { command: 'project create app' });
1240
+ (0, messages_1.printErrorWithHelp)('The API did not return a creator username.', ['Run "playdrop login" again, then retry.'], { command: 'project create app' });
1241
1241
  process.exitCode = 1;
1242
1242
  return;
1243
1243
  }
@@ -115,7 +115,7 @@ async function createAuthenticatedClient(commandLabel) {
115
115
  if (!envConfig) {
116
116
  (0, messages_1.printErrorWithHelp)(`Environment "${envName}" from your Playdrop config is not supported.`, [
117
117
  `Available environments: ${(0, environment_1.formatEnvironmentList)()}.`,
118
- 'Run "playdrop auth login --env <env>" to save a supported environment before retrying.',
118
+ 'Run "playdrop login --env <env>" to save a supported environment before retrying.',
119
119
  ], { command: commandLabel, includeGeneralHelp: false });
120
120
  process.exitCode = 1;
121
121
  throw new Error('unsupported_env');
@@ -65,7 +65,7 @@ async function resolveCreator(client, rawCreator, command) {
65
65
  const response = await client.me();
66
66
  const username = response.user?.username?.trim();
67
67
  if (!username) {
68
- (0, messages_1.printErrorWithHelp)('Could not resolve your current creator account.', ['Run "playdrop auth whoami" to confirm your session, then retry.'], { command });
68
+ (0, messages_1.printErrorWithHelp)('Could not resolve your current creator account.', ['Run "playdrop whoami" to confirm your session, then retry.'], { command });
69
69
  process.exitCode = 1;
70
70
  return null;
71
71
  }
@@ -283,7 +283,7 @@ async function browseCreations(options = {}) {
283
283
  const handled = (0, errors_1.handleCommandFailure)(error, 'creations browse', 'Creation lookup', {
284
284
  apiMessage: (apiError) => ({
285
285
  problem: `Creation lookup failed with status ${apiError.status}.`,
286
- suggestions: ['Run "playdrop auth login" and retry.'],
286
+ suggestions: ['Run "playdrop login" and retry.'],
287
287
  }),
288
288
  });
289
289
  if (!handled) {
@@ -34,7 +34,7 @@ async function showCreditBalance(options = {}) {
34
34
  const handled = (0, errors_1.handleCommandFailure)(error, 'credits balance', 'Credit balance lookup', {
35
35
  apiMessage: (apiError) => ({
36
36
  problem: `Credit balance lookup failed with status ${apiError.status}.`,
37
- suggestions: ['Run "playdrop auth login" and retry.'],
37
+ suggestions: ['Run "playdrop login" and retry.'],
38
38
  }),
39
39
  });
40
40
  if (!handled) {
@@ -80,7 +80,7 @@ async function browseCreditTransactions(options = {}) {
80
80
  const handled = (0, errors_1.handleCommandFailure)(error, 'credits transactions', 'Credit transaction lookup', {
81
81
  apiMessage: (apiError) => ({
82
82
  problem: `Credit transaction lookup failed with status ${apiError.status}.`,
83
- suggestions: ['Run "playdrop auth login" and retry.'],
83
+ suggestions: ['Run "playdrop login" and retry.'],
84
84
  }),
85
85
  });
86
86
  if (!handled) {
@@ -161,7 +161,7 @@ async function detail(rawRef, options = {}) {
161
161
  if (apiError.status === 401 || apiError.status === 403) {
162
162
  return {
163
163
  problem: `You do not have access to "${ref.ref}".`,
164
- suggestions: ['Run "playdrop auth login" if this is private content you own.', 'Use "playdrop help detail" for the ref format.'],
164
+ suggestions: ['Run "playdrop login" if this is private content you own.', 'Use "playdrop help detail" for the ref format.'],
165
165
  };
166
166
  }
167
167
  return {
@@ -120,8 +120,8 @@ async function dev(targetArg, _port, appOption) {
120
120
  }
121
121
  if (error instanceof types_1.ApiError) {
122
122
  (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
123
- 'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
124
- 'Use "playdrop auth whoami" afterwards to confirm your status.'
123
+ 'Run "playdrop login" to refresh your session and ensure the API is reachable.',
124
+ 'Use "playdrop whoami" afterwards to confirm your status.',
125
125
  ], { command: 'project dev' });
126
126
  process.exitCode = 1;
127
127
  return;
@@ -96,7 +96,7 @@ async function sendFeedback(options = {}) {
96
96
  const handled = (0, errors_1.handleCommandFailure)(error, 'feedback send', 'Feedback send', {
97
97
  apiMessage: (apiError) => ({
98
98
  problem: `Feedback send failed with status ${apiError.status}.`,
99
- suggestions: ['Check the provided fields and retry.', 'Run "playdrop auth login" if your session may be stale.'],
99
+ suggestions: ['Check the provided fields and retry.', 'Run "playdrop login" if your session may be stale.'],
100
100
  }),
101
101
  });
102
102
  if (!handled) {
@@ -601,7 +601,7 @@ function handleAiFailure(error, command, context) {
601
601
  if (apiError.status === 401 || apiError.status === 403) {
602
602
  return {
603
603
  problem: `${context} requires you to be logged in.`,
604
- suggestions: ['Run "playdrop auth login" and retry.'],
604
+ suggestions: ['Run "playdrop login" and retry.'],
605
605
  };
606
606
  }
607
607
  return {
@@ -616,7 +616,7 @@ function handleAiFailure(error, command, context) {
616
616
  const status = typeof error?.status === 'number' ? Number(error.status) : null;
617
617
  if (status !== null) {
618
618
  if (status === 401 || status === 403) {
619
- (0, messages_1.printErrorWithHelp)(`${context} requires you to be logged in.`, ['Run "playdrop auth login" and retry.'], { command });
619
+ (0, messages_1.printErrorWithHelp)(`${context} requires you to be logged in.`, ['Run "playdrop login" and retry.'], { command });
620
620
  }
621
621
  else {
622
622
  (0, messages_1.printErrorWithHelp)(`${context} failed with status ${status}.`, ['Retry in a moment.'], { command });
@@ -4,7 +4,7 @@ exports.printGettingStarted = printGettingStarted;
4
4
  function printGettingStarted() {
5
5
  console.log('Start here:\n');
6
6
  console.log('1. Log in');
7
- console.log(' playdrop auth login');
7
+ console.log(' playdrop login');
8
8
  console.log('');
9
9
  console.log('2. Initialize a workspace');
10
10
  console.log(' playdrop project init .');
@@ -55,7 +55,7 @@ async function resolveUsername(apiBase, token) {
55
55
  }
56
56
  if (unknownError instanceof types_1.ApiError) {
57
57
  (0, messages_1.printErrorWithHelp)(`Request failed with status ${unknownError.status}.`, [
58
- 'Run "playdrop auth login" to refresh your credentials.',
58
+ 'Run "playdrop login" to refresh your credentials.',
59
59
  'Use "playdrop project init" again after logging in.'
60
60
  ], { command: 'project init' });
61
61
  process.exitCode = 1;
@@ -1,10 +1,10 @@
1
- import { Writable } from 'node:stream';
2
- export declare class MaskedStdout extends Writable {
3
- private readonly target;
4
- muted: boolean;
5
- constructor(target?: NodeJS.WriteStream & {
6
- fd: 1;
7
- });
8
- _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
9
- }
10
- export declare function login(env: string, usernameArg?: string, passwordArg?: string): Promise<void>;
1
+ type LoginOptions = {
2
+ username?: string;
3
+ password?: string;
4
+ key?: string;
5
+ };
6
+ type OpenBrowserFn = (url: string) => Promise<boolean>;
7
+ export declare function setOpenBrowserForTests(fn: OpenBrowserFn): void;
8
+ export declare function resetOpenBrowserForTests(): void;
9
+ export declare function login(env: string, options?: LoginOptions): Promise<void>;
10
+ export {};
@@ -1,118 +1,241 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MaskedStdout = void 0;
3
+ exports.setOpenBrowserForTests = setOpenBrowserForTests;
4
+ exports.resetOpenBrowserForTests = resetOpenBrowserForTests;
7
5
  exports.login = login;
8
- const promises_1 = __importDefault(require("node:readline/promises"));
9
- const node_stream_1 = require("node:stream");
10
- const node_process_1 = require("node:process");
6
+ const node_child_process_1 = require("node:child_process");
11
7
  const types_1 = require("@playdrop/types");
12
8
  const config_1 = require("../config");
13
9
  const apiClient_1 = require("../apiClient");
14
10
  const http_1 = require("../http");
15
11
  const environment_1 = require("../environment");
16
12
  const messages_1 = require("../messages");
17
- class MaskedStdout extends node_stream_1.Writable {
18
- constructor(target = node_process_1.stdout) {
19
- super();
20
- this.target = target;
21
- this.muted = false;
22
- }
23
- _write(chunk, encoding, callback) {
24
- if (!this.muted) {
25
- this.target.write(chunk, encoding);
26
- }
27
- else {
28
- const str = typeof chunk === 'string' ? chunk : chunk?.toString?.();
29
- if (str && str.includes('\n')) {
30
- this.target.write('\n');
31
- }
32
- }
33
- callback();
13
+ function sleep(ms) {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ }
16
+ function getBrowserCommand(url) {
17
+ if (process.platform === 'darwin') {
18
+ return { command: 'open', args: [url] };
19
+ }
20
+ if (process.platform === 'win32') {
21
+ return { command: 'cmd', args: ['/c', 'start', '', url] };
34
22
  }
23
+ if (process.platform === 'linux') {
24
+ return { command: 'xdg-open', args: [url] };
25
+ }
26
+ return null;
35
27
  }
36
- exports.MaskedStdout = MaskedStdout;
37
- async function login(env, usernameArg, passwordArg) {
38
- const envConfig = (0, environment_1.resolveEnvironmentConfig)(env);
39
- if (!envConfig) {
40
- const choices = (0, environment_1.formatEnvironmentList)();
41
- (0, messages_1.printUnknownEnvironment)(env, choices, 'login');
28
+ async function defaultOpenBrowser(url) {
29
+ const command = getBrowserCommand(url);
30
+ if (!command) {
31
+ return false;
32
+ }
33
+ return await new Promise((resolve) => {
34
+ const child = (0, node_child_process_1.spawn)(command.command, command.args, {
35
+ detached: true,
36
+ stdio: 'ignore',
37
+ });
38
+ child.once('error', () => resolve(false));
39
+ child.once('spawn', () => {
40
+ child.unref();
41
+ resolve(true);
42
+ });
43
+ });
44
+ }
45
+ let openBrowser = defaultOpenBrowser;
46
+ function setOpenBrowserForTests(fn) {
47
+ openBrowser = fn;
48
+ }
49
+ function resetOpenBrowserForTests() {
50
+ openBrowser = defaultOpenBrowser;
51
+ }
52
+ function extractAccessToken(data) {
53
+ return data.accessToken
54
+ ?? (typeof data === 'object' && data !== null && 'token' in data ? data.token : undefined);
55
+ }
56
+ function isLoginResponse(data) {
57
+ return typeof data === 'object' && data !== null && 'user' in data;
58
+ }
59
+ function storeLogin(env, data) {
60
+ const token = extractAccessToken(data);
61
+ if (!token) {
62
+ (0, messages_1.printErrorWithHelp)('Login succeeded but no access token was returned.', [
63
+ 'Please try again in a moment.',
64
+ 'If the issue persists, contact the Playdrop team.',
65
+ ], { command: 'login' });
42
66
  process.exitCode = 1;
43
67
  return;
44
68
  }
45
- if (envConfig.allowInsecureRequests) {
46
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
69
+ (0, config_1.saveConfig)({ token, env });
70
+ const username = data.user?.username ?? 'unknown';
71
+ console.log(`Logged in as ${username} on ${env}.`);
72
+ console.log('Next: run "playdrop whoami" to confirm your session.');
73
+ }
74
+ async function loginWithUsernamePassword(env, baseUrl, username, password) {
75
+ const client = (0, apiClient_1.createCliApiClient)({ baseUrl });
76
+ try {
77
+ const data = await client.login({ username, password });
78
+ storeLogin(env, data);
47
79
  }
48
- const base = envConfig.apiBase;
49
- let username = usernameArg;
50
- let password = passwordArg;
51
- if (!username || !password) {
52
- const maskedOutput = new MaskedStdout();
53
- const rl = promises_1.default.createInterface({ input: node_process_1.stdin, output: maskedOutput, terminal: true });
54
- if (!username) {
55
- maskedOutput.muted = false;
56
- username = (await rl.question('Username: ')).trim();
80
+ catch (unknownError) {
81
+ if (unknownError instanceof types_1.UnsupportedClientError) {
82
+ (0, http_1.handleUnsupportedError)(unknownError, 'Login');
83
+ return;
57
84
  }
58
- if (!password) {
59
- maskedOutput.muted = true;
60
- password = await rl.question('Password: ');
61
- maskedOutput.muted = false;
85
+ if (unknownError instanceof types_1.ApiError) {
86
+ (0, messages_1.printErrorWithHelp)(`Login failed with status ${unknownError.status}.`, [
87
+ 'Verify your username and password, then rerun "playdrop login".',
88
+ 'Use "playdrop whoami" after logging in to confirm your status.',
89
+ ], { command: 'login' });
90
+ process.exitCode = 1;
91
+ return;
62
92
  }
63
- rl.close();
93
+ throw unknownError;
64
94
  }
65
- if (!username || !password) {
66
- (0, messages_1.printErrorWithHelp)('Username and password are required to log in.', [
67
- 'Provide both credentials when prompted, then rerun "playdrop auth login".',
68
- ], { command: 'auth login' });
69
- process.exitCode = 1;
70
- return;
95
+ }
96
+ async function loginWithKey(env, baseUrl, apiKey) {
97
+ const client = (0, apiClient_1.createCliApiClient)({ baseUrl });
98
+ try {
99
+ const data = await client.loginWithApiKey({ apiKey });
100
+ storeLogin(env, data);
101
+ }
102
+ catch (unknownError) {
103
+ if (unknownError instanceof types_1.UnsupportedClientError) {
104
+ (0, http_1.handleUnsupportedError)(unknownError, 'Login');
105
+ return;
106
+ }
107
+ if (unknownError instanceof types_1.ApiError) {
108
+ (0, messages_1.printErrorWithHelp)(`Login failed with status ${unknownError.status}.`, [
109
+ 'Verify your API key, then rerun "playdrop login --key <api-key>".',
110
+ 'Use "playdrop whoami" after logging in to confirm your status.',
111
+ ], { command: 'login' });
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ throw unknownError;
116
+ }
117
+ }
118
+ async function loginInBrowser(env, baseUrl) {
119
+ const client = (0, apiClient_1.createCliApiClient)({ baseUrl });
120
+ let flow;
121
+ try {
122
+ flow = await client.startCliLogin();
71
123
  }
72
- async function doLogin(currentBase) {
73
- const client = (0, apiClient_1.createCliApiClient)({ baseUrl: currentBase });
124
+ catch (unknownError) {
125
+ if (unknownError instanceof types_1.UnsupportedClientError) {
126
+ (0, http_1.handleUnsupportedError)(unknownError, 'Login');
127
+ return;
128
+ }
129
+ if (unknownError instanceof types_1.ApiError) {
130
+ (0, messages_1.printErrorWithHelp)(`Could not start browser login (status ${unknownError.status}).`, [
131
+ 'Try again in a moment.',
132
+ 'If the problem continues, contact the Playdrop team.',
133
+ ], { command: 'login' });
134
+ process.exitCode = 1;
135
+ return;
136
+ }
137
+ throw unknownError;
138
+ }
139
+ console.log(`Open this URL to continue: ${flow.loginUrl}`);
140
+ const opened = await openBrowser(flow.loginUrl).catch(() => false);
141
+ if (opened) {
142
+ console.log('Opened the login page in your browser.');
143
+ }
144
+ else {
145
+ console.log('Open the URL above in your browser to continue.');
146
+ }
147
+ const expiresAtMs = Date.parse(flow.expiresAt);
148
+ const deadlineMs = Number.isFinite(expiresAtMs)
149
+ ? expiresAtMs
150
+ : Date.now() + 10 * 60 * 1000;
151
+ const intervalMs = Math.max(250, Number(flow.pollIntervalMs) || 1000);
152
+ while (Date.now() < deadlineMs) {
74
153
  try {
75
- return await client.login({ username: username, password: password });
154
+ const result = await client.pollCliLogin({
155
+ requestId: flow.requestId,
156
+ pollToken: flow.pollToken,
157
+ });
158
+ if (!isLoginResponse(result)) {
159
+ await sleep(intervalMs);
160
+ continue;
161
+ }
162
+ storeLogin(env, result);
163
+ return;
76
164
  }
77
165
  catch (unknownError) {
78
166
  if (unknownError instanceof types_1.UnsupportedClientError) {
79
167
  (0, http_1.handleUnsupportedError)(unknownError, 'Login');
168
+ return;
80
169
  }
81
170
  if (unknownError instanceof types_1.ApiError) {
82
- (0, messages_1.printErrorWithHelp)(`Login failed with status ${unknownError.status}.`, [
83
- 'Verify your username and password, then rerun "playdrop auth login".',
84
- 'Use "playdrop auth whoami" after logging in to confirm your status.'
85
- ], { command: 'auth login' });
171
+ if (unknownError.status === 410 || unknownError.code === 'cli_login_expired') {
172
+ (0, messages_1.printErrorWithHelp)('Browser login expired before it was approved.', [
173
+ 'Rerun "playdrop login" to start a new browser login request.',
174
+ ], { command: 'login' });
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ (0, messages_1.printErrorWithHelp)(`Browser login failed with status ${unknownError.status}.`, [
179
+ 'Rerun "playdrop login" to try again.',
180
+ ], { command: 'login' });
86
181
  process.exitCode = 1;
87
- return null;
182
+ return;
88
183
  }
89
184
  throw unknownError;
90
185
  }
91
186
  }
92
- let data = null;
93
- try {
94
- data = await doLogin(base);
95
- }
96
- catch (e) {
97
- if (e instanceof http_1.CLIUnsupportedClientError) {
98
- return;
99
- }
100
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to log in.', 'auth login');
187
+ (0, messages_1.printErrorWithHelp)('Browser login timed out after 10 minutes.', [
188
+ 'Rerun "playdrop login" to start a new browser login request.',
189
+ ], { command: 'login' });
190
+ process.exitCode = 1;
191
+ }
192
+ async function login(env, options = {}) {
193
+ const envConfig = (0, environment_1.resolveEnvironmentConfig)(env);
194
+ if (!envConfig) {
195
+ const choices = (0, environment_1.formatEnvironmentList)();
196
+ (0, messages_1.printUnknownEnvironment)(env, choices, 'login');
101
197
  process.exitCode = 1;
102
198
  return;
103
199
  }
104
- if (!data)
200
+ if (envConfig.allowInsecureRequests) {
201
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
202
+ }
203
+ const username = options.username?.trim();
204
+ const password = options.password;
205
+ const apiKey = options.key?.trim();
206
+ if (apiKey && (username || password)) {
207
+ (0, messages_1.printErrorWithHelp)('Choose exactly one login method.', [
208
+ 'Use "playdrop login" for browser login.',
209
+ 'Use "--username" with "--password" for direct login.',
210
+ 'Use "--key" by itself for API key login.',
211
+ ], { command: 'login' });
212
+ process.exitCode = 1;
105
213
  return;
106
- const token = data.accessToken ?? (typeof data === 'object' && data !== null && 'token' in data ? data.token : undefined);
107
- if (!token) {
108
- (0, messages_1.printErrorWithHelp)('Login succeeded but no access token was returned.', [
109
- 'Please try again in a moment.',
110
- 'If the issue persists, contact the Playdrop team.'
111
- ], { command: 'auth login' });
214
+ }
215
+ if ((username && !password) || (!username && password)) {
216
+ (0, messages_1.printErrorWithHelp)('Provide both "--username" and "--password" together.', [
217
+ 'Rerun "playdrop login --username <username> --password <password>".',
218
+ 'Or run "playdrop login" to use browser login instead.',
219
+ ], { command: 'login' });
112
220
  process.exitCode = 1;
113
221
  return;
114
222
  }
115
- (0, config_1.saveConfig)({ token, env });
116
- console.log(`Logged in as ${username} on ${env}.`);
117
- console.log('Next: run "playdrop auth whoami" to confirm your session.');
223
+ try {
224
+ if (apiKey) {
225
+ await loginWithKey(env, envConfig.apiBase, apiKey);
226
+ return;
227
+ }
228
+ if (username && password) {
229
+ await loginWithUsernamePassword(env, envConfig.apiBase, username, password);
230
+ return;
231
+ }
232
+ await loginInBrowser(env, envConfig.apiBase);
233
+ }
234
+ catch (error) {
235
+ if (error instanceof http_1.CLIUnsupportedClientError) {
236
+ return;
237
+ }
238
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to log in.', 'login');
239
+ process.exitCode = 1;
240
+ }
118
241
  }
@@ -5,5 +5,5 @@ const config_1 = require("../config");
5
5
  function logout() {
6
6
  (0, config_1.clearConfig)();
7
7
  console.log('Logged out.');
8
- console.log('Next: run "playdrop auth login" when you need a new session.');
8
+ console.log('Next: run "playdrop login" when you need a new session.');
9
9
  }
@@ -116,7 +116,7 @@ async function browseNotifications(options = {}) {
116
116
  const handled = (0, errors_1.handleCommandFailure)(error, 'notifications browse', 'Notification lookup', {
117
117
  apiMessage: (apiError) => ({
118
118
  problem: `Notification lookup failed with status ${apiError.status}.`,
119
- suggestions: ['Run "playdrop auth login" and retry.'],
119
+ suggestions: ['Run "playdrop login" and retry.'],
120
120
  }),
121
121
  });
122
122
  if (!handled) {
@@ -145,7 +145,7 @@ async function readNotification(rawId, options = {}) {
145
145
  const handled = (0, errors_1.handleCommandFailure)(error, 'notifications read', 'Notification update', {
146
146
  apiMessage: (apiError) => ({
147
147
  problem: `Notification update failed with status ${apiError.status}.`,
148
- suggestions: ['Run "playdrop auth login" and retry.'],
148
+ suggestions: ['Run "playdrop login" and retry.'],
149
149
  }),
150
150
  });
151
151
  if (!handled) {
@@ -168,7 +168,7 @@ async function readAllNotifications(options = {}) {
168
168
  const handled = (0, errors_1.handleCommandFailure)(error, 'notifications read-all', 'Notification update', {
169
169
  apiMessage: (apiError) => ({
170
170
  problem: `Notification update failed with status ${apiError.status}.`,
171
- suggestions: ['Run "playdrop auth login" and retry.'],
171
+ suggestions: ['Run "playdrop login" and retry.'],
172
172
  }),
173
173
  });
174
174
  if (!handled) {
@@ -192,7 +192,7 @@ async function browseVersions(rawRef, options = {}) {
192
192
  if (apiError.status === 401 || apiError.status === 403) {
193
193
  return {
194
194
  problem: `You do not have access to version history for "${ref.ref}".`,
195
- suggestions: ['Run "playdrop auth login" if this is private content you own.'],
195
+ suggestions: ['Run "playdrop login" if this is private content you own.'],
196
196
  };
197
197
  }
198
198
  return {