@buildinternet/uploads 0.14.0 → 0.16.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.
@@ -9,6 +9,8 @@ export declare const execRunner: CommandRunner;
9
9
  export declare function resolveRepo(explicit: string | undefined, run?: CommandRunner): string;
10
10
  /** Resolve the pull request associated with the current branch. */
11
11
  export declare function resolveCurrentPullRequest(repo: string, run?: CommandRunner): GhTarget;
12
+ /** Resolve the current git branch (`--branch` with no value). Throws UsageError on detached HEAD or outside a git repo. */
13
+ export declare function resolveCurrentBranch(run?: CommandRunner): string;
12
14
  /**
13
15
  * Classify a bare PR/issue number via the GitHub API so the default `put`
14
16
  * path can stamp the right `gh.kind`. Returns undefined on any failure (gh
@@ -39,7 +41,13 @@ export declare function ghMetadataFromTargetWithTitle(target: GhTarget, run?: Co
39
41
  * Create the managed attachments comment, or edit it in place if it already
40
42
  * exists. Never touches any other comment. Body is passed via stdin
41
43
  * (`-F body=@-`) so it is never shell-interpolated.
44
+ *
45
+ * `marker` identifies which comment to hunt for (see `findManagedComment`);
46
+ * `body` is expected to already carry that same marker as its first line
47
+ * (built via `attachmentsCommentBody(items, galleries, marker)`), so patching
48
+ * an adopted legacy comment migrates it to the namespaced marker in place.
49
+ * Defaults to the shared legacy marker for backward compatibility.
42
50
  */
43
- export declare function upsertAttachmentsComment(target: GhTarget, body: string, run?: CommandRunner): {
51
+ export declare function upsertAttachmentsComment(target: GhTarget, body: string, run?: CommandRunner, marker?: string): {
44
52
  created: boolean;
45
53
  };
package/dist/github-gh.js CHANGED
@@ -68,6 +68,20 @@ export function resolveCurrentPullRequest(repo, run = execRunner) {
68
68
  }
69
69
  throw new UsageError("could not infer a pull request for the current branch — pass --pr <num> or --issue <num>");
70
70
  }
71
+ /** Resolve the current git branch (`--branch` with no value). Throws UsageError on detached HEAD or outside a git repo. */
72
+ export function resolveCurrentBranch(run = execRunner) {
73
+ let branch;
74
+ try {
75
+ branch = run("git", ["rev-parse", "--abbrev-ref", "HEAD"]).trim();
76
+ }
77
+ catch {
78
+ throw new UsageError("could not determine the current git branch — pass --branch <name> or run inside a git repo");
79
+ }
80
+ if (branch === "" || branch === "HEAD") {
81
+ throw new UsageError("could not determine the current branch (detached HEAD) — pass --branch <name>");
82
+ }
83
+ return branch;
84
+ }
71
85
  /**
72
86
  * Classify a bare PR/issue number via the GitHub API so the default `put`
73
87
  * path can stamp the right `gh.kind`. Returns undefined on any failure (gh
@@ -141,23 +155,41 @@ export function ghMetadataFromTargetWithTitle(target, run = execRunner) {
141
155
  * PR comments live on the issues endpoint, so one path covers PRs and issues.
142
156
  * `--paginate` follows Link headers and merges every page into one array, so the
143
157
  * marker comment is found even on threads past 100 comments.
158
+ *
159
+ * Hunts for `marker` (the namespaced, per-workspace marker) first; when none
160
+ * is found, falls back to a comment carrying the shared legacy
161
+ * `ATTACHMENTS_MARKER` (pre-4b, unnamespaced) so it can be adopted and
162
+ * migrated in place. When `marker` IS the legacy marker (no workspace to
163
+ * namespace with) this collapses to a single hunt, unchanged from pre-4b
164
+ * behavior.
144
165
  */
