@buildinternet/uploads 0.46.0 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -204,7 +204,7 @@ Config layers (first match wins): CLI flags → env vars → `--env-file` → `~
204
204
 
205
205
  Or with `UPLOADS_TOKEN`/`UPLOADS_WORKSPACE` in the environment or user config. Claude Code: `claude mcp add uploads -- uploads --env-file /path/to/.env mcp`.
206
206
 
207
- For HTTP clients there's also a hosted variant at `https://agents.uploads.sh/mcp` — the workspace is inferred from the bearer token, so only the URL and token are needed (`https://agents.uploads.sh/<workspace>/mcp` and the `mcp.uploads.sh` hostname also work). Tools: file operations (including `get_metadata` / `set_metadata` / `find_files`) plus `gallery_create`, `gallery_get`, `gallery_add`, `gallery_link`, and `gallery_find_by_reference`; all use the same bearer-token workspace scopes and gallery URLs come from the API — see `apps/mcp` in the repo. The hosted `put` also accepts a `metadata` param. `uploads install` registers the skills + hosted MCP + Grok/Cursor hooks (short progress; `--verbose` for underlying output). Claude and Codex use their plugins for the same pre-PR screenshot reminder (`uploads hook pre-pr-screenshot`). Its `put` takes no content type: the stored type is sniffed server-side from the bytes and checked against the workspace allowlist, and writes are rate limited per workspace.
207
+ For HTTP clients there's also a hosted variant at `https://agents.uploads.sh/mcp` — the workspace is inferred from the bearer token, so only the URL and token are needed (`https://agents.uploads.sh/<workspace>/mcp` and the `mcp.uploads.sh` hostname also work). Tools: file operations (including `get_metadata` / `set_metadata` / `find_files`) plus `gallery_create`, `gallery_get`, `gallery_add`, `gallery_link`, and `gallery_find_by_reference`; all use the same bearer-token workspace scopes and gallery URLs come from the API — see `apps/mcp` in the repo. The hosted `put` also accepts a `metadata` param. `uploads install` registers the skills + hosted MCP with whichever of Claude Code, Codex, and Grok are on PATH (a missing CLI is skipped) + Grok/Cursor hooks (short progress; `--verbose` for underlying output). Claude and Codex use their plugins for the same pre-PR screenshot reminder (`uploads hook pre-pr-screenshot`). Its `put` takes no content type: the stored type is sniffed server-side from the bytes and checked against the workspace allowlist, and writes are rate limited per workspace.
208
208
 
209
209
  ## Programmatic use
210
210
 
