@buildinternet/uploads 0.42.2 → 0.43.0

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/dist/cli-help.js CHANGED
@@ -131,7 +131,7 @@ ${section(style, "Config")} ${style.muted("(first match wins, per key):")}
131
131
  ${section(style, "Workspace")} ${style.muted("(within config layers):")}
132
132
  --workspace, -w override — global (before command) or per-command (after)
133
133
  UPLOADS_WORKSPACE env / config file
134
- (else inferred from token up_<name>_…, else "default")
134
+ (else inferred from token up_<name>_…, or the API key's only workspace)
135
135
 
136
136
  ${section(style, "Other globals")} ${style.muted("(before command):")}
137
137
  --api-url <url> default: https://api.uploads.sh
package/dist/client.d.ts CHANGED
@@ -565,7 +565,7 @@ export declare function mintWorkspaceToken(apiUrl: string, accessToken: string,
565
565
  workspace: string;
566
566
  scopes?: Array<TokenScope>;
567
567
  label?: string;
568
- ttlSeconds?: number;
568
+ ttlSeconds?: number | null;
569
569
  }): Promise<MintTokenResult>;
570
570
  /**
571
571
  * Parse API error bodies. Prefers the nested envelope
package/dist/client.js CHANGED
@@ -179,7 +179,7 @@ export function mintWorkspaceToken(apiUrl, accessToken, input) {
179
179
  body: JSON.stringify({
180
180
  grants: [{ workspace: input.workspace, ...(input.scopes ? { scopes: input.scopes } : {}) }],
181
181
  ...(input.label ? { label: input.label } : {}),
182
- ...(input.ttlSeconds ? { ttlSeconds: input.ttlSeconds } : {}),
182
+ ...(input.ttlSeconds !== undefined ? { ttlSeconds: input.ttlSeconds } : {}),
183
183
  }),
184
184
  });
185
185
  }
@@ -18,7 +18,7 @@ Subcommands:
18
18
  Keys:
19
19
  UPLOADS_API_URL API base URL (default: ${DEFAULT_API_URL})
20
20
  UPLOADS_WORKSPACE Workspace / bucket tenant (default: ${DEFAULT_WORKSPACE})
21
- UPLOADS_TOKEN Bearer token for the workspace
21
+ UPLOADS_TOKEN Bearer token (up_<workspace>_…)
22
22
  UPLOADS_SESSION_TOKEN Device-flow session (CLI version on account sessions)
23
23
  UPLOADS_DEFAULT_PREFIX Default key prefix for put/list
24
24
  UPLOADS_DEFAULT_REPO Default repo segment for put
@@ -8,6 +8,7 @@ import { loadDefaultsRaw, resolveScreenshotDefaults } from "../config-file.js";
8
8
  import { resolvePutPrefix } from "../destinations.js";
9
9
  import { execRunner, ghMetadataFromTargetWithTitle, resolveRepo, } from "../github-gh.js";
10
10
  import { deriveRepoSlugFromGit } from "../keys.js";
11
+ import { noProjectContextNudge } from "../project-context-nudge.js";
11
12
  import { safeCaptureFacts } from "../capture-facts.js";
12
13
  import { parseMetaFlags, validateMetaMap } from "../metadata.js";
13
14
  import { mergeDerivedMeta } from "../metadata-vocab.js";
@@ -388,6 +389,9 @@ loadAnnotateModule = () => import("../annotate/index.js")) {
388
389
  else if (Object.keys(withFacts).length > 0) {
389
390
  validateMetaMap(withFacts);
390
391
  }
392
+ // #692 follow-up: same advisory as put — a capture with no repo/app context
393
+ // and only a local origin lands in the screenshots page's fallback buckets.
394
+ const contextNudge = !ctx.quiet && !putDefaults.noNudge && !noGit ? noProjectContextNudge(metadata) : undefined;
391
395
  const logHuman = !ctx.quiet && format === "human";
392
396
  if (logHuman)
393
397
  process.stderr.write(`>> capturing ${target}\n`);
@@ -562,6 +566,8 @@ loadAnnotateModule = () => import("../annotate/index.js")) {
562
566
  }
563
567
  if (bindingWarning)
564
568
  process.stderr.write(`${bindingWarning}\n`);
569
+ if (contextNudge)
570
+ process.stderr.write(`${contextNudge}\n`);
565
571
  process.stderr.write("\n");
566
572
  }
567
573
  // One JSON `hint` slot (mirrors bare put): the clip note (issue #652) wins
@@ -576,7 +582,7 @@ loadAnnotateModule = () => import("../annotate/index.js")) {
576
582
  const replacedHint = result.replaced && explicitMeta.state
577
583
  ? `re-capture replaced the previous state=${explicitMeta.state} object at ${result.key} — expected for repeat captures of the same URL + state`
578
584
  : undefined;
579
- const jsonHint = clipHint ?? bindingWarning ?? stagingNote ?? replacedHint;
585
+ const jsonHint = clipHint ?? bindingWarning ?? stagingNote ?? replacedHint ?? contextNudge;
580
586
  switch (format) {
581
587
  case "json":
582
588
  await writeJson({
package/dist/commands.js CHANGED
@@ -16,6 +16,7 @@ import { mergeSidecarMeta } from "./sidecar.js";
16
16
  import { ghAttachmentKeyForMode, ghBranchAttachmentKeyForMode, ghBranchKeyPrefix, ghKeyPrefix, ghPrivateKeyPrefix, ghPrivateBranchKeyPrefix, ghMetadataFromTarget, parseGhKey, parseGhPrivateKey, ghMetadataForBranch, attachmentsCommentBody, attachmentsMarker, AUTO_RENDER_OPTIONS, GH_FALLBACK_AUTHOR_NOTE, normalizeGithubCoordinate, } from "./github.js";
17
17
  import { resolveRepo, resolveCurrentPullRequest, resolveCurrentBranch, resolveDefaultBranch, classifyGhNumber, execRunner, timedExecRunner, ghMetadataFromTargetWithTitle, upsertAttachmentsComment, } from "./github-gh.js";
18
18
  import { deriveRepoFromGit, deriveRepoSlugFromGit } from "./keys.js";
19
+ import { noProjectContextNudge } from "./project-context-nudge.js";
19
20
  import { resolvePutPrefix } from "./destinations.js";
20
21
  import { optimizeImageForUpload, rewriteKeyExtension, } from "./optimize.js";
21
22
  import { applyFrame, resolveFrameId } from "./frame.js";
@@ -1812,6 +1813,11 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
1812
1813
  if (slug)
1813
1814
  metadata = mergeDerivedMeta(metadata, { repo: slug });
1814
1815
  }
1816
+ // #692 follow-up: a path-tagged upload with no repo/gh.repo/app context and
1817
+ // no real (non-local) origin lands in the screenshots page's fallback
1818
+ // buckets — one advisory line at the moment the context went missing.
1819
+ // --no-git is an explicit choice, so it suppresses the nudge too.
1820
+ const contextNudge = !ctx.quiet && !defaults.noNudge && !noGit ? noProjectContextNudge(metadata) : undefined;
1815
1821
  // Bare-put nudge (issue #393): only relevant when staging didn't take over
1816
1822
  // — once `stagingTarget` resolves, staging IS the upgrade the nudge used to
1817
1823
  // point at, so this is skipped entirely rather than firing redundantly.
@@ -1894,7 +1900,7 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
1894
1900
  // warning); stderr prints the nudge/staging-note and binding-warning lines
1895
1901
  // independently, below. pathHint only ever fires on the ghTarget path, so
1896
1902
  // it never competes with the other three.
1897
- const jsonHint = nudge ?? bindingWarning ?? stagingNote ?? pathHint;
1903
+ const jsonHint = nudge ?? bindingWarning ?? stagingNote ?? pathHint ?? contextNudge;
1898
1904
  const galleriesByKey = new Map();
1899
1905
  let galleryHadError = false;
1900
1906
  if (galleryId && uploads.length > 0) {
@@ -1979,6 +1985,8 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
1979
1985
  process.stderr.write(`${bindingWarning}\n`);
1980
1986
  if (pathHint)
1981
1987
  process.stderr.write(`${pathHint}\n`);
1988
+ if (contextNudge)
1989
+ process.stderr.write(`${contextNudge}\n`);
1982
1990
  }
1983
1991
  return failures.length === 0 && !galleryHadError ? 0 : 1;
1984
1992
  }
@@ -2040,6 +2048,8 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
2040
2048
  process.stderr.write(`${bindingWarning}\n`);
2041
2049
  if (pathHint && format !== "json")
2042
2050
  process.stderr.write(`${pathHint}\n`);
2051
+ if (contextNudge && format !== "json")
2052
+ process.stderr.write(`${contextNudge}\n`);
2043
2053
  return gallery?.error ? 1 : 0;
2044
2054
  }
2045
2055
  // --- galleries ---
@@ -2726,7 +2736,9 @@ Scans the PR/issue description and comments for github.com/user-attachments
2726
2736
  media, mirrors new ones into the workspace (indexed, not added to the managed
2727
2737
  comment), and detaches ones no longer referenced. Works on any repo linked to
2728
2738
  the workspace; the .uploads.yml ingestGithubAttachments knob only gates the
2729
- automatic webhook path.
2739
+ automatic webhook path. Bot-authored attachments and images under 200px on
2740
+ either side are always skipped (the .uploads.yml ingestBotAttachments knob
2741
+ re-admits bot media on the webhook path only).
2730
2742
 
2731
2743
  Examples:
2732
2744
  uploads ingest --pr 123
@@ -12,6 +12,7 @@ export interface RepoCommentConfig {
12
12
  linkToFilePage?: boolean;
13
13
  note?: string;
14
14
  ingestGithubAttachments?: boolean;
15
+ ingestBotAttachments?: boolean;
15
16
  }
16
17
  export interface WorkspaceCommentDefaults {
17
18
  imageWidth?: "full" | number;
@@ -20,6 +21,7 @@ export interface WorkspaceCommentDefaults {
20
21
  linkToFilePage?: boolean;
21
22
  note?: string;
22
23
  ingestGithubAttachments?: boolean;
24
+ ingestBotAttachments?: boolean;
23
25
  }
24
26
  export interface ResolvedCommentOptions {
25
27
  imageWidth: "auto" | "full" | number;
@@ -29,6 +31,7 @@ export interface ResolvedCommentOptions {
29
31
  linkToFilePage: boolean;
30
32
  note: string | null;
31
33
  ingestGithubAttachments: boolean;
34
+ ingestBotAttachments: boolean;
32
35
  }
33
36
  export type OptionSource = "repo" | "workspace" | "auto";
34
37
  export declare const AUTO_COMMENT_OPTIONS: ResolvedCommentOptions;
@@ -12,7 +12,8 @@ export const AUTO_COMMENT_OPTIONS = {
12
12
  metaState: true,
13
13
  linkToFilePage: true,
14
14
  note: null,
15
- ingestGithubAttachments: false,
15
+ ingestGithubAttachments: true,
16
+ ingestBotAttachments: false,
16
17
  };
17
18
  export const NOTE_MAX_CHARS = 500;
18
19
  const WIDTH_MIN = 160;
@@ -72,6 +73,14 @@ export function parseRepoCommentConfig(text, format) {
72
73
  else
73
74
  warnings.push(`ingestGithubAttachments: expected a boolean; dropped`);
74
75
  }
76
+ // ingestBotAttachments: boolean
77
+ if ("ingestBotAttachments" in c) {
78
+ const v = c.ingestBotAttachments;
79
+ if (typeof v === "boolean")
80
+ config.ingestBotAttachments = v;
81
+ else
82
+ warnings.push(`ingestBotAttachments: expected a boolean; dropped`);
83
+ }
75
84
  // meta.path / meta.state: booleans nested under `meta`
76
85
  if ("meta" in c) {
77
86
  const v = c.meta;
@@ -127,6 +136,9 @@ export function resolveCommentOptions(repo, ws) {
127
136
  ...(ws?.ingestGithubAttachments !== undefined
128
137
  ? { ingestGithubAttachments: ws.ingestGithubAttachments }
129
138
  : {}),
139
+ ...(ws?.ingestBotAttachments !== undefined
140
+ ? { ingestBotAttachments: ws.ingestBotAttachments }
141
+ : {}),
130
142
  };
131
143
  const options = { ...AUTO_COMMENT_OPTIONS };
132
144
  const source = Object.fromEntries(Object.keys(AUTO_COMMENT_OPTIONS).map((k) => [k, "auto"]));
@@ -138,6 +150,7 @@ export function resolveCommentOptions(repo, ws) {
138
150
  "metaState",
139
151
  "linkToFilePage",
140
152
  "ingestGithubAttachments",
153
+ "ingestBotAttachments",
141
154
  ]) {
142
155
  if (cfg[key] !== undefined && source[key] === "auto") {
143
156
  options[key] = cfg[key];
package/dist/mcp/tools.js CHANGED
@@ -108,7 +108,7 @@ export function createUploadsMcpTools(opts) {
108
108
  const { globals } = opts;
109
109
  const run = opts.runner ?? execRunner;
110
110
  const clientFactory = opts.clientFactory ?? createUploadsClient;
111
- function clientFor(args, requireToken = true) {
111
+ async function clientFor(args, requireToken = true) {
112
112
  const config = resolveConfig({
113
113
  apiUrl: globals.apiUrl,
114
114
  token: globals.token,
@@ -151,7 +151,7 @@ export function createUploadsMcpTools(opts) {
151
151
  const title = optString(args, "title");
152
152
  if (!title)
153
153
  usage("title is required");
154
- const { client } = clientFor(args);
154
+ const { client } = await clientFor(args);
155
155
  return client.createGallery({ title, description: optString(args, "description") });
156
156
  },
157
157
  },
@@ -171,7 +171,7 @@ export function createUploadsMcpTools(opts) {
171
171
  additionalProperties: false,
172
172
  },
173
173
  async handler(args) {
174
- const { client } = clientFor(args);
174
+ const { client } = await clientFor(args);
175
175
  return client.getGallery(galleryId(args));
176
176
  },
177
177
  },
@@ -197,7 +197,7 @@ export function createUploadsMcpTools(opts) {
197
197
  const objectKey = optString(args, "objectKey");
198
198
  if (!objectKey)
199
199
  usage("objectKey is required");
200
- const { client } = clientFor(args);
200
+ const { client } = await clientFor(args);
201
201
  const id = galleryId(args);
202
202
  const current = await client.getGallery(id);
203
203
  return client.addGalleryItem(id, objectKey, {
@@ -228,7 +228,7 @@ export function createUploadsMcpTools(opts) {
228
228
  additionalProperties: false,
229
229
  },
230
230
  async handler(args) {
231
- const { client } = clientFor(args);
231
+ const { client } = await clientFor(args);
232
232
  const id = galleryId(args);
233
233
  const current = await client.getGallery(id);
234
234
  return client.linkGalleryExternalReference(id, {
@@ -259,7 +259,7 @@ export function createUploadsMcpTools(opts) {
259
259
  additionalProperties: false,
260
260
  },
261
261
  async handler(args) {
262
- const { client } = clientFor(args);
262
+ const { client } = await clientFor(args);
263
263
  return client.findGalleriesByReference({
264
264
  ...galleryReference(args),
265
265
  limit: optPosInt(args, "limit"),
@@ -426,7 +426,7 @@ export function createUploadsMcpTools(opts) {
426
426
  catch (err) {
427
427
  usage(err instanceof Error ? err.message : String(err));
428
428
  }
429
- const { config, client } = clientFor(args);
429
+ const { config, client } = await clientFor(args);
430
430
  const defaults = resolvePutDefaults({ envFile: globals.envFile });
431
431
  const frameOpts = mcpFrameOptions(args);
432
432
  const optimizeOpts = mcpOptimizeOptions(args, defaults);
@@ -736,7 +736,7 @@ export function createUploadsMcpTools(opts) {
736
736
  const metadata = metadataArgWithCanonical(args);
737
737
  if (metadata)
738
738
  validateMetaMap(metadata);
739
- const { config, client } = clientFor(args);
739
+ const { config, client } = await clientFor(args);
740
740
  const defaults = resolvePutDefaults({ envFile: globals.envFile });
741
741
  const frameOpts = mcpFrameOptions(args);
742
742
  const optimizeOpts = mcpOptimizeOptions(args, defaults);
@@ -969,7 +969,7 @@ export function createUploadsMcpTools(opts) {
969
969
  const explicitTarget = ghTargetFromArgs(args, run);
970
970
  const target = explicitTarget ??
971
971
  resolveCurrentPullRequest(resolveRepo(optString(args, "repo"), run), run);
972
- const { config, client } = clientFor(args);
972
+ const { config, client } = await clientFor(args);
973
973
  const contentType = optString(args, "contentType");
974
974
  const defaults = resolvePutDefaults({ envFile: globals.envFile });
975
975
  const frameOpts = mcpFrameOptions(args);
@@ -1037,7 +1037,7 @@ export function createUploadsMcpTools(opts) {
1037
1037
  const prefixArg = optString(args, "prefix");
1038
1038
  let prefix = prefixArg ?? (defaults.prefix ? `${defaults.prefix}/` : undefined);
1039
1039
  const target = ghTargetFromArgs(args, run);
1040
- const { client } = clientFor(args);
1040
+ const { client } = await clientFor(args);
1041
1041
  // Also list every active private prefix, if any (issue #631) —
1042
1042
  // mirrors syncAttachmentsComment's gh-fallback gather: a repo's
1043
1043
  // attachment history can be split across the plain shape and
@@ -1093,7 +1093,7 @@ export function createUploadsMcpTools(opts) {
1093
1093
  additionalProperties: false,
1094
1094
  },
1095
1095
  async handler(args) {
1096
- const { client } = clientFor(args);
1096
+ const { client } = await clientFor(args);
1097
1097
  const repo = resolveRepo(optString(args, "repo"), run);
1098
1098
  const branch = optString(args, "branch") ?? resolveCurrentBranch(run);
1099
1099
  return resolveStaged({ client, repo, branch });
@@ -1124,7 +1124,7 @@ export function createUploadsMcpTools(opts) {
1124
1124
  usage("key is required");
1125
1125
  if (optBool(args, "dryRun"))
1126
1126
  return { key, deleted: false, dryRun: true };
1127
- const { client } = clientFor(args);
1127
+ const { client } = await clientFor(args);
1128
1128
  return client.delete(key);
1129
1129
  },
1130
1130
  },
@@ -1147,7 +1147,7 @@ export function createUploadsMcpTools(opts) {
1147
1147
  const key = optString(args, "key");
1148
1148
  if (!key)
1149
1149
  usage("key is required");
1150
- return clientFor(args).client.getMetadata(key);
1150
+ return (await clientFor(args)).client.getMetadata(key);
1151
1151
  },
1152
1152
  },
1153
1153
  {
@@ -1184,7 +1184,7 @@ export function createUploadsMcpTools(opts) {
1184
1184
  }
1185
1185
  if (set)
1186
1186
  validateMetaMap(set);
1187
- const { client } = clientFor(args);
1187
+ const { client } = await clientFor(args);
1188
1188
  return client.patchMetadata(key, { set, delete: del });
1189
1189
  },
1190
1190
  },
@@ -1223,7 +1223,7 @@ export function createUploadsMcpTools(opts) {
1223
1223
  }
1224
1224
  if (hasMeta)
1225
1225
  validateMetaMap(filters);
1226
- const { client } = clientFor(args);
1226
+ const { client } = await clientFor(args);
1227
1227
  return client.findFiles(filters, {
1228
1228
  name,
1229
1229
  prefix: optString(args, "prefix"),
@@ -1249,7 +1249,7 @@ export function createUploadsMcpTools(opts) {
1249
1249
  additionalProperties: false,
1250
1250
  },
1251
1251
  async handler(args) {
1252
- const { client } = clientFor(args);
1252
+ const { client } = await clientFor(args);
1253
1253
  const key = optString(args, "key");
1254
1254
  return key ? client.listMetadataValues(key) : client.listMetadataKeys();
1255
1255
  },
@@ -1266,7 +1266,7 @@ export function createUploadsMcpTools(opts) {
1266
1266
  additionalProperties: false,
1267
1267
  },
1268
1268
  async handler(args) {
1269
- const { client } = clientFor(args);
1269
+ const { client } = await clientFor(args);
1270
1270
  return client.usage();
1271
1271
  },
1272
1272
  },
@@ -1282,7 +1282,7 @@ export function createUploadsMcpTools(opts) {
1282
1282
  additionalProperties: false,
1283
1283
  },
1284
1284
  async handler(args) {
1285
- const { client } = clientFor(args);
1285
+ const { client } = await clientFor(args);
1286
1286
  return client.reconcile();
1287
1287
  },
1288
1288
  },
@@ -1298,7 +1298,7 @@ export function createUploadsMcpTools(opts) {
1298
1298
  additionalProperties: false,
1299
1299
  },
1300
1300
  async handler(args) {
1301
- const { client } = clientFor(args);
1301
+ const { client } = await clientFor(args);
1302
1302
  return client.purgeExpired();
1303
1303
  },
1304
1304
  },
@@ -1320,7 +1320,7 @@ export function createUploadsMcpTools(opts) {
1320
1320
  const target = ghTargetFromArgs(args, run);
1321
1321
  if (!target)
1322
1322
  usage("comment requires pr or issue");
1323
- const { config, client } = clientFor(args);
1323
+ const { config, client } = await clientFor(args);
1324
1324
  // Explicit resync, same as `uploads comment` (issue #480).
1325
1325
  const result = await syncAttachmentsComment(client, target, run, config.workspace, {
1326
1326
  resync: true,
@@ -1336,7 +1336,7 @@ export function createUploadsMcpTools(opts) {
1336
1336
  description: "Check uploads.sh API liveness. No auth or arguments required.",
1337
1337
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
1338
1338
  async handler(args) {
1339
- const { config, client } = clientFor(args, false);
1339
+ const { config, client } = await clientFor(args, false);
1340
1340
  const result = await client.health();
1341
1341
  return { ...result, apiUrl: config.apiUrl };
1342
1342
  },
@@ -1353,7 +1353,7 @@ export function createUploadsMcpTools(opts) {
1353
1353
  additionalProperties: false,
1354
1354
  },
1355
1355
  async handler(args) {
1356
- const { config, client } = clientFor(args);
1356
+ const { config, client } = await clientFor(args);
1357
1357
  return buildDoctorReport(config, client);
1358
1358
  },
1359
1359
  },
@@ -0,0 +1,9 @@
1
+ /**
2
+ * No-project-context nudge (issue #692 follow-up): a path-tagged upload with
3
+ * no repo/gh.repo/app metadata and no real (non-local) origin lands in the
4
+ * screenshots page's "local dev" / "Other" fallback buckets. One advisory
5
+ * stderr line teaches the fix at the moment the context went missing. The
6
+ * predicate mirrors apps/api's projectLabelFromMeta fallback rules exactly —
7
+ * a real URL host is a meaningful group, so it never fires there.
8
+ */
9
+ export declare function noProjectContextNudge(meta: Record<string, string> | undefined): string | undefined;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * No-project-context nudge (issue #692 follow-up): a path-tagged upload with
3
+ * no repo/gh.repo/app metadata and no real (non-local) origin lands in the
4
+ * screenshots page's "local dev" / "Other" fallback buckets. One advisory
5
+ * stderr line teaches the fix at the moment the context went missing. The
6
+ * predicate mirrors apps/api's projectLabelFromMeta fallback rules exactly —
7
+ * a real URL host is a meaningful group, so it never fires there.
8
+ */
9
+ /** Hosts that identify a dev machine, not an app — same set as the API's
10
+ * isLocalHostname (apps/api/src/file-metadata.ts). */
11
+ function isLocalHostname(hostname) {
12
+ const bare = hostname.toLowerCase();
13
+ return (bare === "localhost" ||
14
+ bare.endsWith(".localhost") ||
15
+ bare === "127.0.0.1" ||
16
+ bare === "0.0.0.0" ||
17
+ bare === "[::1]");
18
+ }
19
+ export function noProjectContextNudge(meta) {
20
+ // Only `path`-tagged uploads appear on the screenshots page at all.
21
+ if (!meta?.path)
22
+ return undefined;
23
+ if (meta.repo || meta["gh.repo"] || meta.app)
24
+ return undefined;
25
+ let bucket = "Other";
26
+ if (meta.url) {
27
+ try {
28
+ const parsed = new URL(meta.url);
29
+ if (!isLocalHostname(parsed.hostname))
30
+ return undefined; // real host = real group
31
+ bucket = "local dev";
32
+ }
33
+ catch {
34
+ // unparseable url is just "no url"
35
+ }
36
+ }
37
+ return (`note: no repo detected — the screenshots page will group this under "${bucket}". ` +
38
+ `Run from inside your project repo, or pass --app <name>.`);
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.42.2",
3
+ "version": "0.43.0",
4
4
  "description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
5
5
  "type": "module",
6
6
  "sideEffects": false,