@buildinternet/uploads 0.43.0 → 0.44.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/client.d.ts +35 -0
- package/dist/client.js +14 -0
- package/dist/commands/hook.d.ts +6 -2
- package/dist/commands/hook.js +31 -9
- package/dist/commands/screenshot.js +90 -20
- package/dist/commands.d.ts +76 -0
- package/dist/commands.js +313 -77
- package/dist/comment-config.generated.d.ts +3 -0
- package/dist/comment-config.generated.js +13 -0
- package/dist/comment-render.generated.d.ts +4 -1
- package/dist/comment-render.generated.js +15 -2
- package/dist/config-file.d.ts +4 -1
- package/dist/config-file.js +10 -0
- package/dist/mcp/output-schemas.js +24 -1
- package/dist/mcp/tools.js +132 -27
- package/package.json +1 -1
|
@@ -14,6 +14,9 @@ export const AUTO_COMMENT_OPTIONS = {
|
|
|
14
14
|
note: null,
|
|
15
15
|
ingestGithubAttachments: true,
|
|
16
16
|
ingestBotAttachments: false,
|
|
17
|
+
// Default ON for bound repos (issue #701) — binding is already an
|
|
18
|
+
// explicit opt-in relationship, same posture as ingestGithubAttachments.
|
|
19
|
+
adoptLinkedFiles: true,
|
|
17
20
|
};
|
|
18
21
|
export const NOTE_MAX_CHARS = 500;
|
|
19
22
|
const WIDTH_MIN = 160;
|
|
@@ -81,6 +84,14 @@ export function parseRepoCommentConfig(text, format) {
|
|
|
81
84
|
else
|
|
82
85
|
warnings.push(`ingestBotAttachments: expected a boolean; dropped`);
|
|
83
86
|
}
|
|
87
|
+
// adoptLinkedFiles: boolean
|
|
88
|
+
if ("adoptLinkedFiles" in c) {
|
|
89
|
+
const v = c.adoptLinkedFiles;
|
|
90
|
+
if (typeof v === "boolean")
|
|
91
|
+
config.adoptLinkedFiles = v;
|
|
92
|
+
else
|
|
93
|
+
warnings.push(`adoptLinkedFiles: expected a boolean; dropped`);
|
|
94
|
+
}
|
|
84
95
|
// meta.path / meta.state: booleans nested under `meta`
|
|
85
96
|
if ("meta" in c) {
|
|
86
97
|
const v = c.meta;
|
|
@@ -139,6 +150,7 @@ export function resolveCommentOptions(repo, ws) {
|
|
|
139
150
|
...(ws?.ingestBotAttachments !== undefined
|
|
140
151
|
? { ingestBotAttachments: ws.ingestBotAttachments }
|
|
141
152
|
: {}),
|
|
153
|
+
...(ws?.adoptLinkedFiles !== undefined ? { adoptLinkedFiles: ws.adoptLinkedFiles } : {}),
|
|
142
154
|
};
|
|
143
155
|
const options = { ...AUTO_COMMENT_OPTIONS };
|
|
144
156
|
const source = Object.fromEntries(Object.keys(AUTO_COMMENT_OPTIONS).map((k) => [k, "auto"]));
|
|
@@ -151,6 +163,7 @@ export function resolveCommentOptions(repo, ws) {
|
|
|
151
163
|
"linkToFilePage",
|
|
152
164
|
"ingestGithubAttachments",
|
|
153
165
|
"ingestBotAttachments",
|
|
166
|
+
"adoptLinkedFiles",
|
|
154
167
|
]) {
|
|
155
168
|
if (cfg[key] !== undefined && source[key] === "auto") {
|
|
156
169
|
options[key] = cfg[key];
|
|
@@ -147,5 +147,8 @@ export declare function attachmentImageWidth(filename: string, density?: Attachm
|
|
|
147
147
|
/**
|
|
148
148
|
* Render the one marker-owned GitHub comment. When there are no galleries this
|
|
149
149
|
* intentionally preserves the legacy attachment-only body byte-for-byte.
|
|
150
|
+
*
|
|
151
|
+
* `target` is optional so goldens, the settings preview, and other callers
|
|
152
|
+
* without a PR/issue stay on the generic `--pr <N>` hint.
|
|
150
153
|
*/
|
|
151
|
-
export declare function attachmentsCommentBody(items: AttachmentItem[], galleries?: GalleryCommentItem[], marker?: string, options?: CommentRenderOptions): string;
|
|
154
|
+
export declare function attachmentsCommentBody(items: AttachmentItem[], galleries?: GalleryCommentItem[], marker?: string, options?: CommentRenderOptions, target?: Pick<GhTarget, "kind" | "num">): string;
|
|
@@ -390,11 +390,24 @@ function countInlinableMedia(sorted, maxInlineImages) {
|
|
|
390
390
|
}
|
|
391
391
|
return count;
|
|
392
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Quiet add-media footer. When `target` is a real PR/issue, the command
|
|
395
|
+
* uses that number (`--pr 12` / `--issue 45`) so anyone reading the comment
|
|
396
|
+
* can copy it. Without a target the generic `--pr <N>` placeholder stays.
|
|
397
|
+
*/
|
|
398
|
+
function addMediaFooter(target) {
|
|
399
|
+
const flag = target?.kind === "issues" ? "--issue" : "--pr";
|
|
400
|
+
const n = target && Number.isInteger(target.num) && target.num > 0 ? String(target.num) : "<N>";
|
|
401
|
+
return `<sub>Maintained by <a href="https://uploads.sh">uploads.sh</a> · add media: <code>uploads put <file> ${flag} ${n}</code> · <a href="https://uploads.sh/docs/github-app">docs</a></sub>`;
|
|
402
|
+
}
|
|
393
403
|
/**
|
|
394
404
|
* Render the one marker-owned GitHub comment. When there are no galleries this
|
|
395
405
|
* intentionally preserves the legacy attachment-only body byte-for-byte.
|
|
406
|
+
*
|
|
407
|
+
* `target` is optional so goldens, the settings preview, and other callers
|
|
408
|
+
* without a PR/issue stay on the generic `--pr <N>` hint.
|
|
396
409
|
*/
|
|
397
|
-
export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMENTS_MARKER, options = AUTO_RENDER_OPTIONS) {
|
|
410
|
+
export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMENTS_MARKER, options = AUTO_RENDER_OPTIONS, target) {
|
|
398
411
|
// Non-mutating sort (equivalent to Array#toSorted) — the api worker's
|
|
399
412
|
// tsconfig targets lib ES2022, which predates Array#toSorted (ES2023);
|
|
400
413
|
// packages/uploads/src/github.ts uses toSorted directly.
|
|
@@ -529,6 +542,6 @@ export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMEN
|
|
|
529
542
|
// Footer condensed to a single quiet line — the old two-line explainer
|
|
530
543
|
// ("re-uploading updates everywhere", full add-media flags) repeats on
|
|
531
544
|
// every PR and lost value with each appearance; details live in the docs.
|
|
532
|
-
lines.push(
|
|
545
|
+
lines.push(addMediaFooter(target));
|
|
533
546
|
return lines.join("\n");
|
|
534
547
|
}
|
package/dist/config-file.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { UploadsClientConfig } from "./config.js";
|
|
2
2
|
export declare const UPLOADS_CONFIG_KEYS: readonly ["UPLOADS_API_URL", "UPLOADS_WORKSPACE", "UPLOADS_TOKEN",
|
|
3
3
|
/** Better Auth device-flow session bearer — keeps session.cliVersion fresh. */
|
|
4
|
-
"UPLOADS_SESSION_TOKEN", "UPLOADS_DEFAULT_PREFIX", "UPLOADS_DEFAULT_REPO", "UPLOADS_DEFAULT_REF", "UPLOADS_DEFAULT_WIDTH", "UPLOADS_NO_GIT", "UPLOADS_NO_OPTIMIZE", "UPLOADS_KEEP_EXIF", "UPLOADS_NO_AUTO_META", "UPLOADS_SCREENSHOT_VIA", "UPLOADS_NO_NUDGE"];
|
|
4
|
+
"UPLOADS_SESSION_TOKEN", "UPLOADS_DEFAULT_PREFIX", "UPLOADS_DEFAULT_REPO", "UPLOADS_DEFAULT_REF", "UPLOADS_DEFAULT_WIDTH", "UPLOADS_NO_GIT", "UPLOADS_NO_OPTIMIZE", "UPLOADS_KEEP_EXIF", "UPLOADS_NO_AUTO_META", "UPLOADS_SCREENSHOT_VIA", "UPLOADS_NO_NUDGE", "UPLOADS_NO_AUTO_PR"];
|
|
5
5
|
export type UploadsConfigKey = (typeof UPLOADS_CONFIG_KEYS)[number];
|
|
6
6
|
export type UploadsConfigValues = Partial<Record<UploadsConfigKey, string>>;
|
|
7
7
|
export interface PutDefaults {
|
|
@@ -18,6 +18,9 @@ export interface PutDefaults {
|
|
|
18
18
|
noAutoMeta?: boolean;
|
|
19
19
|
/** When true, `put` never prints the bare-put --pr/attach nudge (issue #393). */
|
|
20
20
|
noNudge?: boolean;
|
|
21
|
+
/** When true, a bare put/screenshot never auto-assumes an unambiguous open
|
|
22
|
+
* PR's context (issue #700) — falls back to the #403/#469 staging default. */
|
|
23
|
+
noAutoPr?: boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const PUT_DEFAULT_KEY_MAP: Record<keyof PutDefaults, UploadsConfigKey>;
|
|
23
26
|
export declare function putDefaultsToConfigValues(defaults: PutDefaults): UploadsConfigValues;
|
package/dist/config-file.js
CHANGED
|
@@ -17,6 +17,7 @@ export const UPLOADS_CONFIG_KEYS = [
|
|
|
17
17
|
"UPLOADS_NO_AUTO_META",
|
|
18
18
|
"UPLOADS_SCREENSHOT_VIA",
|
|
19
19
|
"UPLOADS_NO_NUDGE",
|
|
20
|
+
"UPLOADS_NO_AUTO_PR",
|
|
20
21
|
];
|
|
21
22
|
const PUT_DEFAULT_KEY_MAP = {
|
|
22
23
|
prefix: "UPLOADS_DEFAULT_PREFIX",
|
|
@@ -28,6 +29,7 @@ const PUT_DEFAULT_KEY_MAP = {
|
|
|
28
29
|
keepExif: "UPLOADS_KEEP_EXIF",
|
|
29
30
|
noAutoMeta: "UPLOADS_NO_AUTO_META",
|
|
30
31
|
noNudge: "UPLOADS_NO_NUDGE",
|
|
32
|
+
noAutoPr: "UPLOADS_NO_AUTO_PR",
|
|
31
33
|
};
|
|
32
34
|
function isTruthyConfigFlag(value) {
|
|
33
35
|
if (!value)
|
|
@@ -55,6 +57,8 @@ export function putDefaultsToConfigValues(defaults) {
|
|
|
55
57
|
out.UPLOADS_NO_AUTO_META = "1";
|
|
56
58
|
if (defaults.noNudge)
|
|
57
59
|
out.UPLOADS_NO_NUDGE = "1";
|
|
60
|
+
if (defaults.noAutoPr)
|
|
61
|
+
out.UPLOADS_NO_AUTO_PR = "1";
|
|
58
62
|
return out;
|
|
59
63
|
}
|
|
60
64
|
function parsePutDefaultsFromRaw(raw) {
|
|
@@ -80,6 +84,8 @@ function parsePutDefaultsFromRaw(raw) {
|
|
|
80
84
|
out.noAutoMeta = true;
|
|
81
85
|
if (isTruthyConfigFlag(raw.UPLOADS_NO_NUDGE))
|
|
82
86
|
out.noNudge = true;
|
|
87
|
+
if (isTruthyConfigFlag(raw.UPLOADS_NO_AUTO_PR))
|
|
88
|
+
out.noAutoPr = true;
|
|
83
89
|
return out;
|
|
84
90
|
}
|
|
85
91
|
function parsePutDefaultsFromEnv() {
|
|
@@ -102,6 +108,8 @@ function parsePutDefaultsFromEnv() {
|
|
|
102
108
|
raw.UPLOADS_NO_AUTO_META = process.env.UPLOADS_NO_AUTO_META;
|
|
103
109
|
if (process.env.UPLOADS_NO_NUDGE)
|
|
104
110
|
raw.UPLOADS_NO_NUDGE = process.env.UPLOADS_NO_NUDGE;
|
|
111
|
+
if (process.env.UPLOADS_NO_AUTO_PR)
|
|
112
|
+
raw.UPLOADS_NO_AUTO_PR = process.env.UPLOADS_NO_AUTO_PR;
|
|
105
113
|
return parsePutDefaultsFromRaw(raw);
|
|
106
114
|
}
|
|
107
115
|
/** XDG default shared across buildinternet skills (github-screenshots, uploads, …). */
|
|
@@ -175,6 +183,8 @@ export function mergePutDefaults(...layers) {
|
|
|
175
183
|
out.noAutoMeta = layer.noAutoMeta;
|
|
176
184
|
if (layer.noNudge != null)
|
|
177
185
|
out.noNudge = layer.noNudge;
|
|
186
|
+
if (layer.noAutoPr != null)
|
|
187
|
+
out.noAutoPr = layer.noAutoPr;
|
|
178
188
|
}
|
|
179
189
|
return out;
|
|
180
190
|
}
|
|
@@ -43,6 +43,25 @@ const promoteResultSchema = objectSchema({
|
|
|
43
43
|
]),
|
|
44
44
|
},
|
|
45
45
|
}, ["promoted", "skipped"]);
|
|
46
|
+
/** Hosted `promote` tool's `attached[]` entry (issue #702, `AttachExistingResponse`). */
|
|
47
|
+
const attachExistingResultSchema = objectSchema({
|
|
48
|
+
key: { type: "string" },
|
|
49
|
+
url: nullableString,
|
|
50
|
+
embedUrl: nullableString,
|
|
51
|
+
pageUrl: { type: "string" },
|
|
52
|
+
moved: { type: "boolean" },
|
|
53
|
+
source: objectSchema({ key: { type: "string" } }, ["key"]),
|
|
54
|
+
comment: commentResultSchema,
|
|
55
|
+
}, ["key", "url", "embedUrl", "moved", "source", "comment"]);
|
|
56
|
+
/** Hosted `promote` tool's `attachFailures[]` entry — one per bad `keys` source. */
|
|
57
|
+
const attachFailureSchema = objectSchema({
|
|
58
|
+
key: { type: "string" },
|
|
59
|
+
error: objectSchema({
|
|
60
|
+
message: { type: "string" },
|
|
61
|
+
code: { type: "string" },
|
|
62
|
+
status: { type: "number" },
|
|
63
|
+
}, ["message"]),
|
|
64
|
+
}, ["key", "error"]);
|
|
46
65
|
const putObjectFields = {
|
|
47
66
|
key: { type: "string" },
|
|
48
67
|
url: nullableString,
|
|
@@ -209,10 +228,14 @@ export const healthResultSchema = objectSchema({
|
|
|
209
228
|
apiUrl: { type: "string" },
|
|
210
229
|
});
|
|
211
230
|
export const promoteToolResultSchema = objectSchema({
|
|
231
|
+
// `promotion` is optional (issue #702): a `keys`-only call (no `branch`)
|
|
232
|
+
// never runs the branch sweep, so there's nothing to report under it.
|
|
212
233
|
promotion: promoteResultSchema,
|
|
234
|
+
attached: { type: "array", items: attachExistingResultSchema },
|
|
235
|
+
attachFailures: { type: "array", items: attachFailureSchema },
|
|
213
236
|
comment: commentResultSchema,
|
|
214
237
|
commentError: { type: "string" },
|
|
215
|
-
}
|
|
238
|
+
});
|
|
216
239
|
const galleryItemSchema = objectSchema({
|
|
217
240
|
id: { type: "string" },
|
|
218
241
|
objectKey: { type: "string" },
|
package/dist/mcp/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createUploadsClient } from "../client.js";
|
|
2
|
-
import { buildDoctorReport, ghListPrefixes, ghMergedList, makeGhTarget, mergeStagingMeta, resolveGhPrefixSafe, resolvePutStagingTarget, resolveStaged, syncAttachmentsComment, uploadAttachments, uploadPreparedImage, uploadPuts, } from "../commands.js";
|
|
2
|
+
import { buildDoctorReport, ghListPrefixes, ghMergedList, makeGhTarget, mergeStagingMeta, resolveAutoPrTarget, resolveGhPrefixSafe, resolvePutNudgeContext, resolvePutStagingTarget, resolveStaged, syncAttachmentsComment, autoPrNoteText, putNudgeText, uploadAttachments, uploadPreparedImage, uploadPuts, } from "../commands.js";
|
|
3
3
|
import { resolveFrameId } from "../frame.js";
|
|
4
4
|
import { resolveConfig, resolvePutDefaults, } from "../config.js";
|
|
5
5
|
import { resolvePutPrefix } from "../destinations.js";
|
|
@@ -345,9 +345,13 @@ export function createUploadsMcpTools(opts) {
|
|
|
345
345
|
},
|
|
346
346
|
...frameProps,
|
|
347
347
|
noGit: { type: "boolean", description: "Don't derive the repo segment from git." },
|
|
348
|
+
noPr: {
|
|
349
|
+
type: "boolean",
|
|
350
|
+
description: "Skip auto-PR context (issue #700): without pr/issue/key/ref/prefix/destination, a call on a branch mapping to exactly one open PR otherwise behaves as if pr had been passed (stable key + managed comment sync when comment is set). Also opts out via UPLOADS_NO_AUTO_PR=1.",
|
|
351
|
+
},
|
|
348
352
|
comment: {
|
|
349
353
|
type: "boolean",
|
|
350
|
-
description: "With pr/issue: create or update the managed attachments comment. Posts as uploads-sh[bot] when the GitHub App is installed on the repo; otherwise via local gh auth (best-effort).",
|
|
354
|
+
description: "With pr/issue (or auto-detected PR context): create or update the managed attachments comment. Posts as uploads-sh[bot] when the GitHub App is installed on the repo; otherwise via local gh auth (best-effort).",
|
|
351
355
|
},
|
|
352
356
|
dryRun: {
|
|
353
357
|
type: "boolean",
|
|
@@ -431,16 +435,39 @@ export function createUploadsMcpTools(opts) {
|
|
|
431
435
|
const frameOpts = mcpFrameOptions(args);
|
|
432
436
|
const optimizeOpts = mcpOptimizeOptions(args, defaults);
|
|
433
437
|
const noGit = optBool(args, "noGit") || defaults.noGit === true;
|
|
438
|
+
const noAutoPr = optBool(args, "noPr") || defaults.noAutoPr === true;
|
|
434
439
|
const alt = optString(args, "alt");
|
|
435
440
|
const width = optPosInt(args, "width") ?? defaults.width;
|
|
436
441
|
const contentType = optString(args, "contentType");
|
|
442
|
+
// Auto-PR context (issue #700): local stdio MCP put mirrors the CLI
|
|
443
|
+
// default — no pr/issue/key/ref/prefix/destination, not noGit/noPr,
|
|
444
|
+
// on a branch that maps to exactly one open PR behaves as if `pr`
|
|
445
|
+
// had been passed (stable key + managed comment sync) instead of
|
|
446
|
+
// the #403 staging default below. Never throws — see
|
|
447
|
+
// resolveAutoPrTarget.
|
|
448
|
+
const autoPrTarget = target
|
|
449
|
+
? undefined
|
|
450
|
+
: resolveAutoPrTarget({
|
|
451
|
+
ghTarget: target,
|
|
452
|
+
keyHint: keyArg,
|
|
453
|
+
refArg,
|
|
454
|
+
prefixArg,
|
|
455
|
+
destinationArg: destArg,
|
|
456
|
+
noGit,
|
|
457
|
+
noAutoPr,
|
|
458
|
+
repoArg: optString(args, "repo") ?? defaults.repo,
|
|
459
|
+
run,
|
|
460
|
+
});
|
|
461
|
+
const effectiveTarget = target ?? autoPrTarget;
|
|
437
462
|
// Bare-put branch staging (issue #403): local stdio MCP put mirrors
|
|
438
463
|
// the CLI default — no pr/issue/key/ref/prefix/destination, not
|
|
439
464
|
// noGit, on a non-default git branch stages to the branch prefix
|
|
440
465
|
// (identical key/metadata to `attach --branch`) instead of the
|
|
441
466
|
// dated layout. Never throws — see resolvePutStagingTarget.
|
|
467
|
+
// effectiveTarget (explicit pr/issue OR the #700 auto-PR match)
|
|
468
|
+
// wins over staging, same as it wins over the dated layout.
|
|
442
469
|
const stagingTarget = resolvePutStagingTarget({
|
|
443
|
-
ghTarget:
|
|
470
|
+
ghTarget: effectiveTarget,
|
|
444
471
|
keyHint: keyArg,
|
|
445
472
|
refArg,
|
|
446
473
|
prefixArg,
|
|
@@ -449,6 +476,21 @@ export function createUploadsMcpTools(opts) {
|
|
|
449
476
|
repoArg: optString(args, "repo") ?? defaults.repo,
|
|
450
477
|
run,
|
|
451
478
|
});
|
|
479
|
+
// Bare-put nudge context (issue #393/#700): only relevant when
|
|
480
|
+
// neither auto-PR nor staging took over. Finished into a hint once
|
|
481
|
+
// upload keys exist, below.
|
|
482
|
+
const nudgeCtx = effectiveTarget || stagingTarget
|
|
483
|
+
? undefined
|
|
484
|
+
: resolvePutNudgeContext({
|
|
485
|
+
quiet: false,
|
|
486
|
+
noNudge: defaults.noNudge === true,
|
|
487
|
+
ghTarget: target,
|
|
488
|
+
keyHint: keyArg,
|
|
489
|
+
noGit,
|
|
490
|
+
repoArg: optString(args, "repo") ?? defaults.repo,
|
|
491
|
+
run,
|
|
492
|
+
});
|
|
493
|
+
const autoPrHint = autoPrTarget ? autoPrNoteText(autoPrTarget.num) : undefined;
|
|
452
494
|
// Derived `repo` metadata (spec: 2026-08-11-screenshots-project-grouping-design.md).
|
|
453
495
|
// Same derivation the CLI does; MCP always derives (no --no-auto), so
|
|
454
496
|
// this is only suppressed by noGit. metadataProp's contract: omitting
|
|
@@ -468,7 +510,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
468
510
|
: metadataWithRepo;
|
|
469
511
|
const putShared = {
|
|
470
512
|
client,
|
|
471
|
-
ghTarget:
|
|
513
|
+
ghTarget: effectiveTarget,
|
|
472
514
|
ghBranchTarget: stagingTarget,
|
|
473
515
|
prefix: resolvedPrefix ?? defaults.prefix,
|
|
474
516
|
repo: optString(args, "repo") ?? defaults.repo,
|
|
@@ -496,11 +538,15 @@ export function createUploadsMcpTools(opts) {
|
|
|
496
538
|
if (uploads.length === 0 && failures.length > 0) {
|
|
497
539
|
throw new ToolBatchError(batchFailureMessage(failures), { uploads, failures });
|
|
498
540
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
541
|
+
const hint = autoPrHint ??
|
|
542
|
+
(nudgeCtx && uploads.length > 0
|
|
543
|
+
? putNudgeText(nudgeCtx.branch, nudgeCtx.pr, uploads.map((u) => u.key))
|
|
544
|
+
: undefined);
|
|
545
|
+
if (wantComment && effectiveTarget && uploads.length > 0) {
|
|
546
|
+
const { comment, commentError } = await syncComment(client, effectiveTarget, config.workspace);
|
|
547
|
+
return { uploads, failures, comment, commentError, ...(hint ? { hint } : {}) };
|
|
502
548
|
}
|
|
503
|
-
return { uploads, failures };
|
|
549
|
+
return { uploads, failures, ...(hint ? { hint } : {}) };
|
|
504
550
|
}
|
|
505
551
|
// Single-file: contentBase64 still supported; paths go through uploadPuts.
|
|
506
552
|
if (contentBase64 !== undefined) {
|
|
@@ -509,7 +555,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
509
555
|
const { result, prepared, markdown } = await uploadPreparedImage(client, bytes, sourceName, {
|
|
510
556
|
frame: frameOpts,
|
|
511
557
|
optimize: optimizeOpts,
|
|
512
|
-
ghTarget:
|
|
558
|
+
ghTarget: effectiveTarget,
|
|
513
559
|
ghBranchTarget: stagingTarget,
|
|
514
560
|
key: keyArg,
|
|
515
561
|
prefix: resolvedPrefix ?? defaults.prefix,
|
|
@@ -531,9 +577,19 @@ export function createUploadsMcpTools(opts) {
|
|
|
531
577
|
outputBytes: prepared.outputBytes,
|
|
532
578
|
filename: prepared.filename,
|
|
533
579
|
};
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
580
|
+
const hint = autoPrHint ??
|
|
581
|
+
(nudgeCtx ? putNudgeText(nudgeCtx.branch, nudgeCtx.pr, [result.key]) : undefined);
|
|
582
|
+
if (wantComment && effectiveTarget) {
|
|
583
|
+
const { comment, commentError } = await syncComment(client, effectiveTarget, config.workspace);
|
|
584
|
+
return {
|
|
585
|
+
...result,
|
|
586
|
+
markdown,
|
|
587
|
+
optimize,
|
|
588
|
+
frame: prepared.frame,
|
|
589
|
+
comment,
|
|
590
|
+
commentError,
|
|
591
|
+
...(hint ? { hint } : {}),
|
|
592
|
+
};
|
|
537
593
|
}
|
|
538
594
|
return {
|
|
539
595
|
...result,
|
|
@@ -541,6 +597,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
541
597
|
optimize,
|
|
542
598
|
frame: prepared.frame,
|
|
543
599
|
...(dryRun ? { dryRun: true } : {}),
|
|
600
|
+
...(hint ? { hint } : {}),
|
|
544
601
|
};
|
|
545
602
|
}
|
|
546
603
|
const { uploads, failures, firstError } = await uploadPuts({
|
|
@@ -553,6 +610,8 @@ export function createUploadsMcpTools(opts) {
|
|
|
553
610
|
throw firstError instanceof Error ? firstError : new Error(String(firstError));
|
|
554
611
|
}
|
|
555
612
|
const u = uploads[0];
|
|
613
|
+
const hint = autoPrHint ??
|
|
614
|
+
(nudgeCtx ? putNudgeText(nudgeCtx.branch, nudgeCtx.pr, [u.key]) : undefined);
|
|
556
615
|
const flat = {
|
|
557
616
|
workspace: u.workspace,
|
|
558
617
|
key: u.key,
|
|
@@ -566,9 +625,10 @@ export function createUploadsMcpTools(opts) {
|
|
|
566
625
|
optimize: u.optimize,
|
|
567
626
|
frame: u.frame,
|
|
568
627
|
...(dryRun ? { dryRun: true } : {}),
|
|
628
|
+
...(hint ? { hint } : {}),
|
|
569
629
|
};
|
|
570
|
-
if (wantComment &&
|
|
571
|
-
const { comment, commentError } = await syncComment(client,
|
|
630
|
+
if (wantComment && effectiveTarget) {
|
|
631
|
+
const { comment, commentError } = await syncComment(client, effectiveTarget, config.workspace);
|
|
572
632
|
return { ...flat, comment, commentError };
|
|
573
633
|
}
|
|
574
634
|
return flat;
|
|
@@ -673,9 +733,13 @@ export function createUploadsMcpTools(opts) {
|
|
|
673
733
|
keepExif: { type: "boolean", description: "Keep EXIF/XMP/ICC when optimizing." },
|
|
674
734
|
...frameProps,
|
|
675
735
|
noGit: { type: "boolean", description: "Don't derive the repo segment from git." },
|
|
736
|
+
noPr: {
|
|
737
|
+
type: "boolean",
|
|
738
|
+
description: "Skip auto-PR context (issue #700): without pr/issue/key/ref/prefix/destination, a call on a branch mapping to exactly one open PR otherwise behaves as if pr had been passed. Also opts out via UPLOADS_NO_AUTO_PR=1.",
|
|
739
|
+
},
|
|
676
740
|
comment: {
|
|
677
741
|
type: "boolean",
|
|
678
|
-
description: "With pr/issue: create/update the managed attachments comment (best-effort).",
|
|
742
|
+
description: "With pr/issue (or auto-detected PR context): create/update the managed attachments comment (best-effort).",
|
|
679
743
|
},
|
|
680
744
|
galleryId: {
|
|
681
745
|
type: "string",
|
|
@@ -741,16 +805,38 @@ export function createUploadsMcpTools(opts) {
|
|
|
741
805
|
const frameOpts = mcpFrameOptions(args);
|
|
742
806
|
const optimizeOpts = mcpOptimizeOptions(args, defaults);
|
|
743
807
|
const noGit = optBool(args, "noGit") || defaults.noGit === true;
|
|
808
|
+
const noAutoPr = optBool(args, "noPr") || defaults.noAutoPr === true;
|
|
744
809
|
const alt = optString(args, "alt");
|
|
745
810
|
const width = optPosInt(args, "width") ?? defaults.width;
|
|
811
|
+
// Auto-PR context (issue #700): mirrors the CLI screenshot command
|
|
812
|
+
// and the put tool above — no pr/issue/key/ref/prefix/destination,
|
|
813
|
+
// not noGit/noPr, on a branch that maps to exactly one open PR
|
|
814
|
+
// behaves as if `pr` had been passed (stable key + managed comment
|
|
815
|
+
// sync) instead of the #469 auto-staging default below. Never
|
|
816
|
+
// throws — see resolveAutoPrTarget.
|
|
817
|
+
const autoPrTarget = target
|
|
818
|
+
? undefined
|
|
819
|
+
: resolveAutoPrTarget({
|
|
820
|
+
ghTarget: target,
|
|
821
|
+
keyHint: keyArg,
|
|
822
|
+
refArg,
|
|
823
|
+
prefixArg,
|
|
824
|
+
destinationArg: destArg,
|
|
825
|
+
noGit,
|
|
826
|
+
noAutoPr,
|
|
827
|
+
repoArg: optString(args, "repo") ?? defaults.repo,
|
|
828
|
+
run,
|
|
829
|
+
});
|
|
830
|
+
const effectiveTarget = target ?? autoPrTarget;
|
|
746
831
|
// Auto branch staging (issue #469 lever 1): mirrors the CLI screenshot
|
|
747
832
|
// command and the put tool above (issue #403) — no pr/issue/key/ref/
|
|
748
833
|
// prefix/destination, not noGit, on a non-default git branch stages
|
|
749
834
|
// to the branch prefix (identical key/metadata to `attach --branch`)
|
|
750
835
|
// instead of the dated `screenshots/<repo>/<date>/...` layout. Never
|
|
751
|
-
// throws — see resolvePutStagingTarget.
|
|
836
|
+
// throws — see resolvePutStagingTarget. effectiveTarget (explicit
|
|
837
|
+
// pr/issue OR the #700 auto-PR match) wins over staging.
|
|
752
838
|
const stagingTarget = resolvePutStagingTarget({
|
|
753
|
-
ghTarget:
|
|
839
|
+
ghTarget: effectiveTarget,
|
|
754
840
|
keyHint: keyArg,
|
|
755
841
|
refArg,
|
|
756
842
|
prefixArg,
|
|
@@ -759,13 +845,27 @@ export function createUploadsMcpTools(opts) {
|
|
|
759
845
|
repoArg: optString(args, "repo") ?? defaults.repo,
|
|
760
846
|
run,
|
|
761
847
|
});
|
|
848
|
+
// Bare-screenshot nudge context (issue #393/#700): only relevant
|
|
849
|
+
// when neither auto-PR nor staging took over.
|
|
850
|
+
const nudgeCtx = effectiveTarget || stagingTarget
|
|
851
|
+
? undefined
|
|
852
|
+
: resolvePutNudgeContext({
|
|
853
|
+
quiet: false,
|
|
854
|
+
noNudge: defaults.noNudge === true,
|
|
855
|
+
ghTarget: target,
|
|
856
|
+
keyHint: keyArg,
|
|
857
|
+
noGit,
|
|
858
|
+
repoArg: optString(args, "repo") ?? defaults.repo,
|
|
859
|
+
run,
|
|
860
|
+
});
|
|
861
|
+
const autoPrHint = autoPrTarget ? autoPrNoteText(autoPrTarget.num) : undefined;
|
|
762
862
|
let resolvedPrefix;
|
|
763
863
|
try {
|
|
764
864
|
resolvedPrefix = resolvePutPrefix({
|
|
765
865
|
destination: destArg,
|
|
766
866
|
prefix: prefixArg,
|
|
767
867
|
key: keyArg,
|
|
768
|
-
ghAttachment: Boolean(
|
|
868
|
+
ghAttachment: Boolean(effectiveTarget) || stagingTarget !== undefined,
|
|
769
869
|
});
|
|
770
870
|
}
|
|
771
871
|
catch (err) {
|
|
@@ -837,10 +937,10 @@ export function createUploadsMcpTools(opts) {
|
|
|
837
937
|
}
|
|
838
938
|
// Resolved once (issue #631), only now that upload is actually
|
|
839
939
|
// about to happen — never per file (screenshot uploads exactly one).
|
|
840
|
-
const ghPrefix =
|
|
940
|
+
const ghPrefix = effectiveTarget
|
|
841
941
|
? await resolveGhPrefixSafe(client, {
|
|
842
|
-
repo:
|
|
843
|
-
target: { kind:
|
|
942
|
+
repo: effectiveTarget.repo,
|
|
943
|
+
target: { kind: effectiveTarget.kind, num: effectiveTarget.num },
|
|
844
944
|
})
|
|
845
945
|
: stagingTarget
|
|
846
946
|
? await resolveGhPrefixSafe(client, {
|
|
@@ -851,7 +951,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
851
951
|
const { result, prepared, markdown } = await uploadPreparedImage(client, captured.png, captured.filename, {
|
|
852
952
|
frame: frameOpts,
|
|
853
953
|
optimize: optimizeOpts,
|
|
854
|
-
ghTarget:
|
|
954
|
+
ghTarget: effectiveTarget,
|
|
855
955
|
ghBranchTarget: stagingTarget,
|
|
856
956
|
ghPrefix,
|
|
857
957
|
key: keyArg,
|
|
@@ -897,14 +997,19 @@ export function createUploadsMcpTools(opts) {
|
|
|
897
997
|
frame: prepared.frame,
|
|
898
998
|
gallery,
|
|
899
999
|
...(dryRun ? { dryRun: true } : {}),
|
|
900
|
-
//
|
|
901
|
-
//
|
|
1000
|
+
// Hint precedence (mirrors the CLI): the full-page height cap note
|
|
1001
|
+
// (issue #652) is about the just-captured image itself, more
|
|
1002
|
+
// immediately actionable than the #700 auto-PR/nudge notes below.
|
|
902
1003
|
...(captured.capped?.clipped
|
|
903
1004
|
? { hint: screenshotModule.clipHintText(captured.capped.maxHeightPx, "maxHeight") }
|
|
904
|
-
:
|
|
1005
|
+
: autoPrHint
|
|
1006
|
+
? { hint: autoPrHint }
|
|
1007
|
+
: nudgeCtx
|
|
1008
|
+
? { hint: putNudgeText(nudgeCtx.branch, nudgeCtx.pr, [result.key]) }
|
|
1009
|
+
: {}),
|
|
905
1010
|
};
|
|
906
|
-
if (wantComment &&
|
|
907
|
-
const { comment, commentError } = await syncComment(client,
|
|
1011
|
+
if (wantComment && effectiveTarget) {
|
|
1012
|
+
const { comment, commentError } = await syncComment(client, effectiveTarget, config.workspace);
|
|
908
1013
|
return { ...flat, comment, commentError };
|
|
909
1014
|
}
|
|
910
1015
|
return flat;
|