@@ -212,7 +212,7 @@ export const ROOT_COMMANDS = [
212
212
  essential: true,
213
213
  subcommands: [
214
214
  { name: "skill", summary: "Install the agent skills only" },
215
- { name: "mcp", summary: "Register the remote MCP server only" },
215
+ { name: "mcp", summary: "Register the remote MCP server (skips missing agent CLIs)" },
216
216
  { name: "hooks", summary: "Install PR screenshot hooks for Grok/Cursor" },
217
217
  { name: "all", summary: "Install skills, MCP, and hooks (default)" },
218
218
  ],
package/dist/cli-help.js CHANGED
@@ -167,9 +167,10 @@ ${section(style, "Examples:")}
167
167
  ${style.command("uploads logout")}
168
168
  ${style.command("uploads --version")}
169
169
 
170
- ${section(style, "Agent/MCP:")} ${style.body("`uploads install` sets up skills, hosted MCP, and hooks for")}
171
- ${style.body("Grok/Cursor. Claude and Codex use their plugins for the same PR-screenshot")}
172
- ${style.body("hook. Run `uploads mcp` for local stdio.")}
170
+ ${section(style, "Agent/MCP:")} ${style.body("`uploads install` sets up skills, hosted MCP (Claude/Codex/Grok;")}
171
+ ${style.body("skips any CLI that is not on PATH), and hooks for Grok/Cursor. Claude and")}
172
+ ${style.body("Codex use their plugins for the same PR-screenshot hook. Run `uploads mcp`")}
173
+ ${style.body("for local stdio.")}
173
174
 
174
175
  ${style.muted("Tip: uploads help essentials only")}
175
176
  ${style.muted(" uploads help --all this full listing")}
@@ -1,10 +1,21 @@
1
1
  import { type GlobalFlags } from "../cli-args.js";
2
2
  import { type CommandRunner } from "../github-gh.js";
3
3
  export declare const DEFAULT_MCP_URL = "https://agents.uploads.sh/mcp";
4
+ type McpClientId = "claude" | "codex" | "grok";
5
+ interface McpClient {
6
+ id: McpClientId;
7
+ label: string;
8
+ command: (name: string, url: string, bearer: string) => string[];
9
+ }
10
+ /**
11
+ * Agent CLIs that can register the hosted MCP server. Each is attempted
12
+ * independently; a missing binary is skipped so the others can still install.
13
+ */
14
+ export declare const MCP_CLIENTS: readonly McpClient[];
4
15
  export interface StepResult {
5
16
  command: string[];
6
17
  ok: boolean;
7
- skipped?: "dry-run" | "sign-in" | "already-configured";
18
+ skipped?: "dry-run" | "sign-in" | "already-configured" | "missing-cli";
8
19
  error?: string;
9
20
  output?: string;
10
21
  }
@@ -27,3 +38,4 @@ export declare function runInstall(args: string[], opts: {
27
38
  /** Override home for hook installs (tests). */
28
39
  home?: string;
29
40
  }, help?: boolean): Promise<number>;
41
+ export {};
@@ -6,20 +6,62 @@ import { HOOK_COMMAND, HOOK_INVOCATION, installHookManifests, } from "../hooks-i
6
6
  export const DEFAULT_MCP_URL = "https://agents.uploads.sh/mcp";
7
7
  const SKILL_SOURCE = "buildinternet/uploads";
8
8
  const SKILL_NAMES = ["uploads-cli", "github-screenshots", "annotate-screenshots"];
9
+ /** `mcp add --transport http` with an Authorization header. */
10
+ function httpMcpAdd(binary, name, url, bearer) {
11
+ return [
12
+ binary,
13
+ "mcp",
14
+ "add",
15
+ "--transport",
16
+ "http",
17
+ name,
18
+ url,
19
+ "--header",
20
+ `Authorization: Bearer ${bearer}`,
21
+ ];
22
+ }
23
+ /**
24
+ * Agent CLIs that can register the hosted MCP server. Each is attempted
25
+ * independently; a missing binary is skipped so the others can still install.
26
+ */
27
+ export const MCP_CLIENTS = [
28
+ {
29
+ id: "claude",
30
+ label: "Claude Code",
31
+ command: (name, url, bearer) => httpMcpAdd("claude", name, url, bearer),
32
+ },
33
+ {
34
+ id: "codex",
35
+ label: "Codex",
36
+ // Codex HTTP MCP has no --header; auth is OAuth on first use (same as the
37
+ // plugin's .mcp.json). Passing --bearer-token-env-var UPLOADS_TOKEN would
38
+ // break machines that signed in via `uploads login` (token lives in the
39
+ // config file, not the environment).
40
+ command: (name, url) => ["codex", "mcp", "add", name, "--url", url],
41
+ },
42
+ {
43
+ id: "grok",
44
+ label: "Grok",
45
+ command: (name, url, bearer) => httpMcpAdd("grok", name, url, bearer),
46
+ },
47
+ ];
48
+ const MCP_CLIENT_BINARIES = MCP_CLIENTS.map((c) => c.id).join(", ");
9
49
  const INSTALL_HELP = `uploads install — set up agent integrations (skills + remote MCP + hooks)
10
50
 
11
51
  Installs the github-screenshots, uploads-cli, and annotate-screenshots agent
12
- skills, registers the
13
- hosted MCP server with Claude Code, and installs the PR screenshot reminder
14
- hook for Grok / Cursor when those tools are present. The remote MCP endpoint
15
- infers your workspace from the bearer token, so only the token is needed.
52
+ skills, registers the hosted MCP server with whichever of Claude Code, Codex,
53
+ and Grok are on PATH, and installs the PR screenshot reminder hook for
54
+ Grok / Cursor when those tools are present. A missing agent CLI is skipped —
55
+ it does not fail the rest of the install. The remote MCP endpoint infers your
56
+ workspace from the bearer token, so only the token is needed.
16
57
 
17
58
  Claude Code and Codex ship the same reminder via their plugins (same command:
18
59
  \`${HOOK_INVOCATION}\`) — install those plugins instead of relying on this step.
19
60
 
20
61
  Safe to re-run. An MCP server already registered under this name is reported
21
62
  as \`already configured\` and left as-is — including the token it was created
22
- with. To point it at a new token: \`claude mcp remove <name>\` first.
63
+ with. To point it at a new token: \`<cli> mcp remove <name>\` first
64
+ (e.g. \`claude mcp remove uploads\`).
23
65
 
24
66
  Usage:
25
67
  uploads install [skill|mcp|hooks|all] (default: all)
@@ -28,7 +70,8 @@ What it does:
28
70
  skill Agent skills (via npx skills) — github-screenshots: visuals into
29
71
  PRs/issues; uploads-cli: full CLI reference; annotate-screenshots:
30
72
  hand-drawn callouts and redaction on screenshots
31
- mcp Hosted MCP server in Claude Code — put, list, attach, galleries
73
+ mcp Hosted MCP server in Claude Code, Codex, and Grok each CLI that
74
+ is installed is registered; missing ones are skipped
32
75
  hooks PR screenshot reminder for Grok / Cursor (user-global manifests)
33
76
 
34
77
  What runs under the hood:
@@ -37,7 +80,10 @@ What runs under the hood:
37
80
  with npx on PATH — missing tooling fails once with install guidance)
38
81
  mcp claude mcp add --transport http uploads ${DEFAULT_MCP_URL} \\
39
82
  --header "Authorization: Bearer <token>"
40
- (needs the Claude Code CLI on PATH)
83
+ codex mcp add uploads --url ${DEFAULT_MCP_URL}
84
+ grok mcp add --transport http uploads ${DEFAULT_MCP_URL} \\
85
+ --header "Authorization: Bearer <token>"
86
+ (each is skipped when that CLI is not on PATH)
41
87
  hooks write/merge ~/.grok/hooks/… and ~/.cursor/hooks.json when present
42
88
 
43
89
  Options:
@@ -76,9 +122,15 @@ export function missingBinaryHint(binary) {
76
122
  `Install Node 22+ from https://nodejs.org (or your package manager), open a new shell, ` +
77
123
  `confirm \`${binary} --version\` works, then re-run \`uploads install skill\`.`);
