@reshotdev/screenshot 0.0.1-beta.14 → 0.0.1-beta.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reshotdev/screenshot",
3
- "version": "0.0.1-beta.14",
3
+ "version": "0.0.1-beta.16",
4
4
  "description": "Screenshot and video capture CLI",
5
5
  "author": "Reshot <hello@reshot.dev>",
6
6
  "license": "MIT",
@@ -150,11 +150,12 @@ async function waitForCompletion(
150
150
  }
151
151
 
152
152
  async function verifyApiKey(apiBaseUrl, apiKey, httpClient = axios) {
153
- await httpClient.get(`${apiBaseUrl}/auth/cli/verify`, {
153
+ const response = await httpClient.get(`${apiBaseUrl}/auth/cli/verify`, {
154
154
  headers: {
155
155
  Authorization: `Bearer ${apiKey}`,
156
156
  },
157
157
  });
158
+ return response?.data || null;
158
159
  }
159
160
 
160
161
  async function authCommand(options = {}) {
@@ -172,19 +173,33 @@ async function authCommand(options = {}) {
172
173
  const timeoutMs = Number(options.timeoutMs || DEFAULT_AUTH_TIMEOUT_MS);
173
174
  if (envApiKey && envProjectId) {
174
175
  const platformUrl = process.env.RESHOT_PLATFORM_URL || "https://reshot.dev";
176
+ // Best-effort resolve the project name so settings + the setup report
177
+ // don't record `projectName: null` on this non-interactive path.
178
+ let projectName = null;
179
+ try {
180
+ const apiBaseUrl = options.apiBaseUrl || getApiBaseUrl();
181
+ const verified = await verifyApiKeyFn(apiBaseUrl, envApiKey, httpClient);
182
+ projectName = verified?.project?.name || null;
183
+ } catch {
184
+ // Verification is best-effort here; keep going without the name.
185
+ }
175
186
  writeSettingsFn({
176
187
  projectId: envProjectId,
188
+ projectName,
177
189
  apiKey: envApiKey,
178
190
  platformUrl,
179
191
  linkedAt: new Date().toISOString(),
180
192
  cliVersion: pkg.version,
181
193
  });
182
194
  console.log(chalk.green("✔ Authenticated via environment variables"));
183
- console.log(chalk.gray(` Project: ${envProjectId}`));
195
+ console.log(
196
+ chalk.gray(` Project: ${projectName || envProjectId}`),
197
+ );
184
198
  console.log(chalk.gray(` Platform: ${platformUrl}`));
185
199
  return {
186
200
  mode: "cloud-connected",
187
201
  projectId: envProjectId,
202
+ projectName,
188
203
  platformUrl,
189
204
  };
190
205
  }
@@ -629,6 +629,24 @@ function buildPublishMetadata({
629
629
  };
630
630
  }
631
631
 
632
+ /**
633
+ * Read pixel dimensions from a captured image so the platform can store and
634
+ * expose them (used by `reshot pull` for CLS-safe `<img width height>`
635
+ * embedding). Returns nulls for non-images or on any error — never throws.
636
+ */
637
+ async function getImageDimensions(filePath, contentType) {
638
+ if (!filePath || !(contentType || "").startsWith("image/")) {
639
+ return { width: null, height: null };
640
+ }
641
+ try {
642
+ const sharp = require("sharp");
643
+ const meta = await sharp(filePath).metadata();
644
+ return { width: meta.width ?? null, height: meta.height ?? null };
645
+ } catch {
646
+ return { width: null, height: null };
647
+ }
648
+ }
649
+
632
650
  /**
633
651
  * Publish using transactional flow (direct R2 upload with presigned URLs)
634
652
  */
@@ -915,12 +933,18 @@ async function publishWithTransactionalFlow(
915
933
  assets: [],
916
934
  });
917
935
  }
936
+ const dimensions = await getImageDimensions(
937
+ result.file.path,
938
+ result.file.contentType,
939
+ );
918
940
  groupMap.get(groupKey).assets.push({
919
941
  key: result.file.key,
920
942
  s3Path: result.s3Path,
921
943
  hash: result.file.hash,
922
944
  visualKey: result.file.visualKey,
923
945
  size: result.file.size,
946
+ width: dimensions.width,
947
+ height: dimensions.height,
924
948
  contentType: result.file.contentType,
925
949
  // Include diff data from CLI analysis
926
950
  diffPercentage: result.file.diffData?.diffPercentage ?? null,
@@ -1014,7 +1038,15 @@ async function publishWithTransactionalFlow(
1014
1038
  viewUrl = batchResult.viewUrl;
1015
1039
  }
1016
1040
  } catch (error) {
1017
- console.log(chalk.red(` ✖ Batch request failed: ${error.message}`));
1041
+ // Surface the server's descriptive error body (e.g. a validation
1042
+ // message) instead of just axios's opaque "Request failed with status
1043
+ // code 400" — otherwise a 1-line fix turns into a support ticket.
1044
+ const serverMsg =
1045
+ error.response?.data?.error || error.response?.data?.message;
1046
+ const detail = serverMsg
1047
+ ? `${error.message} — ${serverMsg}`
1048
+ : error.message;
1049
+ console.log(chalk.red(` ✖ Batch request failed: ${detail}`));
1018
1050
  failCount += chunk.length;
1019
1051
  }
1020
1052
  }
@@ -109,6 +109,23 @@ function normalizeAssetMap(assets) {
109
109
  return { assets: normalized, repairs };
110
110
  }
111
111
 
112
+ /**
113
+ * Pick a real {group, visualKey, context} from a normalized asset map so the
114
+ * printed usage example is copy-pasteable for THIS project (prefers the
115
+ * "default" context). Returns null if the map is empty.
116
+ */
117
+ function deriveUsageSample(assets) {
118
+ for (const group of Object.keys(assets || {})) {
119
+ for (const visualKey of Object.keys(assets[group] || {})) {
120
+ const contexts = Object.keys(assets[group][visualKey] || {});
121
+ if (contexts.length === 0) continue;
122
+ const context = contexts.includes("default") ? "default" : contexts[0];
123
+ return { group, visualKey, context };
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+
112
129
  function validateHostedAssetUrl(urlString) {
113
130
  if (!urlString || typeof urlString !== "string") {
114
131
  return;
@@ -456,6 +473,13 @@ async function pullCommand(options = {}) {
456
473
  console.log(chalk.green(`✓ Generated ${contentType} file: ${outputPath}`));
457
474
  console.log(chalk.gray(` ${assetCount} visuals exported\n`));
458
475
 
476
+ // Derive a REAL key path from the pulled map so the printed usage example
477
+ // is copy-pasteable (was hardcoded to a non-existent `dashboard.mainView`).
478
+ const sample = deriveUsageSample(normalizedAssets);
479
+ const sGroup = sample?.group ?? "dashboard";
480
+ const sVisual = sample?.visualKey ?? "mainView";
481
+ const sCtx = sample?.context ?? "default";
482
+
459
483
  // Show format-specific usage instructions
460
484
  if (format === "ts") {
461
485
  console.log(chalk.blue("━━━ TypeScript Usage ━━━\n"));
@@ -463,10 +487,9 @@ async function pullCommand(options = {}) {
463
487
  console.log(chalk.cyan(' import { ReshotVisuals } from "./visuals";\n'));
464
488
  console.log(chalk.white("Use with full autocomplete support:\n"));
465
489
  console.log(chalk.cyan(" // Single context (flattened)"));
466
- console.log(chalk.cyan(" <img src={ReshotVisuals.dashboard.mainView} />\n"));
490
+ console.log(chalk.cyan(` <img src={ReshotVisuals.${sGroup}.${sVisual}} />\n`));
467
491
  console.log(chalk.cyan(" // Multiple contexts (dark mode, locales)"));
468
- console.log(chalk.cyan(" <img src={ReshotVisuals.dashboard.mainView.default} />"));
469
- console.log(chalk.cyan(" <img src={ReshotVisuals.dashboard.mainView.dark} />\n"));
492
+ console.log(chalk.cyan(` <img src={ReshotVisuals.${sGroup}.${sVisual}.${sCtx}} />\n`));
470
493
  if (!full) {
471
494
  console.log(chalk.gray(" Tip: Use --full flag to include width, height, and alt text\n"));
472
495
  }
@@ -476,10 +499,10 @@ async function pullCommand(options = {}) {
476
499
  console.log(chalk.cyan(' import assets from "./reshot-assets.json";\n'));
477
500
  console.log(chalk.white("Access assets with dot notation:\n"));
478
501
  console.log(chalk.cyan(" <img"));
479
- console.log(chalk.cyan(" src={assets.assets.dashboard.mainView.default.src}"));
480
- console.log(chalk.cyan(" width={assets.assets.dashboard.mainView.default.width}"));
481
- console.log(chalk.cyan(" height={assets.assets.dashboard.mainView.default.height}"));
482
- console.log(chalk.cyan(" alt={assets.assets.dashboard.mainView.default.alt}"));
502
+ console.log(chalk.cyan(` src={assets.assets.${sGroup}.${sVisual}.${sCtx}.src}`));
503
+ console.log(chalk.cyan(` width={assets.assets.${sGroup}.${sVisual}.${sCtx}.width}`));
504
+ console.log(chalk.cyan(` height={assets.assets.${sGroup}.${sVisual}.${sCtx}.height}`));
505
+ console.log(chalk.cyan(` alt={assets.assets.${sGroup}.${sVisual}.${sCtx}.alt}`));
483
506
  console.log(chalk.cyan(" />\n"));
484
507
  } else if (format === "csv") {
485
508
  console.log(chalk.blue("━━━ CSV Usage ━━━\n"));