@playdrop/playdrop-cli 0.4.0 → 0.4.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.
Files changed (56) hide show
  1. package/config/client-meta.json +7 -7
  2. package/dist/appUrls.d.ts +9 -0
  3. package/dist/appUrls.js +33 -0
  4. package/dist/browser.d.ts +5 -0
  5. package/dist/browser.js +45 -0
  6. package/dist/captureRuntime.d.ts +31 -0
  7. package/dist/captureRuntime.js +413 -0
  8. package/dist/commands/browse.js +1 -1
  9. package/dist/commands/capture.js +182 -479
  10. package/dist/commands/captureRemote.d.ts +2 -1
  11. package/dist/commands/captureRemote.js +144 -47
  12. package/dist/commands/comments.js +3 -3
  13. package/dist/commands/create.js +6 -6
  14. package/dist/commands/createRemixContent.js +1 -1
  15. package/dist/commands/creations.js +2 -2
  16. package/dist/commands/credits.js +2 -2
  17. package/dist/commands/detail.js +9 -4
  18. package/dist/commands/dev.js +2 -2
  19. package/dist/commands/feedback.js +26 -11
  20. package/dist/commands/generation.js +2 -2
  21. package/dist/commands/gettingStarted.js +1 -1
  22. package/dist/commands/init.js +1 -1
  23. package/dist/commands/login.d.ts +2 -4
  24. package/dist/commands/login.js +16 -51
  25. package/dist/commands/logout.js +1 -1
  26. package/dist/commands/notifications.js +3 -3
  27. package/dist/commands/play.d.ts +5 -0
  28. package/dist/commands/play.js +102 -0
  29. package/dist/commands/upload.js +10 -11
  30. package/dist/commands/versionsBrowse.js +11 -3
  31. package/dist/commands/whoami.js +3 -3
  32. package/dist/index.js +15 -3
  33. package/dist/messages.js +5 -5
  34. package/dist/playwright.d.ts +2 -1
  35. package/dist/playwright.js +18 -1
  36. package/dist/uploadLog.d.ts +2 -0
  37. package/dist/uploadLog.js +14 -0
  38. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
  39. package/node_modules/@playdrop/ai-client/dist/index.js +136 -3
  40. package/node_modules/@playdrop/api-client/dist/client.d.ts +9 -1
  41. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  42. package/node_modules/@playdrop/api-client/dist/client.js +10 -1
  43. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
  44. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  45. package/node_modules/@playdrop/api-client/dist/domains/auth.js +21 -0
  46. package/node_modules/@playdrop/api-client/dist/domains/free-credits.d.ts +27 -0
  47. package/node_modules/@playdrop/api-client/dist/domains/free-credits.d.ts.map +1 -0
  48. package/node_modules/@playdrop/api-client/dist/domains/free-credits.js +66 -0
  49. package/node_modules/@playdrop/api-client/dist/index.d.ts +10 -2
  50. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/index.js +31 -2
  52. package/node_modules/@playdrop/config/client-meta.json +7 -7
  53. package/node_modules/@playdrop/types/dist/api.d.ts +85 -1
  54. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  55. package/node_modules/@playdrop/types/dist/api.js +15 -0
  56. package/package.json +2 -2
@@ -2,10 +2,11 @@ type CaptureRemoteOptions = {
2
2
  timeout?: string | number;
3
3
  screenshot?: string;
4
4
  log?: string;
5
+ expectedUrl?: string;
5
6
  username?: string;
6
7
  password?: string;
7
8
  loginUrl?: string;
8
9
  };
9
- export declare function buildCaptureRemoteCommandArgs(url: string, options?: CaptureRemoteOptions): string[] | null;
10
+ export declare function buildCaptureRemoteCommandArgs(url: string | undefined, options?: CaptureRemoteOptions): string[] | null;
10
11
  export declare function captureRemote(url: string | undefined, options?: CaptureRemoteOptions): Promise<void>;