78
124
  case "claude":
79
- return (`claude not found on PATH — MCP install needs the Claude Code CLI. ` +
125
+ return (`claude not found on PATH — MCP install for Claude Code needs the Claude Code CLI. ` +
80
126
  `Install it from https://docs.anthropic.com/en/docs/claude-code, ensure \`claude\` is on PATH, ` +
81
- `then re-run \`uploads install mcp\`. Skills and hooks still work without Claude Code.`);
127
+ `then re-run \`uploads install mcp\`. Other agent CLIs (and skills/hooks) still work without it.`);
128
+ case "codex":
129
+ return (`codex not found on PATH — MCP install for Codex needs the Codex CLI. ` +
130
+ `Install it, ensure \`codex\` is on PATH, then re-run \`uploads install mcp\`.`);
131
+ case "grok":
132
+ return (`grok not found on PATH — MCP install for Grok needs the Grok CLI. ` +
133
+ `Install it, ensure \`grok\` is on PATH, then re-run \`uploads install mcp\`.`);
82
134
  default:
83
135
  return `${binary} not found on PATH — install it and ensure it is available in this shell.`;
84
136
  }
@@ -131,28 +183,59 @@ function skillCommand(skill) {
131
183
  // -g global, -y non-interactive, -a '*' every agent (skips the multi-select TUI)
132
184
  return ["npx", "-y", "skills", "add", SKILL_SOURCE, "--skill", skill, "-g", "-y", "-a", "*"];
133
185
  }