145
- function findManagedComment(target, run) {
166
+ function findManagedComment(target, run, marker) {
146
167
  const raw = run("gh", [
147
168
  "api",
148
169
  `repos/${target.repo}/issues/${target.num}/comments?per_page=100`,
149
170
  "--paginate",
150
171
  ]);
151
172
  const comments = JSON.parse(raw);
173
+ const namespacedHit = comments.find((c) => typeof c.body === "string" && c.body.includes(marker));
174
+ if (namespacedHit)
175
+ return namespacedHit;
176
+ if (marker === ATTACHMENTS_MARKER)
177
+ return undefined;
152
178
  return comments.find((c) => typeof c.body === "string" && c.body.includes(ATTACHMENTS_MARKER));
153
179
  }
154
180
  /**
155
181
  * Create the managed attachments comment, or edit it in place if it already
156
182
  * exists. Never touches any other comment. Body is passed via stdin
157
183
  * (`-F body=@-`) so it is never shell-interpolated.
184
+ *
185
+ * `marker` identifies which comment to hunt for (see `findManagedComment`);
186
+ * `body` is expected to already carry that same marker as its first line
187
+ * (built via `attachmentsCommentBody(items, galleries, marker)`), so patching
188
+ * an adopted legacy comment migrates it to the namespaced marker in place.
189
+ * Defaults to the shared legacy marker for backward compatibility.
158
190
  */
159
- export function upsertAttachmentsComment(target, body, run = execRunner) {
160
- const existing = findManagedComment(target, run);
191
+ export function upsertAttachmentsComment(target, body, run = execRunner, marker = ATTACHMENTS_MARKER) {
192
+ const existing = findManagedComment(target, run, marker);
161
193
  if (existing) {
162
194
  run("gh", [
163
195
  "api",
package/dist/github.d.ts CHANGED
@@ -22,6 +22,26 @@ export declare function ghKeyPrefix(target: GhTarget): string;
22
22
  * (unlike buildScreenshotKey).
23
23
  */
24
24
  export declare function ghAttachmentKey(target: GhTarget, filename: string): string;
25
+ /**
26
+ * Branch-staged attachment key prefix: `gh/<owner>/<repo>/branch/<branch>/`.
27
+ * Pre-PR staging (Phase 1 of branch-staged attachments) — no PR/issue number
28
+ * exists yet. `branch` goes through `sanitizeKeySegment` like every other key
29
+ * segment, so e.g. `feature/x` becomes `feature-x`.
30
+ */
31
+ export declare function ghBranchKeyPrefix(repo: string, branch: string): string;
32
+ /** Branch-staged attachment key: `ghBranchKeyPrefix` + the sanitized filename. */
33
+ export declare function ghBranchAttachmentKey(repo: string, branch: string, filename: string): string;
34
+ /**
35
+ * `gh.*` metadata for a branch-staged attach: `gh.repo`, `gh.kind=branch`,
36
+ * `gh.branch` (lowercased), and `gh.staged-at` (ISO 8601 UTC, no fractional
37
+ * seconds). No `gh.number`/`gh.ref`/`gh.title` — there is no PR/issue yet.
38
+ * Throws `UsageError` when the lowercased branch name fails the metadata
39
+ * value rule (printable ASCII, 1-512 chars) — unlike `gh.title`'s best-effort
40
+ * degrade, a branch name the caller explicitly chose (via `--branch` or the
41
+ * current git branch) is worth failing loudly on rather than silently
42
+ * dropping from the metadata set.
43
+ */
44
+ export declare function ghMetadataForBranch(repo: string, branch: string, now?: Date): Record<string, string>;
25
45
  /**
26
46
  * The four `gh.*` queryable-metadata pairs `uploads attach` writes
27
47
  * automatically (`.context/2026-07-13-file-metadata-design.md`). `gh.kind`
@@ -36,11 +56,25 @@ export declare function ghAttachmentKey(target: GhTarget, filename: string): str
36
56
  export declare function ghMetadataFromTarget(target: GhTarget): Record<string, string>;
37
57
  /** Hidden marker identifying the one comment this CLI manages. Never change it — existing comments are found by exact match. */
38
58
  export declare const ATTACHMENTS_MARKER = "<!-- uploads.sh:attachments -->";
59
+ /**
60
+ * Per-workspace marker (`<!-- uploads.sh:attachments ws=<workspace> -->`) so
61
+ * two workspaces managing the same repo don't clobber each other's comment.
62
+ * Falls back to the shared legacy marker when `workspace` is missing or does
63
+ * not look like a safe slug — degrade, don't guess or risk breaking the
64
+ * comment's HTML.
65
+ */
66
+ export declare function attachmentsMarker(workspace?: string): string;
67
+ /** Max attachments embedded as inline `<img>` tags before the rest collapse
68
+ * into a `<details>` link list. Keeps very large threads from becoming a wall
69
+ * of images. */
70
+ export declare const MAX_INLINE_ATTACHMENT_IMAGES = 16;
39
71
  export interface AttachmentItem {
40
72
  key: string;
41
73
  url: string | null;
42
74
  /** Prefer for `<img src>` on GitHub (Camo-friendly host). Falls back to `url`. */
43
75
  embedUrl?: string | null;
76
+ /** Canonical `/f/` file-page URL (server-computed). Preferred click-through target; falls back to `url`. */
77
+ pageUrl?: string | null;
44
78
  }
45
79
  /** A public gallery linked to the PR or issue whose managed comment is syncing. */
46
80
  export interface GalleryCommentItem {
@@ -70,4 +104,4 @@ export declare function attachmentImageWidth(filename: string): number;
70
104
  * Render the one marker-owned GitHub comment. When there are no galleries this
71
105
  * intentionally preserves the legacy attachment-only body byte-for-byte.
72
106
  */
73
- export declare function attachmentsCommentBody(items: AttachmentItem[], galleries?: GalleryCommentItem[]): string;
107
+ export declare function attachmentsCommentBody(items: AttachmentItem[], galleries?: GalleryCommentItem[], marker?: string): string;
package/dist/github.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { inferContentType } from "./embed.js";
2
2
  import { sanitizeKeySegment } from "./keys.js";
3
+ import { UsageError } from "./cli-args.js";
4
+ import { isMetaValueSafe } from "./metadata.js";
3
5
  const REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
4
6
  export function isValidRepo(repo) {
5
7
  return REPO_RE.test(repo);
@@ -63,6 +65,42 @@ export function ghKeyPrefix(target) {
63
65
  export function ghAttachmentKey(target, filename) {
64
66
  return `${ghKeyPrefix(target)}${sanitizeKeySegment(filename)}`;
65
67
  }
68
+ /**
69
+ * Branch-staged attachment key prefix: `gh/<owner>/<repo>/branch/<branch>/`.
70
+ * Pre-PR staging (Phase 1 of branch-staged attachments) — no PR/issue number
71
+ * exists yet. `branch` goes through `sanitizeKeySegment` like every other key
72
+ * segment, so e.g. `feature/x` becomes `feature-x`.
73
+ */
74
+ export function ghBranchKeyPrefix(repo, branch) {
75
+ const [owner, name] = repo.split("/");
76
+ return `gh/${sanitizeKeySegment(owner)}/${sanitizeKeySegment(name)}/branch/${sanitizeKeySegment(branch)}/`;
77
+ }
78
+ /** Branch-staged attachment key: `ghBranchKeyPrefix` + the sanitized filename. */
79
+ export function ghBranchAttachmentKey(repo, branch, filename) {
80
+ return `${ghBranchKeyPrefix(repo, branch)}${sanitizeKeySegment(filename)}`;
81
+ }
82
+ /**
83
+ * `gh.*` metadata for a branch-staged attach: `gh.repo`, `gh.kind=branch`,
84
+ * `gh.branch` (lowercased), and `gh.staged-at` (ISO 8601 UTC, no fractional
85
+ * seconds). No `gh.number`/`gh.ref`/`gh.title` — there is no PR/issue yet.
86
+ * Throws `UsageError` when the lowercased branch name fails the metadata
87
+ * value rule (printable ASCII, 1-512 chars) — unlike `gh.title`'s best-effort
88
+ * degrade, a branch name the caller explicitly chose (via `--branch` or the
89
+ * current git branch) is worth failing loudly on rather than silently
90
+ * dropping from the metadata set.
91
+ */
92
+ export function ghMetadataForBranch(repo, branch, now = new Date()) {
93
+ const branchLower = branch.toLowerCase();
94
+ if (!isMetaValueSafe(branchLower)) {
95
+ throw new UsageError(`invalid --branch: "${branch}" must be printable ASCII to stage as gh.branch metadata`);
96
+ }
97
+ return {
98
+ "gh.repo": repo.toLowerCase(),
99
+ "gh.kind": "branch",
100
+ "gh.branch": branchLower,
101
+ "gh.staged-at": now.toISOString().replace(/\.\d{3}Z$/, "Z"),
102
+ };
103
+ }
66
104
  /**
67
105
  * The four `gh.*` queryable-metadata pairs `uploads attach` writes
68
106
  * automatically (`.context/2026-07-13-file-metadata-design.md`). `gh.kind`
@@ -85,6 +123,28 @@ export function ghMetadataFromTarget(target) {
85
123
  }
86
124
  /** Hidden marker identifying the one comment this CLI manages. Never change it — existing comments are found by exact match. */
87
125
  export const ATTACHMENTS_MARKER = "<!-- uploads.sh:attachments -->";
126
+ /** Workspace slugs are `[a-z0-9-]`-ish; only markers built from a slug matching
127
+ * this are trusted as a distinct namespace — anything else (empty, unsafe
128
+ * chars) degrades to the shared legacy marker rather than emitting untrusted
129
+ * text into comment HTML. */
130
+ const WORKSPACE_SLUG_RE = /^[a-z0-9-]{1,64}$/;
131
+ /**
132
+ * Per-workspace marker (`<!-- uploads.sh:attachments ws=<workspace> -->`) so
133
+ * two workspaces managing the same repo don't clobber each other's comment.
134
+ * Falls back to the shared legacy marker when `workspace` is missing or does
135
+ * not look like a safe slug — degrade, don't guess or risk breaking the
136
+ * comment's HTML.
137
+ */
138
+ export function attachmentsMarker(workspace) {
139
+ if (workspace && WORKSPACE_SLUG_RE.test(workspace)) {
140
+ return `<!-- uploads.sh:attachments ws=${workspace} -->`;
141
+ }
142
+ return ATTACHMENTS_MARKER;
143
+ }
144
+ /** Max attachments embedded as inline `<img>` tags before the rest collapse
145
+ * into a `<details>` link list. Keeps very large threads from becoming a wall
146
+ * of images. */
147
+ export const MAX_INLINE_ATTACHMENT_IMAGES = 16;
88
148
  /** Default max width for images in the managed attachments comment (HTML img). */
89
149
  export const ATTACHMENT_IMAGE_WIDTH_DEFAULT = 400;
90
150
  /** Portrait / device mockups — keep phones readable, not full-column. */
@@ -116,10 +176,10 @@ function escapeHtmlText(s) {
116
176
  * Render the one marker-owned GitHub comment. When there are no galleries this
117
177
  * intentionally preserves the legacy attachment-only body byte-for-byte.
118
178
  */
119
- export function attachmentsCommentBody(items, galleries = []) {
179
+ export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMENTS_MARKER) {
120
180
  const sorted = items.toSorted((a, b) => a.key.localeCompare(b.key));
121
181
  const sortedGalleries = galleries.toSorted((a, b) => a.title.localeCompare(b.title) || a.url.localeCompare(b.url));
122
- const lines = [ATTACHMENTS_MARKER];
182
+ const lines = [marker];
123
183
  if (sortedGalleries.length > 0) {
124
184
  lines.push("### 🖼️ Galleries", "");
125
185
  for (const gallery of sortedGalleries) {
@@ -136,27 +196,48 @@ export function attachmentsCommentBody(items, galleries = []) {
136
196
  }
137
197
  if (sorted.length > 0 || sortedGalleries.length === 0)
138
198
  lines.push("### 📎 Attachments", "");
199
+ let inlinedImages = 0;
200
+ const overflowImages = [];
139
201
  for (const item of sorted) {
140
202
  const name = item.key.slice(item.key.lastIndexOf("/") + 1);
141
203
  const stable = item.url;
142
204
  const src = item.embedUrl ?? item.url;
143
- if (src && inferContentType(name).startsWith("image/")) {
205
+ const link = item.pageUrl ?? stable; // click-through: file page when known, else raw
206
+ const isImage = Boolean(src) && inferContentType(name).startsWith("image/");
207
+ if (isImage && inlinedImages >= MAX_INLINE_ATTACHMENT_IMAGES) {
208
+ // Cap hit — defer to the collapsed overflow list below rather than
209
+ // embedding every remaining image inline.
210
+ overflowImages.push(item);
211
+ continue;
212
+ }
213
+ if (isImage) {
214
+ inlinedImages++;
144
215
  // Markdown ![]() has no width control — phone frames become full-column giants.
145
- // img src uses embed host when available (Camo revalidates); click-through keeps stable URL.
216
+ // img src uses embed host when available (Camo revalidates); click-through prefers the file page.
146
217
  const w = attachmentImageWidth(name);
147
218
  const alt = escapeHtmlAttr(name);
148
- const href = escapeHtmlAttr(stable ?? src);
219
+ const href = escapeHtmlAttr(link ?? src);
149
220
  const imgSrc = escapeHtmlAttr(src);
150
221
  lines.push(`<a href="${href}"><img width="${w}" alt="${alt}" src="${imgSrc}"></a>`);
151
222
  lines.push("");
152
223
  }
153
- else if (stable) {
154
- lines.push(`- [${name}](${stable})`);
224
+ else if (link) {
225
+ lines.push(`- [${name}](${link})`);
155
226
  }
156
227
  else {
157
228
  lines.push(`- ${name}`);
158
229
  }
159
230
  }
231
+ if (overflowImages.length > 0) {
232
+ const n = overflowImages.length;
233
+ lines.push(`<details><summary>${n} more attachment${n === 1 ? "" : "s"}</summary>`, "");
234
+ for (const item of overflowImages) {
235
+ const name = item.key.slice(item.key.lastIndexOf("/") + 1);
236
+ const link = item.pageUrl ?? item.url;
237
+ lines.push(link ? `- [${name}](${link})` : `- ${name}`);
238
+ }
239
+ lines.push("", "</details>", "");
240
+ }
160
241
  lines.push('<sub>Maintained by <a href="https://uploads.sh">uploads.sh</a> — re-uploading a file with the same name updates it everywhere it is embedded.</sub>');
161
242
  return lines.join("\n");
162
243
  }
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { UploadsError, type UploadsErrorCode } from "./errors.js";
7
7
  export { createUploadsClient, type UploadsClient, type PutOptions, type ProvenanceInput, type ListOptions, type PutResult, type ListItem, type ListResult, type HeadResult, type DeleteResult, type GalleryItem, type Gallery, type GallerySummary, type GalleryListOptions, type GalleryListResult, type CreateGalleryOptions, type AddGalleryItemOptions, type DeleteGalleryOptions, type HealthResult, type UsageResult, type ReconcileResult, type PurgeExpiredResult, type PurgeExpiredResponse, type FindFilesOptions, type FindFilesItem, type FindFilesResult, type GetMetadataResult, type PatchMetadataOptions, } from "./client.js";
8
8
  export { buildCliProvenance } from "./provenance.js";
9
9
  export { META_KEY_RE, META_VALUE_MAX, META_MAX_KEYS, META_MAX_TOTAL_BYTES, validateMetaEntry, parseMetaPair, parseMetaFlags, } from "./metadata.js";
10
- export { ATTACHMENTS_MARKER, attachmentsCommentBody, ghAttachmentKey, ghKeyPrefix, ghMetadataFromTarget, isValidRepo, parseRepoFromRemoteUrl, type AttachmentItem, type GhTarget, type GhTargetKind, } from "./github.js";
10
+ export { ATTACHMENTS_MARKER, attachmentsCommentBody, ghAttachmentKey, ghKeyPrefix, ghMetadataFromTarget, ghBranchAttachmentKey, ghBranchKeyPrefix, ghMetadataForBranch, isValidRepo, parseRepoFromRemoteUrl, type AttachmentItem, type GhTarget, type GhTargetKind, } from "./github.js";
11
11
  export { DEFAULT_OPTIMIZE_MAX_EDGE, DEFAULT_OPTIMIZE_QUALITY, optimizeImageForUpload, rewriteKeyExtension, withImageExtension, type OptimizeImageOptions, type OptimizeImageResult, type OptimizeOutputFormat, } from "./optimize.js";
12
12
  export { FRAME_PRESETS, applyFrame, listFramePresets, resolveFrameId, type FrameFit, type FrameOptions, type FrameResult, } from "./frame.js";
13
13
  export { execRunner, resolveRepo, upsertAttachmentsComment, type CommandRunner, } from "./github-gh.js";
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export { UploadsError } from "./errors.js";
7
7
  export { createUploadsClient, } from "./client.js";
8
8
  export { buildCliProvenance } from "./provenance.js";
9
9
  export { META_KEY_RE, META_VALUE_MAX, META_MAX_KEYS, META_MAX_TOTAL_BYTES, validateMetaEntry, parseMetaPair, parseMetaFlags, } from "./metadata.js";
10
- export { ATTACHMENTS_MARKER, attachmentsCommentBody, ghAttachmentKey, ghKeyPrefix, ghMetadataFromTarget, isValidRepo, parseRepoFromRemoteUrl, } from "./github.js";
10
+ export { ATTACHMENTS_MARKER, attachmentsCommentBody, ghAttachmentKey, ghKeyPrefix, ghMetadataFromTarget, ghBranchAttachmentKey, ghBranchKeyPrefix, ghMetadataForBranch, isValidRepo, parseRepoFromRemoteUrl, } from "./github.js";
11
11
  export { DEFAULT_OPTIMIZE_MAX_EDGE, DEFAULT_OPTIMIZE_QUALITY, optimizeImageForUpload, rewriteKeyExtension, withImageExtension, } from "./optimize.js";
12
12
  export { FRAME_PRESETS, applyFrame, listFramePresets, resolveFrameId, } from "./frame.js";
13
13
  export { execRunner, resolveRepo, upsertAttachmentsComment, } from "./github-gh.js";
package/dist/mcp/tools.js CHANGED
@@ -123,11 +123,11 @@ export function createUploadsMcpTools(opts) {
123
123
  });
124
124
  return { config, client: clientFactory(config) };
125
125
  }
126
- const syncComment = async (client, target) => {
126
+ const syncComment = async (client, target, workspace) => {
127
127
  let comment;
128
128
  let commentError;
129
129
  try {
130
- comment = await syncAttachmentsComment(client, target, run);
130
+ comment = await syncAttachmentsComment(client, target, run, workspace);
131
131
  }
132
132
  catch (err) {
133
133
  // Uploads already succeeded; the comment is best-effort by design.
@@ -402,7 +402,7 @@ export function createUploadsMcpTools(opts) {
402
402
  catch (err) {
403
403
  usage(err instanceof Error ? err.message : String(err));
404
404
  }
405
- const { client } = clientFor(args);
405
+ const { config, client } = clientFor(args);
406
406
  const defaults = resolvePutDefaults({ envFile: globals.envFile });
407
407
  const frameOpts = mcpFrameOptions(args);
408
408
  const optimizeOpts = mcpOptimizeOptions(args, defaults);
@@ -436,7 +436,7 @@ export function createUploadsMcpTools(opts) {
436
436
  throw new ToolBatchError(batchFailureMessage(failures), { uploads, failures });
437
437
  }
438
438
  if (wantComment && target && uploads.length > 0) {
439
- const { comment, commentError } = await syncComment(client, target);
439
+ const { comment, commentError } = await syncComment(client, target, config.workspace);
440
440
  return { uploads, failures, comment, commentError };
441
441
  }
442
442
  return { uploads, failures };
@@ -469,7 +469,7 @@ export function createUploadsMcpTools(opts) {
469
469
  filename: prepared.filename,
470
470
  };
471
471
  if (wantComment && target) {
472
- const { comment, commentError } = await syncComment(client, target);
472
+ const { comment, commentError } = await syncComment(client, target, config.workspace);
473
473
  return { ...result, markdown, optimize, frame: prepared.frame, comment, commentError };
474
474
  }
475
475
  return {
@@ -504,7 +504,7 @@ export function createUploadsMcpTools(opts) {
504
504
  ...(dryRun ? { dryRun: true } : {}),
505
505
  };
506
506
  if (wantComment && target) {
507
- const { comment, commentError } = await syncComment(client, target);
507
+ const { comment, commentError } = await syncComment(client, target, config.workspace);
508
508
  return { ...flat, comment, commentError };
509
509
  }
510
510
  return flat;
@@ -761,7 +761,7 @@ export function createUploadsMcpTools(opts) {
761
761
  ...(dryRun ? { dryRun: true } : {}),
762
762
  };
763
763
  if (wantComment && target) {
764
- const { comment, commentError } = await syncComment(client, target);
764
+ const { comment, commentError } = await syncComment(client, target, config.workspace);
765
765
  return { ...flat, comment, commentError };
766
766
  }
767
767
  return flat;
@@ -821,7 +821,7 @@ export function createUploadsMcpTools(opts) {
821
821
  const explicitTarget = ghTargetFromArgs(args, run);
822
822
  const target = explicitTarget ??
823
823
  resolveCurrentPullRequest(resolveRepo(optString(args, "repo"), run), run);
824
- const { client } = clientFor(args);
824
+ const { config, client } = clientFor(args);
825
825
  const contentType = optString(args, "contentType");
826
826
  const defaults = resolvePutDefaults({ envFile: globals.envFile });
827
827
  const frameOpts = mcpFrameOptions(args);
@@ -856,7 +856,7 @@ export function createUploadsMcpTools(opts) {
856
856
  }
857
857
  if (optBool(args, "noComment"))
858
858
  return { target, uploads, failures };
859
- const { comment, commentError } = await syncComment(client, target);
859
+ const { comment, commentError } = await syncComment(client, target, config.workspace);
860
860
  return { target, uploads, failures, comment, commentError };
861
861
  },
862
862
  },
@@ -1062,8 +1062,8 @@ export function createUploadsMcpTools(opts) {
1062
1062
  const target = ghTargetFromArgs(args, run);
1063
1063
  if (!target)
1064
1064
  usage("comment requires pr or issue");
1065
- const { client } = clientFor(args);
1066
- const result = await syncAttachmentsComment(client, target, run);
1065
+ const { config, client } = clientFor(args);
1066
+ const result = await syncAttachmentsComment(client, target, run, config.workspace);
1067
1067
  return { ...target, ...result };
1068
1068
  },
1069
1069
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.14.0",
3
+ "version": "0.16.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,