@buildinternet/uploads 0.19.0 → 0.21.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/capture-facts.d.ts +19 -0
- package/dist/capture-facts.js +49 -0
- package/dist/cli-catalog.js +4 -0
- package/dist/cli-help.js +4 -4
- package/dist/client.d.ts +23 -2
- package/dist/client.js +12 -4
- package/dist/commands/screenshot.js +15 -7
- package/dist/commands.d.ts +47 -2
- package/dist/commands.js +153 -33
- package/dist/errors.d.ts +10 -2
- package/dist/errors.js +8 -1
- package/dist/image-facts.d.ts +12 -0
- package/dist/image-facts.js +108 -0
- package/dist/mcp/args.d.ts +24 -1
- package/dist/mcp/args.js +61 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/tools.js +42 -15
- package/dist/metadata-vocab.d.ts +24 -0
- package/dist/metadata-vocab.js +127 -0
- package/dist/metadata.d.ts +7 -0
- package/dist/metadata.js +13 -0
- package/package.json +2 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ScreenshotTarget, type ScreenshotViewport } from "./screenshot.js";
|
|
2
|
+
export interface CaptureFactsInput {
|
|
3
|
+
target: ScreenshotTarget;
|
|
4
|
+
viewport: ScreenshotViewport;
|
|
5
|
+
/** Only set when the caller forced a scheme (`--dark` / `--light`). */
|
|
6
|
+
colorScheme?: "dark" | "light";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Derive `url`/`path`/`env`/`theme`/`viewport` from a capture. `env` is only
|
|
10
|
+
* ever `local`: inferring `prod` from "not localhost" would mislabel every
|
|
11
|
+
* staging and preview URL, and wrong metadata is worse than absent metadata.
|
|
12
|
+
*/
|
|
13
|
+
export declare function captureFacts(input: CaptureFactsInput): Record<string, string>;
|
|
14
|
+
/**
|
|
15
|
+
* `captureFacts` for a raw target string, never at the cost of the capture
|
|
16
|
+
* itself: an unclassifiable target yields no facts rather than an error.
|
|
17
|
+
* Shared by the CLI and MCP screenshot paths.
|
|
18
|
+
*/
|
|
19
|
+
export declare function safeCaptureFacts(target: string, viewport: ScreenshotViewport, colorScheme: "dark" | "light" | undefined): Record<string, string>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical metadata derived from a screenshot capture, where the CLI knows
|
|
3
|
+
* the inputs exactly. Pure: no I/O, no throwing — an unparseable URL simply
|
|
4
|
+
* yields fewer keys.
|
|
5
|
+
*
|
|
6
|
+
* Design: .context/2026-07-21-upload-metadata-vocabulary-design.md
|
|
7
|
+
*/
|
|
8
|
+
import { dropUnsafeMetaValues } from "./metadata.js";
|
|
9
|
+
import { formatViewport } from "./metadata-vocab.js";
|
|
10
|
+
import { classifyTarget } from "./screenshot.js";
|
|
11
|
+
/**
|
|
12
|
+
* Derive `url`/`path`/`env`/`theme`/`viewport` from a capture. `env` is only
|
|
13
|
+
* ever `local`: inferring `prod` from "not localhost" would mislabel every
|
|
14
|
+
* staging and preview URL, and wrong metadata is worse than absent metadata.
|
|
15
|
+
*/
|
|
16
|
+
export function captureFacts(input) {
|
|
17
|
+
const facts = {};
|
|
18
|
+
facts.viewport = formatViewport(input.viewport.width, input.viewport.height, input.viewport.deviceScaleFactor);
|
|
19
|
+
if (input.colorScheme)
|
|
20
|
+
facts.theme = input.colorScheme;
|
|
21
|
+
if (input.target.kind === "url") {
|
|
22
|
+
facts.url = input.target.url;
|
|
23
|
+
try {
|
|
24
|
+
facts.path = new URL(input.target.url).pathname || "/";
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// classifyTarget already validated this, but never let a URL parse
|
|
28
|
+
// failure cost us the other facts.
|
|
29
|
+
}
|
|
30
|
+
if (input.target.localOnly)
|
|
31
|
+
facts.env = "local";
|
|
32
|
+
}
|
|
33
|
+
// A long query string can exceed the 512-char value cap; drop rather than
|
|
34
|
+
// let a derived value fail the upload.
|
|
35
|
+
return dropUnsafeMetaValues(facts);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `captureFacts` for a raw target string, never at the cost of the capture
|
|
39
|
+
* itself: an unclassifiable target yields no facts rather than an error.
|
|
40
|
+
* Shared by the CLI and MCP screenshot paths.
|
|
41
|
+
*/
|
|
42
|
+
export function safeCaptureFacts(target, viewport, colorScheme) {
|
|
43
|
+
try {
|
|
44
|
+
return captureFacts({ target: classifyTarget(target), viewport, colorScheme });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return {}; // derived metadata must never fail a capture
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/cli-catalog.js
CHANGED
|
@@ -36,6 +36,8 @@ export const PUT_LIKE_FLAGS = [
|
|
|
36
36
|
"--frame-url",
|
|
37
37
|
"--gallery",
|
|
38
38
|
"--meta",
|
|
39
|
+
"--state",
|
|
40
|
+
"--app",
|
|
39
41
|
"--workspace",
|
|
40
42
|
"-w",
|
|
41
43
|
"--help",
|
|
@@ -80,6 +82,8 @@ export const SCREENSHOT_FLAGS = [
|
|
|
80
82
|
"--comment",
|
|
81
83
|
"--gallery",
|
|
82
84
|
"--meta",
|
|
85
|
+
"--state",
|
|
86
|
+
"--app",
|
|
83
87
|
"--dry-run",
|
|
84
88
|
"--format",
|
|
85
89
|
"--workspace",
|
package/dist/cli-help.js
CHANGED
|
@@ -87,10 +87,10 @@ ${section(style, "Examples:")}
|
|
|
87
87
|
${style.command("uploads put")} ./shot.png --pr 123 --name hero.png
|
|
88
88
|
${style.command("uploads put")} ./after.png --pr 123 --comment
|
|
89
89
|
${style.command("uploads put")} ./bug.png --issue 45
|
|
90
|
-
${style.command("uploads put")} ./shot.png --meta
|
|
90
|
+
${style.command("uploads put")} ./shot.png --meta path=/settings --state after
|
|
91
91
|
${style.command("uploads attach")} ./before.png ./after.png
|
|
92
92
|
${style.command("uploads attach")} ./shot.png --pr 123 --repo myorg/myapp
|
|
93
|
-
${style.command("uploads attach")} ./shot.png --meta
|
|
93
|
+
${style.command("uploads attach")} ./shot.png --meta path=/settings --state after
|
|
94
94
|
${style.command("uploads doctor")}
|
|
95
95
|
${style.command("uploads install")}
|
|
96
96
|
${style.command("uploads logout")}
|
|
@@ -137,11 +137,11 @@ ${section(style, "Examples:")}
|
|
|
137
137
|
${style.command("uploads put")} ./after.png --pr 123 --comment
|
|
138
138
|
${style.command("uploads put")} ./bug.png --issue 45 --repo myorg/myapp
|
|
139
139
|
${style.command("uploads put")} ./shot.png --dry-run --format url
|
|
140
|
-
${style.command("uploads put")} ./shot.png --meta
|
|
140
|
+
${style.command("uploads put")} ./shot.png --meta path=/settings --state after
|
|
141
141
|
${style.command("uploads attach")} ./before.png ./after.png
|
|
142
142
|
${style.command("uploads attach")} ./shot.png --pr 123 --repo myorg/myapp
|
|
143
143
|
${style.command("uploads attach")} ./artifact.zip --issue 45 --no-comment
|
|
144
|
-
${style.command("uploads attach")} ./shot.png --meta
|
|
144
|
+
${style.command("uploads attach")} ./shot.png --meta path=/settings --state after
|
|
145
145
|
${style.command("uploads gallery")} create --title "Release screenshots"
|
|
146
146
|
${style.command("uploads doctor")}
|
|
147
147
|
${style.command("uploads logout")}
|
package/dist/client.d.ts
CHANGED
|
@@ -28,6 +28,14 @@ export interface PutOptions {
|
|
|
28
28
|
metadata?: Record<string, string>;
|
|
29
29
|
/** Validate key + resolve public URL without writing. `size` is local bytes only. */
|
|
30
30
|
dryRun?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Opt in to overwriting an existing object on a "strict" (non-`gh/`) key —
|
|
33
|
+
* see issue #174. Ignored (always allowed) on managed `gh/` paths
|
|
34
|
+
* (`attach`, `put --pr`/`--issue`), which stay silent hot-swap. Omit/false
|
|
35
|
+
* on a strict path with an existing key throws `UploadsError` with code
|
|
36
|
+
* `KEY_EXISTS`.
|
|
37
|
+
*/
|
|
38
|
+
replace?: boolean;
|
|
31
39
|
}
|
|
32
40
|
export interface ListOptions {
|
|
33
41
|
prefix?: string;
|
|
@@ -67,6 +75,11 @@ export interface PutResult {
|
|
|
67
75
|
* at this key would overwrite. Always set by the API for put/dry-run.
|
|
68
76
|
*/
|
|
69
77
|
replaced?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* dryRun only: true when a real put at this key would be refused (strict
|
|
80
|
+
* non-`gh/` key, existing object, no `replace`) instead of overwriting.
|
|
81
|
+
*/
|
|
82
|
+
wouldRefuse?: boolean;
|
|
70
83
|
metadata?: Record<string, string>;
|
|
71
84
|
}
|
|
72
85
|
export interface ListItem {
|
|
@@ -175,8 +188,7 @@ export interface FindGalleriesByReferenceOptions {
|
|
|
175
188
|
/**
|
|
176
189
|
* Reasons the bot did not post. The CLI falls back to the local `gh` path
|
|
177
190
|
* for all of these except `not_authorized` (issue #297 baseline control):
|
|
178
|
-
* the target repo is bound to a different workspace
|
|
179
|
-
* caller is the communal `default` workspace, which can't claim new repos).
|
|
191
|
+
* the target repo is bound to a different workspace.
|
|
180
192
|
* Falling back to `gh` there would let the human's own credentials post
|
|
181
193
|
* anyway, defeating the point of the server-side gate, so the CLI surfaces
|
|
182
194
|
* the decline instead.
|
|
@@ -219,6 +231,13 @@ export interface GithubLinkResult {
|
|
|
219
231
|
/** POST-only: whether THIS call's workspace ended up owning the binding. */
|
|
220
232
|
export interface GithubLinkClaimResult extends GithubLinkResult {
|
|
221
233
|
claimed: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* Present (only) when `claimed` is false because the repo is unbound and
|
|
236
|
+
* this workspace couldn't be verified as entitled to claim it — issue
|
|
237
|
+
* #297's cross-tenant authorization gate. Distinct from the "someone else
|
|
238
|
+
* already owns it" case, which instead reports a non-null `workspace`.
|
|
239
|
+
*/
|
|
240
|
+
reason?: "not_authorized";
|
|
222
241
|
}
|
|
223
242
|
/** `DELETE /v1/:workspace/github/link` result (issue #318, self-serve unlink). */
|
|
224
243
|
export interface GithubLinkUnlinkResult {
|
|
@@ -430,12 +449,14 @@ export declare function extractErrorFields(body: unknown, fallback?: string): {
|
|
|
430
449
|
message: string;
|
|
431
450
|
code?: string;
|
|
432
451
|
requiredScope?: string;
|
|
452
|
+
existingUrl?: string;
|
|
433
453
|
};
|
|
434
454
|
/** Fetch + parse an error-response body via {@link extractErrorFields}. */
|
|
435
455
|
export declare function parseErrorEnvelope(res: Response, fallback?: string): Promise<{
|
|
436
456
|
message: string;
|
|
437
457
|
code?: string;
|
|
438
458
|
requiredScope?: string;
|
|
459
|
+
existingUrl?: string;
|
|
439
460
|
}>;
|
|
440
461
|
export declare function createUploadsClient(config: UploadsClientConfig): {
|
|
441
462
|
put(body: Uint8Array, opts: PutOptions & {
|
package/dist/client.js
CHANGED
|
@@ -188,7 +188,7 @@ function usageBase(config) {
|
|
|
188
188
|
function galleriesBase(config) {
|
|
189
189
|
return `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/galleries`;
|
|
190
190
|
}
|
|
191
|
-
function mapApiError(status, error, code, requiredScope) {
|
|
191
|
+
function mapApiError(status, error, code, requiredScope, existingUrl) {
|
|
192
192
|
const normalized = error.toLowerCase();
|
|
193
193
|
if (status === 401 || code === "unauthorized" || normalized === "unauthorized") {
|
|
194
194
|
return new UploadsError(error, "UNAUTHORIZED", status);
|
|
@@ -218,6 +218,9 @@ function mapApiError(status, error, code, requiredScope) {
|
|
|
218
218
|
if (code === "github_required") {
|
|
219
219
|
return new UploadsError(error, "GITHUB_REQUIRED", status);
|
|
220
220
|
}
|
|
221
|
+
if (code === "key_exists") {
|
|
222
|
+
return new UploadsError(error, "KEY_EXISTS", status, { existingUrl });
|
|
223
|
+
}
|
|
221
224
|
return new UploadsError(error, "API_ERROR", status);
|
|
222
225
|
}
|
|
223
226
|
/**
|
|
@@ -239,6 +242,7 @@ export function extractErrorFields(body, fallback = "request failed") {
|
|
|
239
242
|
...(typeof details?.required_scope === "string"
|
|
240
243
|
? { requiredScope: details.required_scope }
|
|
241
244
|
: {}),
|
|
245
|
+
...(typeof details?.url === "string" ? { existingUrl: details.url } : {}),
|
|
242
246
|
};
|
|
243
247
|
}
|
|
244
248
|
if (typeof err === "string") {
|
|
@@ -256,8 +260,8 @@ export async function parseErrorEnvelope(res, fallback = "request failed") {
|
|
|
256
260
|
return extractErrorFields(body, fallback);
|
|
257
261
|
}
|
|
258
262
|
async function parseErrorResponse(res) {
|
|
259
|
-
const { message, code, requiredScope } = await parseErrorEnvelope(res, res.statusText || "request failed");
|
|
260
|
-
return mapApiError(res.status, message, code, requiredScope);
|
|
263
|
+
const { message, code, requiredScope, existingUrl } = await parseErrorEnvelope(res, res.statusText || "request failed");
|
|
264
|
+
return mapApiError(res.status, message, code, requiredScope, existingUrl);
|
|
261
265
|
}
|
|
262
266
|
export function createUploadsClient(config) {
|
|
263
267
|
async function request(method, path, opts) {
|
|
@@ -318,7 +322,8 @@ export function createUploadsClient(config) {
|
|
|
318
322
|
}));
|
|
319
323
|
const contentType = opts.contentType ?? inferContentType(opts.filename);
|
|
320
324
|
if (opts.dryRun) {
|
|
321
|
-
const
|
|
325
|
+
const qs = opts.replace ? "dryRun=1&replace=1" : "dryRun=1";
|
|
326
|
+
const preview = await request("PUT", `${filesBase(config)}/${encodeKeyPath(key)}?${qs}`);
|
|
322
327
|
if (preview.url == null) {
|
|
323
328
|
throw new UploadsError("workspace has no publicBaseUrl (cannot resolve a public URL)", "NO_PUBLIC_URL");
|
|
324
329
|
}
|
|
@@ -330,9 +335,12 @@ export function createUploadsClient(config) {
|
|
|
330
335
|
size: body.byteLength,
|
|
331
336
|
contentType,
|
|
332
337
|
replaced: preview.replaced === true,
|
|
338
|
+
wouldRefuse: preview.wouldRefuse === true,
|
|
333
339
|
};
|
|
334
340
|
}
|
|
335
341
|
const headers = { "Content-Type": contentType };
|
|
342
|
+
if (opts.replace)
|
|
343
|
+
headers["X-Uploads-Replace"] = "1";
|
|
336
344
|
if (opts.provenance) {
|
|
337
345
|
for (const [k, v] of Object.entries(opts.provenance)) {
|
|
338
346
|
if (v !== undefined && v !== "")
|
|
@@ -2,13 +2,15 @@ import { readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { flagBool, flagInt, flagString, flagValues, parseCommandArgs, UsageError, } from "../cli-args.js";
|
|
4
4
|
import { writeCommandHelp } from "../cli-style.js";
|
|
5
|
-
import { branchFromFlags, frameOptionsFromFlags, ghTargetFromFlags, optimizeOptionsFromFlags, syncAttachmentsComment, commentViaSuffix, uploadPreparedImage, } from "../commands.js";
|
|
5
|
+
import { branchFromFlags, derivedMetaEnabled, frameOptionsFromFlags, ghTargetFromFlags, optimizeOptionsFromFlags, stateAppMetaFromFlags, warnNearMissMeta, syncAttachmentsComment, commentViaSuffix, uploadPreparedImage, } from "../commands.js";
|
|
6
6
|
import { resolvePutDefaults } from "../config.js";
|
|
7
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 { ghBranchAttachmentKey, ghMetadataForBranch } from "../github.js";
|
|
11
|
+
import { safeCaptureFacts } from "../capture-facts.js";
|
|
11
12
|
import { parseMetaFlags, validateMetaMap } from "../metadata.js";
|
|
13
|
+
import { mergeDerivedMeta } from "../metadata-vocab.js";
|
|
12
14
|
import { writeJson, writeStdout } from "../io.js";
|
|
13
15
|
import { assertHideSelector, captureScreenshot, parseViewport, parseWaitUntil, } from "../screenshot.js";
|
|
14
16
|
const SCREENSHOT_HELP = `uploads screenshot <target> [options]
|
|
@@ -77,6 +79,8 @@ Options:
|
|
|
77
79
|
otherwise via local gh.
|
|
78
80
|
--gallery <id> Add the uploaded object to this public gallery
|
|
79
81
|
--meta <k=v> Queryable custom metadata (repeatable)
|
|
82
|
+
--state <s> before|after|empty|error|loading — the UI state shown
|
|
83
|
+
--app <name> Surface shown: web, ios, android, cli
|
|
80
84
|
--workspace, -w <name> Override workspace
|
|
81
85
|
--dry-run Capture + resolve key/URL without uploading
|
|
82
86
|
--format human|url|markdown|json
|
|
@@ -234,18 +238,22 @@ captureImpl = captureScreenshot) {
|
|
|
234
238
|
const frameOpts = frameOptionsFromFlags(parsed.flags);
|
|
235
239
|
const altFlag = flagString(parsed.flags, "--alt");
|
|
236
240
|
const width = flagInt(parsed.flags, "--width", "--width") ?? putDefaults.width;
|
|
237
|
-
const metaExtras = parseMetaFlags(flagValues(parsed.flags, "--meta"));
|
|
238
|
-
|
|
241
|
+
const metaExtras = warnNearMissMeta(ctx, parseMetaFlags(flagValues(parsed.flags, "--meta")));
|
|
242
|
+
// Explicit input (--meta plus the dedicated flags) wins over capture facts.
|
|
243
|
+
const explicitMeta = { ...metaExtras, ...stateAppMetaFromFlags(parsed.flags) };
|
|
244
|
+
const deriveMeta = derivedMetaEnabled(parsed.flags, putDefaults);
|
|
245
|
+
const withFacts = mergeDerivedMeta(explicitMeta, deriveMeta ? safeCaptureFacts(target, viewport, colorScheme) : {});
|
|
246
|
+
let metadata = withFacts;
|
|
239
247
|
if (ghTarget) {
|
|
240
|
-
metadata = { ...
|
|
248
|
+
metadata = { ...withFacts, ...ghMetadataFromTargetWithTitle(ghTarget, run) };
|
|
241
249
|
validateMetaMap(metadata);
|
|
242
250
|
}
|
|
243
251
|
else if (branchArg !== undefined) {
|
|
244
|
-
metadata = { ...
|
|
252
|
+
metadata = { ...withFacts, ...ghMetadataForBranch(branchRepo, branchArg) };
|
|
245
253
|
validateMetaMap(metadata);
|
|
246
254
|
}
|
|
247
|
-
else if (Object.keys(
|
|
248
|
-
validateMetaMap(
|
|
255
|
+
else if (Object.keys(withFacts).length > 0) {
|
|
256
|
+
validateMetaMap(withFacts);
|
|
249
257
|
}
|
|
250
258
|
const logHuman = !ctx.quiet && format === "human";
|
|
251
259
|
if (logHuman)
|
package/dist/commands.d.ts
CHANGED
|
@@ -41,6 +41,28 @@ export declare function ghTargetFromFlags(flags: CommandFlags["flags"], run: Com
|
|
|
41
41
|
export declare function branchFromFlags(flags: CommandFlags["flags"], run: CommandRunner): string | undefined;
|
|
42
42
|
/** Shared put/attach optimize flags + UPLOADS_NO_OPTIMIZE default. */
|
|
43
43
|
export declare function optimizeOptionsFromFlags(flags: CommandFlags["flags"], defaults: PutDefaults): OptimizeImageOptions;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the derived-metadata tier is on — screenshot capture facts and EXIF
|
|
46
|
+
* promotion. `--no-auto` and `UPLOADS_NO_AUTO_META=1` turn it off; `--auto`
|
|
47
|
+
* forces past the config default.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately *not* gated on `--no-git`. That flag means "don't shell out to
|
|
50
|
+
* git", which says nothing about a viewport or a URL path — a capture of a
|
|
51
|
+
* local .html file outside any repo should still record what it captured.
|
|
52
|
+
* `--no-git` still disables gh.* below, which genuinely needs a repo.
|
|
53
|
+
*/
|
|
54
|
+
export declare function derivedMetaEnabled(flags: CommandFlags["flags"], defaults: Pick<PutDefaults, "noAutoMeta">): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Warn about metadata keys that look like misspellings of canonical ones, then
|
|
57
|
+
* return the map unchanged — we nag, we never rewrite a caller's key.
|
|
58
|
+
*/
|
|
59
|
+
export declare function warnNearMissMeta(ctx: CliContext, meta: Record<string, string>): Record<string, string>;
|
|
60
|
+
/**
|
|
61
|
+
* Canonical `state`/`app` pairs from their dedicated flags. Shared by put,
|
|
62
|
+
* attach and screenshot. These are sugar for the matching `--meta` keys; the
|
|
63
|
+
* point is `--help` discoverability and `--state` validation.
|
|
64
|
+
*/
|
|
65
|
+
export declare function stateAppMetaFromFlags(flags: CommandFlags["flags"]): Record<string, string>;
|
|
44
66
|
export type PreparedUpload = OptimizeImageResult & {
|
|
45
67
|
frame?: Pick<FrameResult, "framed" | "frameId" | "skippedReason">;
|
|
46
68
|
};
|
|
@@ -67,7 +89,19 @@ export interface UploadPreparedImageOptions {
|
|
|
67
89
|
deriveRepoFromGit?: boolean;
|
|
68
90
|
contentType?: string;
|
|
69
91
|
dryRun?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Opt in to overwriting an existing object on a strict (non-`gh/`) key —
|
|
94
|
+
* see issue #174. Ignored server-side on managed `gh/` paths (`ghTarget`
|
|
95
|
+
* set), which always hot-swap.
|
|
96
|
+
*/
|
|
97
|
+
replace?: boolean;
|
|
70
98
|
metadata?: Record<string, string>;
|
|
99
|
+
/**
|
|
100
|
+
* Promote this image's own EXIF allowlist into its metadata (see
|
|
101
|
+
* image-facts.ts). Lives here, on the shared bytes tail, so every upload
|
|
102
|
+
* surface — CLI put/screenshot, MCP put/screenshot — derives alike.
|
|
103
|
+
*/
|
|
104
|
+
deriveImageFacts?: boolean;
|
|
71
105
|
provenanceClient?: string;
|
|
72
106
|
/**
|
|
73
107
|
* Alt text for the markdown. Takes the prepared result so callers whose
|
|
@@ -118,8 +152,7 @@ export declare function commentViaSuffix(via: AttachmentsCommentResult["via"]):
|
|
|
118
152
|
/**
|
|
119
153
|
* Thrown by `syncAttachmentsComment` when the server declines with
|
|
120
154
|
* `not_authorized` (issue #297 baseline control) — this repo is bound to a
|
|
121
|
-
* different workspace
|
|
122
|
-
* workspace. Deliberately not caught by the generic "bot endpoint
|
|
155
|
+
* different workspace. Deliberately not caught by the generic "bot endpoint
|
|
123
156
|
* unreachable" fallback below: falling back to gh here would let the
|
|
124
157
|
* human's own credentials post anyway, defeating the point of the
|
|
125
158
|
* server-side gate.
|
|
@@ -164,6 +197,8 @@ export declare function uploadAttachments(opts: {
|
|
|
164
197
|
frameFit?: "cover" | "contain";
|
|
165
198
|
};
|
|
166
199
|
metadata?: Record<string, string>;
|
|
200
|
+
/** Forwarded per file — see image-facts.ts. */
|
|
201
|
+
deriveImageFacts?: boolean;
|
|
167
202
|
/** Provenance `client` field (default uploads-cli). */
|
|
168
203
|
provenanceClient?: string;
|
|
169
204
|
concurrency?: number;
|
|
@@ -196,6 +231,8 @@ export declare function uploadBranchAttachments(opts: {
|
|
|
196
231
|
frameFit?: "cover" | "contain";
|
|
197
232
|
};
|
|
198
233
|
metadata?: Record<string, string>;
|
|
234
|
+
/** Forwarded per file — see image-facts.ts. */
|
|
235
|
+
deriveImageFacts?: boolean;
|
|
199
236
|
provenanceClient?: string;
|
|
200
237
|
concurrency?: number;
|
|
201
238
|
}): Promise<{
|
|
@@ -227,6 +264,12 @@ export declare function uploadPuts(opts: {
|
|
|
227
264
|
deriveRepoFromGit?: boolean;
|
|
228
265
|
contentType?: string;
|
|
229
266
|
dryRun?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Opt in to overwriting an existing object on a strict (non-`gh/`) key —
|
|
269
|
+
* see issue #174. Ignored server-side when `ghTarget` targets a managed
|
|
270
|
+
* `gh/` path, which always hot-swaps.
|
|
271
|
+
*/
|
|
272
|
+
replace?: boolean;
|
|
230
273
|
optimize: OptimizeImageOptions;
|
|
231
274
|
frame: {
|
|
232
275
|
frameId?: string;
|
|
@@ -234,6 +277,8 @@ export declare function uploadPuts(opts: {
|
|
|
234
277
|
frameFit?: "cover" | "contain";
|
|
235
278
|
};
|
|
236
279
|
metadata?: Record<string, string>;
|
|
280
|
+
/** Forwarded per file to `uploadPreparedImage` — see image-facts.ts. */
|
|
281
|
+
deriveImageFacts?: boolean;
|
|
237
282
|
provenanceClient?: string;
|
|
238
283
|
/** When set, used as alt for every file; else each file's basename. */
|
|
239
284
|
alt?: string;
|