11
12
  export {};
@@ -1,65 +1,102 @@
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
3
  exports.buildCaptureRemoteCommandArgs = buildCaptureRemoteCommandArgs;
7
4
  exports.captureRemote = captureRemote;
8
- const node_child_process_1 = require("node:child_process");
9
- const node_path_1 = __importDefault(require("node:path"));
5
+ const node_path_1 = require("node:path");
6
+ const types_1 = require("@playdrop/types");
7
+ const commandContext_1 = require("../commandContext");
8
+ const http_1 = require("../http");
10
9
  const messages_1 = require("../messages");
10
+ const devShared_1 = require("./devShared");
11
+ const captureRuntime_1 = require("../captureRuntime");
11
12
  function parseTimeout(raw) {
12
13
  if (raw === undefined) {
13
- return null;
14
+ return 10;
14
15
  }
15
16
  const parsed = typeof raw === 'number' ? raw : Number.parseFloat(String(raw));
16
17
  if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 600) {
17
- return null;
18
- }
19
- return String(parsed);
20
- }
21
- function buildCaptureRemoteCommandArgs(url, options = {}) {
22
- const trimmedUrl = typeof url === 'string' ? url.trim() : '';
23
- if (!trimmedUrl) {
24
- return null;
25
- }
26
- const args = [
27
- node_path_1.default.resolve(__dirname, '../../scripts/capture-url.mjs'),
28
- '--url',
29
- trimmedUrl,
30
- ];
31
- const timeout = parseTimeout(options.timeout);
32
- if (options.timeout !== undefined && timeout === null) {
33
18
  throw new Error('invalid_timeout');
34
19
  }
35
- if (timeout) {
36
- args.push('--timeout', timeout);
37
- }
38
- if (options.screenshot) {
39
- args.push('--screenshot', options.screenshot);
20
+ return parsed;
21
+ }
22
+ function assertValidUrl(value, errorCode) {
23
+ try {
24
+ new URL(value);
40
25
  }
41
- if (options.log) {
42
- args.push('--log', options.log);
26
+ catch {
27
+ throw new Error(errorCode);
43
28
  }
29
+ }
30
+ function parseCredentialPair(options) {
44
31
  const username = options.username?.trim() || '';
45
32
  const password = options.password || '';
46
33
  if ((username && !password) || (!username && password)) {
47
34
  throw new Error('invalid_credentials_pair');
48
35
  }
36
+ return { username, password };
37
+ }
38
+ function pushOptionalArg(args, flag, value) {
39
+ if (!value?.trim()) {
40
+ return;
41
+ }
42
+ args.push(flag, value.trim());
43
+ }
44
+ function buildCaptureRemoteCommandArgs(url, options = {}) {
45
+ const trimmedUrl = typeof url === 'string' ? url.trim() : '';
46
+ if (!trimmedUrl) {
47
+ return null;
48
+ }
49
+ const { username, password } = parseCredentialPair(options);
50
+ const args = ['--url', trimmedUrl];
51
+ if (options.timeout !== undefined) {
52
+ args.push('--timeout', String(parseTimeout(options.timeout)));
53
+ }
54
+ pushOptionalArg(args, '--screenshot', options.screenshot);
55
+ pushOptionalArg(args, '--log', options.log);
56
+ pushOptionalArg(args, '--expected-url', options.expectedUrl);
49
57
  if (username) {
50
58
  args.push('--username', username, '--password', password);
51
59
  }
52
- if (options.loginUrl) {
53
- args.push('--login-url', options.loginUrl);
54
- }
60
+ pushOptionalArg(args, '--login-url', options.loginUrl);
55
61
  return args;
56
62
  }
63
+ function parseOptions(url, options = {}) {
64
+ const trimmedUrl = typeof url === 'string' ? url.trim() : '';
65
+ if (!trimmedUrl) {
66
+ throw new Error('missing_url');
67
+ }
68
+ assertValidUrl(trimmedUrl, 'invalid_url');
69
+ const expectedUrl = options.expectedUrl?.trim() || null;
70
+ if (expectedUrl) {
71
+ assertValidUrl(expectedUrl, 'invalid_expected_url');
72
+ }
73
+ const { username, password } = parseCredentialPair(options);
74
+ return {
75
+ targetUrl: trimmedUrl,
76
+ expectedUrl,
77
+ timeoutSeconds: parseTimeout(options.timeout),
78
+ screenshotPath: (0, node_path_1.resolve)(process.cwd(), options.screenshot?.trim() || 'output/playwright/capture-url.png'),
79
+ logPath: options.log?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.log.trim()) : null,
80
+ login: username
81
+ ? {
82
+ username,
83
+ password,
84
+ loginUrl: options.loginUrl?.trim() || null,
85
+ }
86
+ : null,
87
+ };
88
+ }
57
89
  async function captureRemote(url, options = {}) {
58
- let args;
90
+ let parsed;
59
91
  try {
60
- args = buildCaptureRemoteCommandArgs(url ?? '', options);
92
+ parsed = parseOptions(url, options);
61
93
  }
62
94
  catch (error) {
95
+ if (error instanceof Error && error.message === 'invalid_expected_url') {
96
+ (0, messages_1.printErrorWithHelp)('The --expected-url value must be a valid URL.', [], { command: 'project capture remote' });
97
+ process.exitCode = 1;
98
+ return;
99
+ }
63
100
  if (error instanceof Error && error.message === 'invalid_timeout') {
64
101
  (0, messages_1.printErrorWithHelp)('The --timeout value must be a number between 0 and 600 seconds.', [], { command: 'project capture remote' });
65
102
  process.exitCode = 1;
@@ -70,21 +107,81 @@ async function captureRemote(url, options = {}) {
70
107
  process.exitCode = 1;
71
108
  return;
72
109
  }
73
- throw error;
74
- }
75
- if (!args) {
76
- (0, messages_1.printErrorWithHelp)('A URL is required.', ['Example: playdrop project capture remote https://www.playdrop.ai/creators/playdrop/apps/game/hangingout'], { command: 'project capture remote' });
110
+ if (error instanceof Error && error.message === 'invalid_url') {
111
+ (0, messages_1.printErrorWithHelp)('A valid URL is required.', ['Example: playdrop project capture remote https://www.playdrop.ai/creators/playdrop/apps/game/hangingout/play'], { command: 'project capture remote' });
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ (0, messages_1.printErrorWithHelp)('A URL is required.', ['Example: playdrop project capture remote https://www.playdrop.ai/creators/playdrop/apps/game/hangingout/play'], { command: 'project capture remote' });
77
116
  process.exitCode = 1;
78
117
  return;
79
118
  }
80
- const result = (0, node_child_process_1.spawnSync)(process.execPath, args, {
81
- stdio: 'inherit',
119
+ await (0, commandContext_1.withEnvironment)('project capture remote', 'Capturing remote app logs', async ({ client, token }) => {
120
+ let currentUser = null;
121
+ try {
122
+ currentUser = await (0, devShared_1.fetchDevUser)(client);
123
+ }
124
+ catch (error) {
125
+ if (error instanceof http_1.CLIUnsupportedClientError) {
126
+ return;
127
+ }
128
+ if (error instanceof types_1.UnsupportedClientError) {
129
+ (0, http_1.handleUnsupportedError)(error, 'Authentication');
130
+ process.exitCode = 1;
131
+ return;
132
+ }
133
+ if (error instanceof types_1.ApiError) {
134
+ (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
135
+ 'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
136
+ 'Use "playdrop auth whoami" afterwards to confirm your status.',
137
+ ], { command: 'project capture remote' });
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+ if ((0, devShared_1.isNetworkError)(error)) {
142
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture remote');
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ throw error;
147
+ }
148
+ console.log(`[capture] Launching Playwright against ${parsed.targetUrl}`);
149
+ try {
150
+ const result = await (0, captureRuntime_1.runCapture)({
151
+ targetUrl: parsed.targetUrl,
152
+ expectedUrl: parsed.expectedUrl ?? parsed.targetUrl,
153
+ timeoutMs: Math.round(parsed.timeoutSeconds * 1000),
154
+ minimumLogLevel: 'info',
155
+ screenshotPath: parsed.screenshotPath,
156
+ logPath: parsed.logPath,
157
+ token: parsed.login ? undefined : token,
158
+ user: parsed.login ? undefined : currentUser,
159
+ login: parsed.login,
160
+ });
161
+ if (result.errorCount > 0) {
162
+ console.error(`[capture] Completed with ${result.errorCount} error(s) after ${parsed.timeoutSeconds} seconds.`);
163
+ process.exitCode = 1;
164
+ }
165
+ else {
166
+ console.log(`[capture] Completed without console errors after ${parsed.timeoutSeconds} seconds.`);
167
+ }
168
+ }
169
+ catch (error) {
170
+ if (error instanceof http_1.CLIUnsupportedClientError) {
171
+ return;
172
+ }
173
+ if (error instanceof types_1.UnsupportedClientError) {
174
+ (0, http_1.handleUnsupportedError)(error, 'Capture');
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ if (error instanceof Error && error.stack) {
179
+ console.error(error.stack);
180
+ }
181
+ else {
182
+ console.error(error instanceof Error ? error.message : String(error));
183
+ }
184
+ process.exitCode = 1;
185
+ }
82
186
  });
83
- if (typeof result.status === 'number' && result.status !== 0) {
84
- process.exitCode = result.status;
85
- return;
86
- }
87
- if (result.error) {
88
- throw result.error;
89
- }
90
187
  }
@@ -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 login" if this is private content you own.'],
75
+ suggestions: ['Run "playdrop auth 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 login" and retry.'],
128
+ suggestions: ['Run "playdrop auth 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 login" and retry.'],
169
+ suggestions: ['Run "playdrop auth login" and retry.'],
170
170
  };
171
171
  }
172
172
  if (apiError.status === 404) {
@@ -642,7 +642,7 @@ function parseTemplateKey(value) {
642
642
  return null;
643
643
  }
644
644
  const normalizedType = typeSegment.toLowerCase();
645
- if (normalizedType !== 'template') {
645
+ if (normalizedType !== 'template' && normalizedType !== 'app') {
646
646
  return null;
647
647
  }
648
648
  const slug = rest.join('/');
@@ -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 login --env <env>" to save a supported environment before creating apps.'
880
+ 'Run "playdrop auth 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;
@@ -945,8 +945,8 @@ async function create(name, options = {}) {
945
945
  else {
946
946
  const parsed = parseTemplateKey(templateOption);
947
947
  if (!parsed) {
948
- (0, messages_1.printErrorWithHelp)('The --template value must use the {creator}/template/{name} key format.', [
949
- 'Example: playdrop/template/typescript_template.',
948
+ (0, messages_1.printErrorWithHelp)('The --template value must use the {creator}/template/{name} or {creator}/app/{name} key format.', [
949
+ 'Examples: playdrop/template/typescript_template or playdrop/app/typescript_template.',
950
950
  'Use the exact template ref shown in Playdrop.'
951
951
  ], { command: 'project create app' });
952
952
  process.exitCode = 1;
@@ -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 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 auth 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 login" again, then retry.'], { command: 'project create app' });
1240
+ (0, messages_1.printErrorWithHelp)('The API did not return a creator username.', ['Run "playdrop auth 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 login --env <env>" to save a supported environment before retrying.',
118
+ 'Run "playdrop auth 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 whoami" to confirm your session, then retry.'], { command });
68
+ (0, messages_1.printErrorWithHelp)('Could not resolve your current creator account.', ['Run "playdrop auth whoami" to confirm your session, then retry.'], { command });
69
69
  process.exitCode = 1;
70
70
  return null;
71
71
  }
@@ -286,7 +286,7 @@ async function browseCreations(options = {}) {
286
286
  const handled = (0, errors_1.handleCommandFailure)(error, 'creations browse', 'Creation lookup', {
287
287
  apiMessage: (apiError) => ({
288
288
  problem: `Creation lookup failed with status ${apiError.status}.`,
289
- suggestions: ['Run "playdrop login" and retry.'],
289
+ suggestions: ['Run "playdrop auth login" and retry.'],
290
290
  }),
291
291
  });
292
292
  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 login" and retry.'],
37
+ suggestions: ['Run "playdrop auth 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 login" and retry.'],
83
+ suggestions: ['Run "playdrop auth login" and retry.'],
84
84
  }),
85
85
  });
86
86
  if (!handled) {
@@ -6,6 +6,7 @@ const errors_1 = require("../errors");
6
6
  const messages_1 = require("../messages");
7
7
  const output_1 = require("../output");
8
8
  const refs_1 = require("../refs");
9
+ const appUrls_1 = require("../appUrls");
9
10
  function printRefValidationError(raw) {
10
11
  (0, messages_1.printErrorWithHelp)('A canonical ref is required.', [
11
12
  'Use the format <creator>/<kind>/<name>.',
@@ -41,11 +42,15 @@ function extractAssetFileUrls(asset) {
41
42
  .filter((entry) => typeof entry?.role === 'string' && typeof entry?.url === 'string' && entry.url.trim().length > 0)
42
43
  .map((entry) => ({ role: entry.role, url: entry.url.trim() }));
43
44
  }
44
- function decorateApp(app) {
45
+ function decorateApp(app, webBase) {
45
46
  return {
46
47
  ...app,
47
48
  urls: {
48
- play: typeof app.assetUrl === 'string' && app.assetUrl.trim().length > 0 ? app.assetUrl : null,
49
+ play: (0, appUrls_1.buildPlatformPlayUrl)(webBase, {
50
+ creatorUsername: app.creatorUsername,
51
+ appName: app.name,
52
+ appType: app.type,
53
+ }),
49
54
  source: typeof app.sourceUrl === 'string' && app.sourceUrl.trim().length > 0 ? app.sourceUrl : null,
50
55
  },
51
56
  };
@@ -127,7 +132,7 @@ async function detail(rawRef, options = {}) {
127
132
  try {
128
133
  if (ref.kind === 'app') {
129
134
  const response = await client.fetchAppBySlug(ref.creator, ref.name);
130
- const item = decorateApp(response.app);
135
+ const item = decorateApp(response.app, envConfig.webBase);
131
136
  if (options.json) {
132
137
  (0, output_1.printJson)({ kind: ref.kind, ref: ref.ref, item });
133
138
  return;
@@ -165,7 +170,7 @@ async function detail(rawRef, options = {}) {
165
170
  if (apiError.status === 401 || apiError.status === 403) {
166
171
  return {
167
172
  problem: `You do not have access to "${ref.ref}".`,
168
- suggestions: ['Run "playdrop login" if this is private content you own.', 'Use "playdrop help detail" for the ref format.'],
173
+ suggestions: ['Run "playdrop auth login" if this is private content you own.', 'Use "playdrop help detail" for the ref format.'],
169
174
  };
170
175
  }
171
176
  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 login" to refresh your session and ensure the API is reachable.',
124
- 'Use "playdrop whoami" afterwards to confirm your 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.',
125
125
  ], { command: 'project dev' });
126
126
  process.exitCode = 1;
127
127
  return;
@@ -90,13 +90,13 @@ async function sendFeedback(options = {}) {
90
90
  (0, output_1.printJson)({ success: true, response });
91
91
  return;
92
92
  }
93
- (0, output_1.printSuccess)('Feedback sent.', ['Next: run "playdrop feedback browse" if you need to review feedback as an admin.']);
93
+ (0, output_1.printSuccess)('Feedback sent.', ['Next: run "playdrop feedback browse" if you want to review your submitted feedback.']);
94
94
  }
95
95
  catch (error) {
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 login" if your session may be stale.'],
99
+ suggestions: ['Check the provided fields and retry.', 'Run "playdrop auth login" if your session may be stale.'],
100
100
  }),
101
101
  });
102
102
  if (!handled) {
@@ -147,14 +147,21 @@ async function browseFeedback(options = {}) {
147
147
  console.log(` ${item.title}`);
148
148
  console.log(` ${item.comment}`);
149
149
  }
150
- console.log('\nNext: run "playdrop feedback delete <id>" if you need to remove one entry.');
151
150
  }
152
151
  catch (error) {
153
152
  const handled = (0, errors_1.handleCommandFailure)(error, 'feedback browse', 'Feedback browse', {
154
- apiMessage: (apiError) => ({
155
- problem: `Feedback browse failed with status ${apiError.status}.`,
156
- suggestions: ['Ensure you are logged in with an account that can review feedback.'],
157
- }),
153
+ apiMessage: (apiError) => {
154
+ if (apiError.status === 403) {
155
+ return {
156
+ problem: 'You can only browse your own feedback unless you are an admin.',
157
+ suggestions: ['Drop --user-id or use your own user id, then retry.'],
158
+ };
159
+ }
160
+ return {
161
+ problem: `Feedback browse failed with status ${apiError.status}.`,
162
+ suggestions: ['Ensure you are logged in with a valid Playdrop session.'],
163
+ };
164
+ },
158
165
  });
159
166
  if (!handled) {
160
167
  throw error;
@@ -178,10 +185,18 @@ async function deleteFeedbackEntry(rawId, options = {}) {
178
185
  }
179
186
  catch (error) {
180
187
  const handled = (0, errors_1.handleCommandFailure)(error, 'feedback delete', 'Feedback delete', {
181
- apiMessage: (apiError) => ({
182
- problem: `Feedback delete failed with status ${apiError.status}.`,
183
- suggestions: ['Check the id and your permissions, then retry.'],
184
- }),
188
+ apiMessage: (apiError) => {
189
+ if (apiError.status === 403) {
190
+ return {
191
+ problem: 'Only admins can delete feedback.',
192
+ suggestions: [],
193
+ };
194
+ }
195
+ return {
196
+ problem: `Feedback delete failed with status ${apiError.status}.`,
197
+ suggestions: ['Check the id and your permissions, then retry.'],
198
+ };
199
+ },
185
200
  });
186
201
  if (!handled) {
187
202
  throw error;
@@ -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 login" and retry.'],
604
+ suggestions: ['Run "playdrop auth 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 login" and retry.'], { command });
619
+ (0, messages_1.printErrorWithHelp)(`${context} requires you to be logged in.`, ['Run "playdrop auth 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 login');
7
+ console.log(' playdrop auth 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 login" to refresh your credentials.',
58
+ 'Run "playdrop auth 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,8 @@
1
+ import { resetOpenBrowserForTests, setOpenBrowserForTests } from '../browser';
2
+ export { resetOpenBrowserForTests, setOpenBrowserForTests, };
1
3
  type LoginOptions = {
2
4
  username?: string;
3
5
  password?: string;
4
6
  key?: string;
5
7
  };
6
- type OpenBrowserFn = (url: string) => Promise<boolean>;
7
- export declare function setOpenBrowserForTests(fn: OpenBrowserFn): void;
8
- export declare function resetOpenBrowserForTests(): void;
9
8
  export declare function login(env: string, options?: LoginOptions): Promise<void>;
10
- export {};