134
- /**
135
- * `claude mcp add` refuses to overwrite an existing entry and exits non-zero
136
- * ("MCP server uploads already exists in local config"). That is the steady
137
- * state for anyone re-running `uploads install`, not a failure — recognize it
138
- * so the run stays green and the footer tells them how to re-add if they want
139
- * a fresh token in the header.
140
- */
141
- function alreadyConfigured(result) {
142
- return !result.ok && /already exists/i.test(result.error ?? "");
186
+ /** `mcp add` exits non-zero when the name is already registered; treat that as success. */
187
+ function isAlreadyConfigured(error) {
188
+ return /already exists|already (configured|registered|present)|duplicate/i.test(error);
143
189
  }
144
- function mcpCommand(name, url, bearer) {
145
- return [
146
- "claude",
147
- "mcp",
148
- "add",
149
- "--transport",
150
- "http",
151
- name,
152
- url,
153
- "--header",
154
- `Authorization: Bearer ${bearer}`,
155
- ];
190
+ function mcpStepKey(client) {
191
+ return `mcp:${client.id}`;
192
+ }
193
+ function mcpClientForStep(step) {
194
+ return MCP_CLIENTS.find((c) => mcpStepKey(c) === step);
195
+ }
196
+ function stepFamily(key) {
197
+ if (key.startsWith("skill:"))
198
+ return "skills";
199
+ if (key.startsWith("mcp:"))
200
+ return "mcp";
201
+ return key;
202
+ }
203
+ function partitionSteps(results) {
204
+ const skills = [];
205
+ const mcp = [];
206
+ const other = [];
207
+ for (const entry of Object.entries(results)) {
208
+ const key = entry[0];
209
+ if (key.startsWith("skill:"))
210
+ skills.push(entry);
211
+ else if (key.startsWith("mcp:"))
212
+ mcp.push(entry);
213
+ else
214
+ other.push(entry);
215
+ }
216
+ return { skills, mcp, other };
217
+ }
218
+ /** Run one client's `mcp add`; missing binaries skip, duplicates are already-configured. */
219
+ function runMcpClientStep(run, command) {
220
+ try {
221
+ const output = run(command[0], command.slice(1)).trim();
222
+ return { command, ok: true, output: output || undefined };
223
+ }
224
+ catch (err) {
225
+ if (isEnoent(err)) {
226
+ return {
227
+ command,
228
+ ok: true,
229
+ skipped: "missing-cli",
230
+ error: `${command[0]} not found on PATH`,
231
+ };
232
+ }
233
+ const message = err instanceof Error ? err.message : String(err);
234
+ if (isAlreadyConfigured(message)) {
235
+ return { command, ok: true, skipped: "already-configured", output: message };
236
+ }
237
+ return { command, ok: false, error: message };
238
+ }
156
239
  }
157
240
  function peekToken(globals) {
158
241
  try {
@@ -171,27 +254,35 @@ function peekToken(globals) {
171
254
  }
172
255
  function printOneHumanStep(step, r, redact, verbose, mcpName) {
173
256
  const cmd = redact(r.command.join(" "));
174
- if (r.skipped === "dry-run") {
175
- process.stdout.write(`${step}: would run — ${cmd}\n`);
176
- }
177
- else if (r.skipped === "sign-in") {
178
- process.stdout.write(`${step}: skipped — ${redact(r.error ?? "needs sign-in")}\n`);
179
- }
180
- else if (r.skipped === "already-configured") {
181
- process.stdout.write(`${step}: already configured — "${mcpName}" is registered in Claude Code (nothing to do)\n` +
182
- ` To re-register (e.g. with a new token): claude mcp remove ${mcpName} && uploads install mcp\n`);
257
+ switch (r.skipped) {
258
+ case "dry-run":
259
+ process.stdout.write(`${step}: would run — ${cmd}\n`);
260
+ return;
261
+ case "sign-in":
262
+ process.stdout.write(`${step}: skipped — ${redact(r.error ?? "needs sign-in")}\n`);
263
+ return;
264
+ case "missing-cli":
265
+ process.stdout.write(`${step}: skipped ${r.error ?? `${r.command[0]} not found on PATH`}\n`);
266
+ return;
267
+ case "already-configured": {
268
+ const client = mcpClientForStep(step);
269
+ const label = client?.label ?? "the client";
270
+ const remove = `${client?.id ?? "<cli>"} mcp remove ${mcpName}`;
271
+ process.stdout.write(`${step}: already configured — "${mcpName}" is registered in ${label} (nothing to do)\n` +
272
+ ` To re-register (e.g. with a new token): ${remove} && uploads install mcp\n`);
273
+ return;
274
+ }
183
275
  }
184
- else if (r.ok) {
276
+ if (r.ok) {
185
277
  process.stdout.write(`${step}: ok\n`);
186
278
  if (verbose && r.output) {
187
279
  process.stdout.write(` ${redact(r.output).split("\n").join("\n ")}\n`);
188
280
  }
281
+ return;
189
282
  }
190
- else {
191
- process.stderr.write(`${step}: failed — ${redact(r.error ?? "")}\n`);
192
- if (verbose)
193
- process.stderr.write(` command: ${cmd}\n`);
194
- }
283
+ process.stderr.write(`${step}: failed — ${redact(r.error ?? "")}\n`);
284
+ if (verbose)
285
+ process.stderr.write(` command: ${cmd}\n`);
195
286
  }
196
287
  /** Shared error when every skill step failed identically; otherwise undefined. */
197
288
  function identicalSkillFailure(skillEntries) {
@@ -203,27 +294,41 @@ function identicalSkillFailure(skillEntries) {
203
294
  const allSame = skillEntries.every(([, r]) => !r.ok && !r.skipped && r.error === error);
204
295
  return allSame ? error : undefined;
205
296
  }
297
+ function printMcpHumanSteps(mcp, redact, verbose, mcpName) {
298
+ const first = mcp[0];
299
+ if (!first)
300
+ return;
301
+ if (mcp.every(([, r]) => r.skipped === "sign-in")) {
302
+ printOneHumanStep("mcp", first[1], redact, verbose, mcpName);
303
+ return;
304
+ }
305
+ if (mcp.every(([, r]) => r.skipped === "missing-cli")) {
306
+ process.stdout.write(`mcp: skipped — no agent CLI on PATH (${MCP_CLIENT_BINARIES}). Skills and hooks still work.\n`);
307
+ return;
308
+ }
309
+ for (const [step, r] of mcp) {
310
+ printOneHumanStep(step, r, redact, verbose, mcpName);
311
+ }
312
+ }
206
313
  /** Collapse identical skill failures to one `skills:` line (missing npx, old npm, …). */
207
314
  function printHumanSteps(results, redact, verbose, mcpName) {
208
- const entries = Object.entries(results);
209
- const skillEntries = entries.filter(([step]) => step.startsWith("skill:"));
210
- const sharedError = identicalSkillFailure(skillEntries);
315
+ const { skills, mcp, other } = partitionSteps(results);
316
+ const sharedError = identicalSkillFailure(skills);
211
317
  if (sharedError !== undefined) {
212
318
  process.stderr.write(`skills: failed — ${redact(sharedError)}\n`);
213
319
  if (verbose) {
214
- for (const [step, r] of skillEntries) {
320
+ for (const [step, r] of skills) {
215
321
  process.stderr.write(` ${step}: ${redact(r.command.join(" "))}\n`);
216
322
  }
217
323
  }
218
324
  }
219
325
  else {
220
- for (const [step, r] of skillEntries) {
326
+ for (const [step, r] of skills) {
221
327
  printOneHumanStep(step, r, redact, verbose, mcpName);
222
328
  }
223
329
  }
224
- for (const [step, r] of entries) {
225
- if (step.startsWith("skill:"))
226
- continue;
330
+ printMcpHumanSteps(mcp, redact, verbose, mcpName);
331
+ for (const [step, r] of other) {
227
332
  printOneHumanStep(step, r, redact, verbose, mcpName);
228
333
  }
229
334
  }
@@ -288,24 +393,27 @@ export async function runInstall(args, opts, help = false) {
288
393
  }
289
394
  }
290
395
  if (target === "mcp" || target === "all") {
291
- if (!dryRun && !token) {
292
- results.mcp = {
293
- command: mcpCommand(name, url, "<token>"),
294
- ok: false,
295
- skipped: "sign-in",
296
- error: "needs sign-in run `uploads login`, then `uploads install mcp`",
297
- };
298
- }
299
- else {
300
- const command = mcpCommand(name, url, token || "<token>");
301
- if (human)
302
- process.stdout.write("Installing MCP server…\n");
303
- const step = dryRun
304
- ? { command, ok: true, skipped: "dry-run" }
305
- : runStep(run, command);
306
- results.mcp = alreadyConfigured(step)
307
- ? { command, ok: true, skipped: "already-configured", output: step.error }
308
- : step;
396
+ const bearer = token || "<token>";
397
+ const skipSignIn = !dryRun && !token;
398
+ if (human && !skipSignIn)
399
+ process.stdout.write("Installing MCP server…\n");
400
+ for (const client of MCP_CLIENTS) {
401
+ const command = client.command(name, url, bearer);
402
+ const key = mcpStepKey(client);
403
+ if (skipSignIn) {
404
+ results[key] = {
405
+ command,
406
+ ok: false,
407
+ skipped: "sign-in",
408
+ error: "needs sign-in — run `uploads login`, then `uploads install mcp`",
409
+ };
410
+ }
411
+ else if (dryRun) {
412
+ results[key] = { command, ok: true, skipped: "dry-run" };
413
+ }
414
+ else {
415
+ results[key] = runMcpClientStep(run, command);
416
+ }
309
417
  }
310
418
  }
311
419
  if (target === "hooks" || target === "all") {
@@ -345,28 +453,25 @@ export async function runInstall(args, opts, help = false) {
345
453
  (dryRun || (human && (verbose || hookWrites.some((w) => w.action !== "skipped"))))) {
346
454
  printHookResults(hookWrites);
347
455
  }
348
- const skillResults = Object.entries(results)
349
- .filter(([step]) => step.startsWith("skill:"))
350
- .map(([, r]) => r);
456
+ const { skills: skillEntries, mcp: mcpEntries } = partitionSteps(results);
457
+ const skillResults = skillEntries.map(([, r]) => r);
351
458
  const skillsOk = skillResults.length > 0 && skillResults.every((r) => r.ok);
352
459
  const skillsFailed = skillResults.some((r) => !r.ok);
460
+ const mcpResults = mcpEntries.map(([, r]) => r);
461
+ const mcpFailed = mcpResults.some((r) => !r.ok);
353
462
  if (!failed && !dryRun) {
354
463
  const stepLabels = [
355
- ...new Set(Object.keys(results).map((k) => (k.startsWith("skill:") ? "skills" : k))),
464
+ ...new Set(Object.entries(results)
465
+ .filter(([, r]) => r.ok && r.skipped !== "missing-cli" && r.skipped !== "sign-in")
466
+ .map(([k]) => stepFamily(k))),
356
467
  ];
357
- printSuccessFooter(stepLabels, signedIn);
468
+ if (stepLabels.length > 0)
469
+ printSuccessFooter(stepLabels, signedIn);
358
470
  }
359
- else if (failed && !dryRun && skillsOk && results.mcp && !results.mcp.ok) {
360
- let next;
361
- if (results.mcp.skipped === "sign-in") {
362
- next = "Sign in with `uploads login`, then re-run `uploads install mcp`.";
363
- }
364
- else if (results.mcp.error?.includes("not found on PATH")) {
365
- next = "Install the Claude Code CLI (or skip MCP), then re-run `uploads install mcp`.";
366
- }
367
- else {
368
- next = "Fix the MCP step above, then re-run `uploads install mcp`.";
369
- }
471
+ else if (failed && !dryRun && skillsOk && mcpFailed) {
472
+ const next = mcpResults.every((r) => r.skipped === "sign-in")
473
+ ? "Sign in with `uploads login`, then re-run `uploads install mcp`."
474
+ : "Fix the MCP step above, then re-run `uploads install mcp`.";
370
475
  process.stdout.write(`\nSkills are installed. ${next}\n`);
371
476
  }
372
477
  else if (failed && !dryRun && skillsFailed) {
@@ -459,7 +459,7 @@ export async function runLogin(args, opts, help = false, deviceIo = defaultDevic
459
459
  process.stdout.write(`saved credentials to ${path}\napi: ${savedApiUrl}\nworkspace: ${result.workspace}\ntoken: ${redactToken(result.token)}\n`);
460
460
  process[doctor.ok ? "stdout" : "stderr"].write(`doctor: ${checked ? (doctor.ok ? "ok" : `failed — ${doctor.error}`) : "skipped"}\n`);
461
461
  if (doctor.ok)
462
- process.stdout.write("\nusing a coding agent? run `uploads install` to add the uploads skill + MCP server to Claude Code\n");
462
+ process.stdout.write("\nusing a coding agent? run `uploads install` to add the uploads skill + MCP server\n");
463
463
  }
464
464
  return doctor.ok ? 0 : 1;
465
465
  }
@@ -11,8 +11,9 @@ const UPDATE_HELP = `uploads update — update the CLI and refresh agent integra
11
11
  Upgrades the globally installed npm package, then re-runs \`uploads install\` so
12
12
  the agent skills match the new version. Skills drift on their own, so this
13
13
  refreshes them even when the CLI is already current. An MCP server already
14
- registered is left as-is (\`already configured\`) — \`claude mcp add\` never
15
- overwrites an existing entry.
14
+ registered is left as-is (\`already configured\`) — \`claude mcp add\` (and the
15
+ Codex/Grok equivalents) never overwrite an existing entry. A missing agent CLI
16
+ is skipped so it does not fail the rest of the refresh.
16
17
 
17
18
  Usage:
18
19
  uploads update [options]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.46.0",
3
+ "version": "0.46.1",
4
4
  "description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
5
5
  "type": "module",
6
6
  "sideEffects": false,