@buildinternet/uploads 0.11.0 → 0.12.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 +14 -3
- package/dist/async.d.ts +5 -0
- package/dist/async.js +21 -0
- package/dist/cli-catalog.d.ts +7 -0
- package/dist/cli-catalog.js +52 -1
- package/dist/cli.js +12 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.js +13 -7
- package/dist/commands/completion.js +15 -5
- package/dist/commands/install.js +5 -0
- package/dist/commands/screenshot.d.ts +6 -0
- package/dist/commands/screenshot.js +317 -0
- package/dist/commands.d.ts +162 -2
- package/dist/commands.js +440 -143
- package/dist/config-file.d.ts +28 -2
- package/dist/config-file.js +44 -6
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/mcp/batch-error.d.ts +16 -0
- package/dist/mcp/batch-error.js +24 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +16 -0
- package/dist/mcp/tools.d.ts +8 -1
- package/dist/mcp/tools.js +393 -94
- package/dist/screenshot-local.d.ts +64 -0
- package/dist/screenshot-local.js +310 -0
- package/dist/screenshot-remote.d.ts +23 -0
- package/dist/screenshot-remote.js +79 -0
- package/dist/screenshot.d.ts +74 -0
- package/dist/screenshot.js +231 -0
- package/package.json +4 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { flagBool, flagInt, flagString, flagValues, parseCommandArgs, UsageError, } from "../cli-args.js";
|
|
4
|
+
import { writeCommandHelp } from "../cli-style.js";
|
|
5
|
+
import { frameOptionsFromFlags, ghTargetFromFlags, optimizeOptionsFromFlags, syncAttachmentsComment, uploadPreparedImage, } from "../commands.js";
|
|
6
|
+
import { resolvePutDefaults } from "../config.js";
|
|
7
|
+
import { loadDefaultsRaw, resolveScreenshotDefaults } from "../config-file.js";
|
|
8
|
+
import { resolvePutPrefix } from "../destinations.js";
|
|
9
|
+
import { ghMetadataFromTarget } from "../github.js";
|
|
10
|
+
import { execRunner } from "../github-gh.js";
|
|
11
|
+
import { parseMetaFlags, validateMetaMap } from "../metadata.js";
|
|
12
|
+
import { writeJson, writeStdout } from "../io.js";
|
|
13
|
+
import { captureScreenshot, parseViewport, parseWaitUntil, } from "../screenshot.js";
|
|
14
|
+
const SCREENSHOT_HELP = `uploads screenshot <target> [options]
|
|
15
|
+
|
|
16
|
+
Capture a URL or a local .html file and host it — a hosted, PR-embeddable
|
|
17
|
+
image in one step. target is an http(s) URL or a path to an .html file.
|
|
18
|
+
|
|
19
|
+
Two capture backends: "local" drives an already-installed Chrome/Chromium via
|
|
20
|
+
playwright-core (no browser download); "remote" renders server-side via the
|
|
21
|
+
uploads.sh render endpoint (no local browser needed, counts against the
|
|
22
|
+
workspace's monthly upload budget). Default --via auto prefers local when a
|
|
23
|
+
browser is found, else remote.
|
|
24
|
+
|
|
25
|
+
.html files work on both backends (sent inline to remote, ≤ 2 MiB; anything
|
|
26
|
+
they reference via file:// or relative paths only resolves with --via local).
|
|
27
|
+
localhost/private-network URLs are reachable only by the
|
|
28
|
+
local backend — with --via remote (or auto falling back to remote) these
|
|
29
|
+
fail fast with a clear error instead of sending a doomed request.
|
|
30
|
+
|
|
31
|
+
After capture, screenshots share the put upload pipeline: optional --frame,
|
|
32
|
+
optimize-by-default, --pr/--issue attachment + --comment, --gallery, --meta.
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--via auto|local|remote Capture backend (default: auto, or UPLOADS_SCREENSHOT_VIA)
|
|
36
|
+
--browser <path> Explicit local browser executable (or UPLOADS_CHROME_PATH / CHROME_PATH)
|
|
37
|
+
--cdp <endpoint> Attach to a running Chrome via CDP (http://host:port or ws://…)
|
|
38
|
+
--viewport <WxH[@Sx]> Size + device scale factor (default: 1280x800@2)
|
|
39
|
+
--selector <css> Capture one element instead of the viewport
|
|
40
|
+
--full-page Capture the full scrollable page
|
|
41
|
+
--dark / --light Emulate prefers-color-scheme (full media-query emulation on --via local
|
|
42
|
+
only; --via remote just sets the CSS color-scheme property, so a page's
|
|
43
|
+
own prefers-color-scheme queries won't flip)
|
|
44
|
+
--wait <load|domcontentloaded|networkidle|ms> Settle strategy (default: load); a millisecond
|
|
45
|
+
count is local-only — use --via local
|
|
46
|
+
--out <file> Also write the PNG to a local file
|
|
47
|
+
--no-upload Skip hosting; requires --out (local file only)
|
|
48
|
+
--destination <id> Typed root: screenshots | gh | f
|
|
49
|
+
--prefix <path> Key prefix (default: screenshots, or UPLOADS_DEFAULT_PREFIX)
|
|
50
|
+
--repo <owner/repo> Repo segment (default: git remote, or UPLOADS_DEFAULT_REPO)
|
|
51
|
+
--ref <id> PR/issue/branch segment (default: today, or UPLOADS_DEFAULT_REF)
|
|
52
|
+
--key <key> Explicit object key; cannot combine with --pr/--issue
|
|
53
|
+
--alt <text> Alt text (default: derived filename)
|
|
54
|
+
--width <px> <img width=…> markdown
|
|
55
|
+
--frame <id> Device/browser frame before optimize (phone|browser|iphone-16-pro)
|
|
56
|
+
--frame-url <url> Address bar text for --frame browser
|
|
57
|
+
--frame-fit cover|contain How the shot fills the screen (default: cover)
|
|
58
|
+
--no-optimize Skip client-side image optimization
|
|
59
|
+
--optimize-max-edge <px> Max long edge when optimizing (default: 2400)
|
|
60
|
+
--optimize-quality <1-100> WebP quality (default: 85)
|
|
61
|
+
--keep-exif Keep EXIF/XMP/ICC when optimizing
|
|
62
|
+
--pr <num> Attach to a pull request (stable URL, no hash)
|
|
63
|
+
--issue <num> Attach to an issue
|
|
64
|
+
--comment With --pr/--issue: update the managed attachments comment
|
|
65
|
+
--gallery <id> Add the uploaded object to this public gallery
|
|
66
|
+
--meta <k=v> Queryable custom metadata (repeatable)
|
|
67
|
+
--workspace, -w <name> Override workspace
|
|
68
|
+
--dry-run Capture + resolve key/URL without uploading
|
|
69
|
+
--format human|url|markdown|json
|
|
70
|
+
|
|
71
|
+
Exit codes: 0 ok · 2 usage/no browser found/file · 3 auth/policy/budget · 4 network · 1 other.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
uploads screenshot https://uploads.sh
|
|
75
|
+
uploads screenshot ./card.html --out ./card.png
|
|
76
|
+
uploads screenshot https://app.example/settings --selector main --dark
|
|
77
|
+
uploads screenshot http://localhost:3000 --via local --full-page
|
|
78
|
+
uploads screenshot https://uploads.sh --pr 128 --comment
|
|
79
|
+
uploads screenshot ./card.html --no-upload --out ./card.png
|
|
80
|
+
`;
|
|
81
|
+
function colorSchemeFromFlags(flags) {
|
|
82
|
+
const dark = flagBool(flags, "--dark");
|
|
83
|
+
const light = flagBool(flags, "--light");
|
|
84
|
+
if (dark && light)
|
|
85
|
+
throw new UsageError("--dark and --light are mutually exclusive");
|
|
86
|
+
if (dark)
|
|
87
|
+
return "dark";
|
|
88
|
+
if (light)
|
|
89
|
+
return "light";
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
function viaFromFlags(flags, fallback) {
|
|
93
|
+
const raw = flagString(flags, "--via");
|
|
94
|
+
if (!raw)
|
|
95
|
+
return fallback;
|
|
96
|
+
if (raw === "auto" || raw === "local" || raw === "remote")
|
|
97
|
+
return raw;
|
|
98
|
+
throw new UsageError(`invalid --via: ${raw} (use auto, local, or remote)`);
|
|
99
|
+
}
|
|
100
|
+
export async function runScreenshot(ctx, args, help = false, run = execRunner,
|
|
101
|
+
/** Injectable for tests — avoids launching a real browser or hitting the network. */
|
|
102
|
+
captureImpl = captureScreenshot) {
|
|
103
|
+
if (help) {
|
|
104
|
+
writeCommandHelp(SCREENSHOT_HELP);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const parsed = parseCommandArgs(args);
|
|
108
|
+
if (parsed.help) {
|
|
109
|
+
writeCommandHelp(SCREENSHOT_HELP);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
const target = parsed.positionals[0];
|
|
113
|
+
if (!target) {
|
|
114
|
+
writeCommandHelp(SCREENSHOT_HELP);
|
|
115
|
+
return 2;
|
|
116
|
+
}
|
|
117
|
+
if (parsed.positionals.length > 1) {
|
|
118
|
+
throw new UsageError("screenshot takes exactly one target");
|
|
119
|
+
}
|
|
120
|
+
// Read the on-disk config once and share it between the screenshot and
|
|
121
|
+
// put-style default resolvers (both would otherwise read the same file).
|
|
122
|
+
const rawDefaults = loadDefaultsRaw({ envFile: ctx.envFile });
|
|
123
|
+
const screenshotDefaults = resolveScreenshotDefaults({ envFile: ctx.envFile }, rawDefaults);
|
|
124
|
+
const via = viaFromFlags(parsed.flags, screenshotDefaults.via ?? "auto");
|
|
125
|
+
const browserPath = flagString(parsed.flags, "--browser");
|
|
126
|
+
const cdp = flagString(parsed.flags, "--cdp");
|
|
127
|
+
const viewport = parseViewport(flagString(parsed.flags, "--viewport"));
|
|
128
|
+
const selector = flagString(parsed.flags, "--selector");
|
|
129
|
+
const fullPage = flagBool(parsed.flags, "--full-page");
|
|
130
|
+
const colorScheme = colorSchemeFromFlags(parsed.flags);
|
|
131
|
+
const waitUntil = parseWaitUntil(flagString(parsed.flags, "--wait"));
|
|
132
|
+
const outFile = flagString(parsed.flags, "--out");
|
|
133
|
+
const noUpload = flagBool(parsed.flags, "--no-upload");
|
|
134
|
+
if (noUpload && !outFile)
|
|
135
|
+
throw new UsageError("--no-upload requires --out");
|
|
136
|
+
const keyHint = flagString(parsed.flags, "--key");
|
|
137
|
+
const destFlag = flagString(parsed.flags, "--destination");
|
|
138
|
+
const prefixFlag = flagString(parsed.flags, "--prefix");
|
|
139
|
+
const ghTarget = ghTargetFromFlags(parsed.flags, run);
|
|
140
|
+
const wantComment = parsed.flags.has("--comment");
|
|
141
|
+
const galleryId = flagString(parsed.flags, "--gallery");
|
|
142
|
+
const dryRun = flagBool(parsed.flags, "--dry-run");
|
|
143
|
+
if (wantComment && !ghTarget)
|
|
144
|
+
throw new UsageError("--comment requires --pr or --issue");
|
|
145
|
+
if (ghTarget) {
|
|
146
|
+
if (keyHint)
|
|
147
|
+
throw new UsageError("--key cannot be combined with --pr/--issue");
|
|
148
|
+
if (flagString(parsed.flags, "--ref"))
|
|
149
|
+
throw new UsageError("--ref cannot be combined with --pr/--issue");
|
|
150
|
+
if (prefixFlag)
|
|
151
|
+
throw new UsageError("--prefix cannot be combined with --pr/--issue");
|
|
152
|
+
}
|
|
153
|
+
if (dryRun) {
|
|
154
|
+
if (wantComment)
|
|
155
|
+
throw new UsageError("--dry-run cannot be combined with --comment");
|
|
156
|
+
if (galleryId)
|
|
157
|
+
throw new UsageError("--dry-run cannot be combined with --gallery");
|
|
158
|
+
if (noUpload)
|
|
159
|
+
throw new UsageError("--dry-run cannot be combined with --no-upload");
|
|
160
|
+
}
|
|
161
|
+
let resolvedPrefix;
|
|
162
|
+
try {
|
|
163
|
+
resolvedPrefix = resolvePutPrefix({
|
|
164
|
+
destination: destFlag,
|
|
165
|
+
prefix: prefixFlag,
|
|
166
|
+
key: keyHint,
|
|
167
|
+
ghAttachment: Boolean(ghTarget),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
throw new UsageError(err instanceof Error ? err.message : String(err));
|
|
172
|
+
}
|
|
173
|
+
const format = ctx.json
|
|
174
|
+
? "json"
|
|
175
|
+
: (() => {
|
|
176
|
+
const raw = flagString(parsed.flags, "--format");
|
|
177
|
+
if (!raw || raw === "human")
|
|
178
|
+
return "human";
|
|
179
|
+
if (raw === "url" || raw === "markdown" || raw === "json")
|
|
180
|
+
return raw;
|
|
181
|
+
throw new UsageError(`invalid --format: ${raw}`);
|
|
182
|
+
})();
|
|
183
|
+
const putDefaults = resolvePutDefaults({ envFile: ctx.envFile }, rawDefaults);
|
|
184
|
+
const optimizeOpts = optimizeOptionsFromFlags(parsed.flags, putDefaults);
|
|
185
|
+
const frameOpts = frameOptionsFromFlags(parsed.flags);
|
|
186
|
+
const altFlag = flagString(parsed.flags, "--alt");
|
|
187
|
+
const width = flagInt(parsed.flags, "--width", "--width") ?? putDefaults.width;
|
|
188
|
+
const metaExtras = parseMetaFlags(flagValues(parsed.flags, "--meta"));
|
|
189
|
+
let metadata = metaExtras;
|
|
190
|
+
if (ghTarget) {
|
|
191
|
+
metadata = { ...metaExtras, ...ghMetadataFromTarget(ghTarget) };
|
|
192
|
+
validateMetaMap(metadata);
|
|
193
|
+
}
|
|
194
|
+
else if (Object.keys(metaExtras).length > 0) {
|
|
195
|
+
validateMetaMap(metaExtras);
|
|
196
|
+
}
|
|
197
|
+
const logHuman = !ctx.quiet && format === "human";
|
|
198
|
+
if (logHuman)
|
|
199
|
+
process.stderr.write(`>> capturing ${target}\n`);
|
|
200
|
+
const captured = await captureImpl({
|
|
201
|
+
target,
|
|
202
|
+
via,
|
|
203
|
+
browserPath,
|
|
204
|
+
cdp,
|
|
205
|
+
viewport,
|
|
206
|
+
selector,
|
|
207
|
+
fullPage,
|
|
208
|
+
colorScheme,
|
|
209
|
+
waitUntil,
|
|
210
|
+
apiUrl: ctx.config.apiUrl,
|
|
211
|
+
token: ctx.config.token,
|
|
212
|
+
});
|
|
213
|
+
if (logHuman)
|
|
214
|
+
process.stderr.write(`>> captured via ${captured.backend} backend\n`);
|
|
215
|
+
if (outFile) {
|
|
216
|
+
writeFileSync(outFile, captured.png);
|
|
217
|
+
if (logHuman)
|
|
218
|
+
process.stderr.write(`>> wrote ${outFile}\n`);
|
|
219
|
+
}
|
|
220
|
+
if (noUpload) {
|
|
221
|
+
if (ctx.json) {
|
|
222
|
+
await writeJson({ file: outFile, backend: captured.backend, size: captured.png.byteLength });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
await writeStdout(`FILE: ${outFile}\n`);
|
|
226
|
+
}
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
const repo = flagString(parsed.flags, "--repo") ?? putDefaults.repo;
|
|
230
|
+
const ref = flagString(parsed.flags, "--ref") ?? putDefaults.ref;
|
|
231
|
+
const alt = altFlag ?? basename(captured.filename);
|
|
232
|
+
const { result, prepared, markdown } = await uploadPreparedImage(ctx.client, captured.png, captured.filename, {
|
|
233
|
+
frame: frameOpts,
|
|
234
|
+
optimize: optimizeOpts,
|
|
235
|
+
ghTarget,
|
|
236
|
+
key: keyHint,
|
|
237
|
+
prefix: resolvedPrefix ?? putDefaults.prefix,
|
|
238
|
+
repo,
|
|
239
|
+
ref,
|
|
240
|
+
deriveRepoFromGit: !(flagBool(parsed.flags, "--no-git") || putDefaults.noGit === true),
|
|
241
|
+
dryRun,
|
|
242
|
+
metadata,
|
|
243
|
+
provenanceClient: "uploads-cli-screenshot",
|
|
244
|
+
alt: () => alt,
|
|
245
|
+
width,
|
|
246
|
+
});
|
|
247
|
+
let gallery;
|
|
248
|
+
if (galleryId) {
|
|
249
|
+
try {
|
|
250
|
+
const current = await ctx.client.getGallery(galleryId);
|
|
251
|
+
const item = await ctx.client.addGalleryItem(galleryId, result.key, {
|
|
252
|
+
expectedVersion: current.version,
|
|
253
|
+
altText: alt,
|
|
254
|
+
});
|
|
255
|
+
gallery = { id: galleryId, url: current.url };
|
|
256
|
+
void item;
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
gallery = { id: galleryId, error: err instanceof Error ? err.message : String(err) };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
let comment;
|
|
263
|
+
let commentError;
|
|
264
|
+
if (wantComment && ghTarget) {
|
|
265
|
+
try {
|
|
266
|
+
comment = await syncAttachmentsComment(ctx.client, ghTarget, run);
|
|
267
|
+
if (logHuman)
|
|
268
|
+
process.stderr.write(`>> attachments comment ${comment.action}\n`);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
commentError = err instanceof Error ? err.message : String(err);
|
|
272
|
+
process.stderr.write(`warning: upload succeeded but the GitHub comment failed (is gh installed and authenticated?): ${commentError}\n`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (logHuman) {
|
|
276
|
+
if (prepared.frame?.framed)
|
|
277
|
+
process.stderr.write(`>> framed with ${prepared.frame.frameId}\n`);
|
|
278
|
+
if (prepared.optimized) {
|
|
279
|
+
process.stderr.write(`>> optimized ${prepared.originalBytes} → ${prepared.outputBytes} bytes\n`);
|
|
280
|
+
}
|
|
281
|
+
process.stderr.write(`>> key: ${result.key}${dryRun ? " (dry run — not uploaded)" : ""}\n\n`);
|
|
282
|
+
}
|
|
283
|
+
switch (format) {
|
|
284
|
+
case "json":
|
|
285
|
+
await writeJson({
|
|
286
|
+
workspace: result.workspace,
|
|
287
|
+
key: result.key,
|
|
288
|
+
url: result.url,
|
|
289
|
+
embedUrl: result.embedUrl,
|
|
290
|
+
size: result.size,
|
|
291
|
+
contentType: result.contentType,
|
|
292
|
+
replaced: result.replaced,
|
|
293
|
+
markdown,
|
|
294
|
+
backend: captured.backend,
|
|
295
|
+
gallery,
|
|
296
|
+
...(dryRun ? { dryRun: true } : {}),
|
|
297
|
+
});
|
|
298
|
+
break;
|
|
299
|
+
case "url":
|
|
300
|
+
await writeStdout(`${result.url}\n`);
|
|
301
|
+
break;
|
|
302
|
+
case "markdown":
|
|
303
|
+
await writeStdout(`${markdown}\n`);
|
|
304
|
+
break;
|
|
305
|
+
default: {
|
|
306
|
+
const embedLine = result.embedUrl ? `EMBED: ${result.embedUrl}\n` : "";
|
|
307
|
+
await writeStdout(`URL: ${result.url}\n${embedLine}MARKDOWN: ${markdown}${gallery?.url ? `\nGALLERY: ${gallery.url}` : ""}\n`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (gallery?.error) {
|
|
311
|
+
process.stderr.write(`warning: upload succeeded but adding it to gallery ${gallery.id} failed: ${gallery.error}\n`);
|
|
312
|
+
}
|
|
313
|
+
if (commentError && ctx.json) {
|
|
314
|
+
// already reported to stderr above; json output stays upload-focused.
|
|
315
|
+
}
|
|
316
|
+
return gallery?.error ? 1 : 0;
|
|
317
|
+
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type UploadsClient } from "./client.js";
|
|
1
|
+
import { type PutResult, type UploadsClient } from "./client.js";
|
|
2
2
|
import { type CommandFlags } from "./cli-args.js";
|
|
3
3
|
import { type ResolvedConfig } from "./config.js";
|
|
4
4
|
import { type GhTarget } from "./github.js";
|
|
@@ -6,6 +6,11 @@ import { type CommandRunner } from "./github-gh.js";
|
|
|
6
6
|
import { type OptimizeImageOptions, type OptimizeImageResult } from "./optimize.js";
|
|
7
7
|
import { type FrameResult } from "./frame.js";
|
|
8
8
|
import type { PutDefaults } from "./config-file.js";
|
|
9
|
+
import type { DetectRoots } from "./screenshot-local.js";
|
|
10
|
+
/** Parallel fan-out for multi-file put/attach (matches files-sdk bulk default). */
|
|
11
|
+
export declare const UPLOAD_BATCH_CONCURRENCY = 8;
|
|
12
|
+
/** @deprecated Use UPLOAD_BATCH_CONCURRENCY. */
|
|
13
|
+
export declare const ATTACH_CONCURRENCY = 8;
|
|
9
14
|
export { formatUsageHuman } from "./format-usage.js";
|
|
10
15
|
export interface CliContext {
|
|
11
16
|
config: ResolvedConfig;
|
|
@@ -21,6 +26,8 @@ export declare function readFileArg(fileArg: string): Uint8Array;
|
|
|
21
26
|
* neither is present. Shared by the CLI flags and the MCP tool arguments.
|
|
22
27
|
*/
|
|
23
28
|
export declare function makeGhTarget(pr: number | undefined, issue: number | undefined, repoArg: string | undefined, run: CommandRunner): GhTarget | undefined;
|
|
29
|
+
/** Reads --pr/--issue (+ --repo) into a GhTarget; undefined when neither flag is present. */
|
|
30
|
+
export declare function ghTargetFromFlags(flags: CommandFlags["flags"], run: CommandRunner): GhTarget | undefined;
|
|
24
31
|
/** Shared put/attach optimize flags + UPLOADS_NO_OPTIMIZE default. */
|
|
25
32
|
export declare function optimizeOptionsFromFlags(flags: CommandFlags["flags"], defaults: PutDefaults): OptimizeImageOptions;
|
|
26
33
|
export type PreparedUpload = OptimizeImageResult & {
|
|
@@ -33,6 +40,53 @@ export declare function prepareImageForUpload(bytes: Uint8Array, filename: strin
|
|
|
33
40
|
frameFit?: "cover" | "contain";
|
|
34
41
|
optimize: OptimizeImageOptions;
|
|
35
42
|
}): Promise<PreparedUpload>;
|
|
43
|
+
export interface UploadPreparedImageOptions {
|
|
44
|
+
frame: {
|
|
45
|
+
frameId?: string;
|
|
46
|
+
frameUrl?: string;
|
|
47
|
+
frameFit?: "cover" | "contain";
|
|
48
|
+
};
|
|
49
|
+
optimize: OptimizeImageOptions;
|
|
50
|
+
/** gh attachment key wins over `key` when both are set (matches every call site). */
|
|
51
|
+
ghTarget?: GhTarget;
|
|
52
|
+
key?: string;
|
|
53
|
+
prefix?: string;
|
|
54
|
+
repo?: string;
|
|
55
|
+
ref?: string;
|
|
56
|
+
deriveRepoFromGit?: boolean;
|
|
57
|
+
contentType?: string;
|
|
58
|
+
dryRun?: boolean;
|
|
59
|
+
metadata?: Record<string, string>;
|
|
60
|
+
provenanceClient?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Alt text for the markdown. Takes the prepared result so callers whose
|
|
63
|
+
* default depends on the post-frame/optimize filename can use it — each
|
|
64
|
+
* call site's existing default is preserved verbatim (see
|
|
65
|
+
* .context/2026-07-16-screenshot-command-RESULT.md, "Simplify pass").
|
|
66
|
+
*/
|
|
67
|
+
alt: (prepared: PreparedUpload) => string;
|
|
68
|
+
width?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface UploadPreparedImageResult {
|
|
71
|
+
result: PutResult;
|
|
72
|
+
prepared: PreparedUpload;
|
|
73
|
+
markdown: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Shared bytes-oriented upload tail: frame + optimize the bytes, resolve the
|
|
77
|
+
* object key (gh attachment key wins over an explicit key; extension
|
|
78
|
+
* rewritten post-optimize), put, and build the GitHub embed markdown. Used by
|
|
79
|
+
* the screenshot CLI command, the MCP screenshot tool, and the MCP put
|
|
80
|
+
* tool's contentBase64 path — the three in-memory-bytes call sites.
|
|
81
|
+
* uploadPuts/uploadAttachments loop over file paths with their own bounded
|
|
82
|
+
* concurrency and delegate here per item.
|
|
83
|
+
*/
|
|
84
|
+
export declare function uploadPreparedImage(client: UploadsClient, bytes: Uint8Array, sourceName: string, opts: UploadPreparedImageOptions): Promise<UploadPreparedImageResult>;
|
|
85
|
+
export declare function frameOptionsFromFlags(flags: CommandFlags["flags"]): {
|
|
86
|
+
frameId?: string;
|
|
87
|
+
frameUrl?: string;
|
|
88
|
+
frameFit?: "cover" | "contain";
|
|
89
|
+
};
|
|
36
90
|
/**
|
|
37
91
|
* List every attachment under the target's prefix and create/update the
|
|
38
92
|
* managed comment. Throws on gh failure — callers decide whether that is
|
|
@@ -42,6 +96,92 @@ export declare function syncAttachmentsComment(client: UploadsClient, target: Gh
|
|
|
42
96
|
action: "created" | "updated" | "skipped";
|
|
43
97
|
count: number;
|
|
44
98
|
}>;
|
|
99
|
+
export type AttachUploadItem = PutResult & {
|
|
100
|
+
file: string;
|
|
101
|
+
markdown: string;
|
|
102
|
+
optimize: {
|
|
103
|
+
optimized: boolean;
|
|
104
|
+
skippedReason?: OptimizeImageResult["skippedReason"];
|
|
105
|
+
originalBytes: number;
|
|
106
|
+
outputBytes: number;
|
|
107
|
+
filename: string;
|
|
108
|
+
};
|
|
109
|
+
frame?: PreparedUpload["frame"];
|
|
110
|
+
};
|
|
111
|
+
export type AttachFailure = {
|
|
112
|
+
file: string;
|
|
113
|
+
error: {
|
|
114
|
+
message: string;
|
|
115
|
+
code?: string;
|
|
116
|
+
status?: number;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Prepare + put each path as a PR/issue attachment with bounded concurrency.
|
|
121
|
+
* Per-file errors collect in `failures` (does not throw). `firstError` is the
|
|
122
|
+
* original cause of the first failure — for rethrowing single-file CLI paths.
|
|
123
|
+
*/
|
|
124
|
+
export declare function uploadAttachments(opts: {
|
|
125
|
+
client: UploadsClient;
|
|
126
|
+
target: GhTarget;
|
|
127
|
+
files: readonly string[];
|
|
128
|
+
contentType?: string;
|
|
129
|
+
optimize: OptimizeImageOptions;
|
|
130
|
+
frame: {
|
|
131
|
+
frameId?: string;
|
|
132
|
+
frameUrl?: string;
|
|
133
|
+
frameFit?: "cover" | "contain";
|
|
134
|
+
};
|
|
135
|
+
metadata?: Record<string, string>;
|
|
136
|
+
/** Provenance `client` field (default uploads-cli). */
|
|
137
|
+
provenanceClient?: string;
|
|
138
|
+
concurrency?: number;
|
|
139
|
+
}): Promise<{
|
|
140
|
+
uploads: AttachUploadItem[];
|
|
141
|
+
failures: AttachFailure[];
|
|
142
|
+
firstError?: unknown;
|
|
143
|
+
}>;
|
|
144
|
+
export type PutUploadItem = PutResult & {
|
|
145
|
+
file: string;
|
|
146
|
+
markdown: string;
|
|
147
|
+
optimize: AttachUploadItem["optimize"];
|
|
148
|
+
frame?: PreparedUpload["frame"];
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Prepare + put each path with put-style key resolution and bounded concurrency.
|
|
152
|
+
* Same partial-failure shape as uploadAttachments.
|
|
153
|
+
*/
|
|
154
|
+
export declare function uploadPuts(opts: {
|
|
155
|
+
client: UploadsClient;
|
|
156
|
+
files: readonly string[];
|
|
157
|
+
/** Single-file --name leaf override. */
|
|
158
|
+
nameOverride?: string;
|
|
159
|
+
/** Single-file --key. */
|
|
160
|
+
explicitKey?: string;
|
|
161
|
+
ghTarget?: GhTarget;
|
|
162
|
+
prefix?: string;
|
|
163
|
+
repo?: string;
|
|
164
|
+
ref?: string;
|
|
165
|
+
deriveRepoFromGit?: boolean;
|
|
166
|
+
contentType?: string;
|
|
167
|
+
dryRun?: boolean;
|
|
168
|
+
optimize: OptimizeImageOptions;
|
|
169
|
+
frame: {
|
|
170
|
+
frameId?: string;
|
|
171
|
+
frameUrl?: string;
|
|
172
|
+
frameFit?: "cover" | "contain";
|
|
173
|
+
};
|
|
174
|
+
metadata?: Record<string, string>;
|
|
175
|
+
provenanceClient?: string;
|
|
176
|
+
/** When set, used as alt for every file; else each file's basename. */
|
|
177
|
+
alt?: string;
|
|
178
|
+
width?: number;
|
|
179
|
+
concurrency?: number;
|
|
180
|
+
}): Promise<{
|
|
181
|
+
uploads: PutUploadItem[];
|
|
182
|
+
failures: AttachFailure[];
|
|
183
|
+
firstError?: unknown;
|
|
184
|
+
}>;
|
|
45
185
|
export declare function runAttach(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
46
186
|
export declare function runPut(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
|
|
47
187
|
export declare function runGallery(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|
|
@@ -84,7 +224,27 @@ export interface DoctorReport {
|
|
|
84
224
|
/** Workspace/token mismatch warning (also present in hints). */
|
|
85
225
|
warning?: string;
|
|
86
226
|
hints: string[];
|
|
227
|
+
/** `screenshot`'s local-browser detection (fs scans only — never launches a browser). */
|
|
228
|
+
browser: {
|
|
229
|
+
/** false when this runtime has no Node fs/process (e.g. the apps/mcp Worker). */
|
|
230
|
+
supported: boolean;
|
|
231
|
+
found: boolean;
|
|
232
|
+
/** Which backend `uploads screenshot --via auto` would pick right now. */
|
|
233
|
+
autoBackend: "local" | "remote";
|
|
234
|
+
candidates: {
|
|
235
|
+
source: string;
|
|
236
|
+
kind: string;
|
|
237
|
+
executablePath: string;
|
|
238
|
+
}[];
|
|
239
|
+
/** The best candidate by rank (may differ from candidates[0], which is scan order). */
|
|
240
|
+
winner?: {
|
|
241
|
+
source: string;
|
|
242
|
+
kind: string;
|
|
243
|
+
executablePath: string;
|
|
244
|
+
};
|
|
245
|
+
note?: string;
|
|
246
|
+
};
|
|
87
247
|
}
|
|
88
248
|
/** Doctor's health + auth + workspace checks, shared by the CLI and the MCP tool. */
|
|
89
|
-
export declare function buildDoctorReport(config: ResolvedConfig, client: UploadsClient): Promise<DoctorReport>;
|
|
249
|
+
export declare function buildDoctorReport(config: ResolvedConfig, client: UploadsClient, detectRoots?: DetectRoots): Promise<DoctorReport>;
|
|
90
250
|
export declare function runDoctor(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
|