@buildinternet/uploads 0.3.0 → 0.4.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @buildinternet/uploads
2
2
 
3
- CLI and client for **uploads.sh** — upload files, get public URLs, and produce GitHub-ready markdown. Successor to the R2 scripts in `buildinternet-skills/github-screenshots`.
3
+ CLI and client for **uploads.sh** — upload files, get public URLs, and produce GitHub-ready markdown.
4
4
 
5
5
  ## CLI
6
6
 
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ Config (first match wins, per key):
19
19
  environment UPLOADS_API_URL, UPLOADS_TOKEN, UPLOADS_WORKSPACE
20
20
  --env-file <path>
21
21
  $BUILDINTERNET_CONFIG
22
- ~/.config/buildinternet/config (shared with github-screenshots)
22
+ ~/.config/buildinternet/config
23
23
 
24
24
  Workspace (within config layers):
25
25
  --workspace, -w override — global (before command) or per-command (after)
package/dist/client.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import type { UploadsClientConfig } from "./config.js";
2
+ /** Allowlisted object provenance (maps to X-Uploads-Meta-* on put). */
3
+ export type ProvenanceInput = {
4
+ client?: string;
5
+ "client-version"?: string;
6
+ "source-name"?: string;
7
+ optimized?: "0" | "1";
8
+ frame?: string;
9
+ "keep-exif"?: "0" | "1";
10
+ };
2
11
  export interface PutOptions {
3
12
  key?: string;
4
13
  contentType?: string;
@@ -6,6 +15,8 @@ export interface PutOptions {
6
15
  repo?: string;
7
16
  ref?: string;
8
17
  deriveRepoFromGit?: boolean;
18
+ /** Stored as R2 custom metadata; echoed on put/head. */
19
+ provenance?: ProvenanceInput;
9
20
  }
10
21
  export interface ListOptions {
11
22
  prefix?: string;
@@ -18,6 +29,7 @@ export interface PutResult {
18
29
  url: string;
19
30
  size: number;
20
31
  contentType: string;
32
+ metadata?: Record<string, string>;
21
33
  }
22
34
  export interface ListItem {
23
35
  key: string;
@@ -35,6 +47,7 @@ export interface HeadResult {
35
47
  size: number;
36
48
  contentType: string;
37
49
  uploaded?: string;
50
+ metadata?: Record<string, string>;
38
51
  }
39
52
  export interface DeleteResult {
40
53
  key: string;
package/dist/client.js CHANGED
@@ -117,9 +117,16 @@ export function createUploadsClient(config) {
117
117
  deriveRepoFromGit: opts.deriveRepoFromGit,
118
118
  }));
119
119
  const contentType = opts.contentType ?? inferContentType(opts.filename);
120
+ const headers = { "Content-Type": contentType };
121
+ if (opts.provenance) {
122
+ for (const [k, v] of Object.entries(opts.provenance)) {
123
+ if (v !== undefined && v !== "")
124
+ headers[`X-Uploads-Meta-${k}`] = v;
125
+ }
126
+ }
120
127
  const result = await request("PUT", `${filesBase(config)}/${encodeKeyPath(key)}`, {
121
128
  body,
122
- headers: { "Content-Type": contentType },
129
+ headers,
123
130
  });
124
131
  if (result.url == null) {
125
132
  throw new UploadsError("upload succeeded but workspace has no publicBaseUrl", "NO_PUBLIC_URL", 201);
package/dist/commands.js CHANGED
@@ -11,6 +11,7 @@ import { resolveRepo, resolveCurrentPullRequest, execRunner, upsertAttachmentsCo
11
11
  import { resolvePutPrefix } from "./destinations.js";
12
12
  import { optimizeImageForUpload, rewriteKeyExtension, } from "./optimize.js";
13
13
  import { applyFrame, resolveFrameId } from "./frame.js";
14
+ import { buildCliProvenance } from "./provenance.js";
14
15
  // --- put ---
15
16
  const PUT_HELP = `uploads put <file> [options]
16
17
 
@@ -230,6 +231,12 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
230
231
  filename: prepared.filename,
231
232
  key: ghAttachmentKey(target, prepared.filename),
232
233
  contentType: prepared.optimized ? prepared.contentType : contentTypeOverride,
234
+ provenance: buildCliProvenance({
235
+ sourceName,
236
+ optimized: prepared.optimized,
237
+ frameId: prepared.frame?.framed ? prepared.frame.frameId : undefined,
238
+ keepExif: optimizeOpts.keepExif === true,
239
+ }),
233
240
  });
234
241
  results.push({
235
242
  ...result,
@@ -363,6 +370,12 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
363
370
  ref: flagString(parsed.flags, "--ref") ?? defaults.ref,
364
371
  contentType: prepared.optimized ? prepared.contentType : contentTypeOverride,
365
372
  deriveRepoFromGit: !noGit,
373
+ provenance: buildCliProvenance({
374
+ sourceName,
375
+ optimized: prepared.optimized,
376
+ frameId: prepared.frame?.framed ? prepared.frame.frameId : undefined,
377
+ keepExif: optimizeOpts.keepExif === true,
378
+ }),
366
379
  });
367
380
  const markdown = buildMarkdown(result.url, { alt, width });
368
381
  const optimizeMeta = {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export { sanitizeKeySegment, sha256Short, deriveRepoFromGit, buildScreenshotKey
3
3
  export { BUILTIN_DESTINATIONS, isBuiltinDestination, keyMatchesDestination, resolveDestinationRoot, resolvePutPrefix, type BuiltinDestinationId, } from "./destinations.js";
4
4
  export { DEFAULT_API_URL, DEFAULT_WORKSPACE, UPLOADS_CONFIG_KEYS, defaultConfigPath, resolveConfigPath, loadConfigFile, loadEnvFile, resolveApiUrl, resolveConfig, describeConfigSources, redactToken, writeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, workspaceFromToken, workspaceMismatch, type UploadsClientConfig, type ResolvedConfig, type WorkspaceSource, type ConfigValueSource, type ConfigSources, type UploadsConfigKey, type UploadsConfigValues, type PutDefaults, } from "./config.js";
5
5
  export { UploadsError, type UploadsErrorCode } from "./errors.js";
6
- export { createUploadsClient, type UploadsClient, type PutOptions, type ListOptions, type PutResult, type ListItem, type ListResult, type HeadResult, type DeleteResult, type HealthResult, type UsageResult, type ReconcileResult, type PurgeExpiredResult, type PurgeExpiredResponse, } from "./client.js";
6
+ export { createUploadsClient, type UploadsClient, type PutOptions, type ProvenanceInput, type ListOptions, type PutResult, type ListItem, type ListResult, type HeadResult, type DeleteResult, type HealthResult, type UsageResult, type ReconcileResult, type PurgeExpiredResult, type PurgeExpiredResponse, } from "./client.js";
7
+ export { buildCliProvenance } from "./provenance.js";
7
8
  export { ATTACHMENTS_MARKER, attachmentsCommentBody, ghAttachmentKey, ghKeyPrefix, isValidRepo, parseRepoFromRemoteUrl, type AttachmentItem, type GhTarget, type GhTargetKind, } from "./github.js";
8
9
  export { DEFAULT_OPTIMIZE_MAX_EDGE, DEFAULT_OPTIMIZE_QUALITY, optimizeImageForUpload, rewriteKeyExtension, withImageExtension, type OptimizeImageOptions, type OptimizeImageResult, type OptimizeOutputFormat, } from "./optimize.js";
9
10
  export { FRAME_PRESETS, applyFrame, listFramePresets, resolveFrameId, type FrameFit, type FrameOptions, type FrameResult, } from "./frame.js";
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { BUILTIN_DESTINATIONS, isBuiltinDestination, keyMatchesDestination, reso
4
4
  export { DEFAULT_API_URL, DEFAULT_WORKSPACE, UPLOADS_CONFIG_KEYS, defaultConfigPath, resolveConfigPath, loadConfigFile, loadEnvFile, resolveApiUrl, resolveConfig, describeConfigSources, redactToken, writeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, workspaceFromToken, workspaceMismatch, } from "./config.js";
5
5
  export { UploadsError } from "./errors.js";
6
6
  export { createUploadsClient, } from "./client.js";
7
+ export { buildCliProvenance } from "./provenance.js";
7
8
  export { ATTACHMENTS_MARKER, attachmentsCommentBody, ghAttachmentKey, ghKeyPrefix, isValidRepo, parseRepoFromRemoteUrl, } from "./github.js";
8
9
  export { DEFAULT_OPTIMIZE_MAX_EDGE, DEFAULT_OPTIMIZE_QUALITY, optimizeImageForUpload, rewriteKeyExtension, withImageExtension, } from "./optimize.js";
9
10
  export { FRAME_PRESETS, applyFrame, listFramePresets, resolveFrameId, } from "./frame.js";
package/dist/mcp/tools.js CHANGED
@@ -15,6 +15,7 @@ import { buildMarkdown } from "../embed.js";
15
15
  import { resolvePutPrefix } from "../destinations.js";
16
16
  import { ghAttachmentKey, ghKeyPrefix } from "../github.js";
17
17
  import { rewriteKeyExtension } from "../optimize.js";
18
+ import { buildCliProvenance } from "../provenance.js";
18
19
  import { execRunner, resolveCurrentPullRequest, resolveRepo, } from "../github-gh.js";
19
20
  import { optPosInt, optString, usage } from "./args.js";
20
21
  function optBool(args, name) {
@@ -254,9 +255,11 @@ export function createUploadsMcpTools(opts) {
254
255
  : new Uint8Array(Buffer.from(contentBase64, "base64"));
255
256
  const sourceName = file !== undefined ? (filenameArg ?? basename(file)) : filenameArg;
256
257
  const defaults = resolvePutDefaults({ envFile: globals.envFile });
258
+ const frameOpts = mcpFrameOptions(args);
259
+ const optimizeOpts = mcpOptimizeOptions(args, defaults);
257
260
  const prepared = await prepareImageForUpload(bytes, sourceName, {
258
- ...mcpFrameOptions(args),
259
- optimize: mcpOptimizeOptions(args, defaults),
261
+ ...frameOpts,
262
+ optimize: optimizeOpts,
260
263
  });
261
264
  const filename = prepared.filename;
262
265
  let key = target ? ghAttachmentKey(target, filename) : keyArg;
@@ -271,6 +274,13 @@ export function createUploadsMcpTools(opts) {
271
274
  ref: refArg ?? defaults.ref,
272
275
  contentType: prepared.optimized ? prepared.contentType : optString(args, "contentType"),
273
276
  deriveRepoFromGit: !noGit,
277
+ provenance: buildCliProvenance({
278
+ sourceName,
279
+ client: "uploads-mcp",
280
+ optimized: prepared.optimized,
281
+ frameId: prepared.frame?.framed ? prepared.frame.frameId : undefined,
282
+ keepExif: optimizeOpts.keepExif === true,
283
+ }),
274
284
  });
275
285
  const markdown = buildMarkdown(result.url, {
276
286
  alt: optString(args, "alt") ?? sourceName,
@@ -352,6 +362,13 @@ export function createUploadsMcpTools(opts) {
352
362
  filename: prepared.filename,
353
363
  key: ghAttachmentKey(target, prepared.filename),
354
364
  contentType: prepared.optimized ? prepared.contentType : contentType,
365
+ provenance: buildCliProvenance({
366
+ sourceName,
367
+ client: "uploads-mcp",
368
+ optimized: prepared.optimized,
369
+ frameId: prepared.frame?.framed ? prepared.frame.frameId : undefined,
370
+ keepExif: optimizeOpts.keepExif === true,
371
+ }),
355
372
  });
356
373
  uploads.push({
357
374
  ...result,
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Client-side provenance headers for put (X-Uploads-Meta-*).
3
+ * API allowlists keys; secrets never go here.
4
+ */
5
+ import type { ProvenanceInput } from "./client.js";
6
+ export declare function buildCliProvenance(opts: {
7
+ sourceName: string;
8
+ optimized?: boolean;
9
+ frameId?: string;
10
+ keepExif?: boolean;
11
+ client?: string;
12
+ }): ProvenanceInput;
@@ -0,0 +1,29 @@
1
+ import { createRequire } from "node:module";
2
+ let cachedVersion;
3
+ function packageVersion() {
4
+ if (cachedVersion)
5
+ return cachedVersion;
6
+ try {
7
+ const require = createRequire(import.meta.url);
8
+ const pkg = require("../package.json");
9
+ cachedVersion = pkg.version ?? "0.0.0";
10
+ }
11
+ catch {
12
+ cachedVersion = "0.0.0";
13
+ }
14
+ return cachedVersion;
15
+ }
16
+ export function buildCliProvenance(opts) {
17
+ const provenance = {
18
+ client: opts.client ?? "uploads-cli",
19
+ "client-version": packageVersion(),
20
+ "source-name": opts.sourceName.slice(0, 128),
21
+ };
22
+ if (opts.optimized)
23
+ provenance.optimized = "1";
24
+ if (opts.frameId)
25
+ provenance.frame = opts.frameId.slice(0, 64);
26
+ if (opts.keepExif)
27
+ provenance["keep-exif"] = "1";
28
+ return provenance;
29
+ }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
5
5
  "type": "module",
6
+ "sideEffects": false,
6
7
  "license": "MIT",
7
8
  "repository": {
8
9
  "type": "git",