@buildinternet/uploads 0.31.0 → 0.32.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.
@@ -11,7 +11,7 @@ non-secret page URL plus a code you share separately). The legacy
11
11
 
12
12
  Options:
13
13
  --admin-token <token> Or ADMIN_TOKEN (UPLOADS_ADMIN_TOKEN is a legacy alias)
14
- --workspace <name> Default: default
14
+ --workspace <name> Required — the workspace the invite grants access to
15
15
  --label <label>
16
16
  --expires-in <seconds> Default: server policy
17
17
  --token-expires-in <seconds> Upload token lifetime (default: server policy)
@@ -79,7 +79,15 @@ export async function runAdmin(args, opts, help = false) {
79
79
  throw new UsageError("ADMIN_TOKEN is required for admin enrollment creation");
80
80
  const apiUrl = flagString(parsed.flags, "--api-url") ?? opts.apiUrl ?? "https://api.uploads.sh";
81
81
  const webUrl = flagString(parsed.flags, "--web-url");
82
- const workspace = flagString(parsed.flags, "--workspace") ?? "default";
82
+ // No default. An invite grants files:read/files:write on the workspace it
83
+ // names, so omitting the flag used to silently target the shared `default`
84
+ // workspace — and enrollment redemption mints a token without an org
85
+ // membership, so the recipient would not appear in any member list while
86
+ // still reading and writing its files.
87
+ const workspace = flagString(parsed.flags, "--workspace");
88
+ if (!workspace) {
89
+ throw new UsageError("--workspace is required (the workspace the invite grants access to)");
90
+ }
83
91
  const label = flagString(parsed.flags, "--label");
84
92
  const email = flagString(parsed.flags, "--email");
85
93
  const result = await createEnrollment(apiUrl, adminToken, {
@@ -4,7 +4,7 @@ export declare const DEFAULT_MCP_URL = "https://agents.uploads.sh/mcp";
4
4
  export interface StepResult {
5
5
  command: string[];
6
6
  ok: boolean;
7
- skipped?: "dry-run" | "sign-in";
7
+ skipped?: "dry-run" | "sign-in" | "already-configured";
8
8
  error?: string;
9
9
  output?: string;
10
10
  }
@@ -17,6 +17,10 @@ infers your workspace from the bearer token, so only the token is needed.
17
17
  Claude Code and Codex ship the same reminder via their plugins (same command:
18
18
  \`${HOOK_COMMAND}\`) — install those plugins instead of relying on this step.
19
19
 
20
+ Safe to re-run. An MCP server already registered under this name is reported
21
+ 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.
23
+
20
24
  Usage:
21
25
  uploads install [skill|mcp|hooks|all] (default: all)
22
26
 
@@ -73,6 +77,16 @@ function skillCommand(skill) {
73
77
  // -g global, -y non-interactive, -a '*' every agent (skips the multi-select TUI)
74
78
  return ["npx", "-y", "skills", "add", SKILL_SOURCE, "--skill", skill, "-g", "-y", "-a", "*"];
75
79
  }
80
+ /**
81
+ * `claude mcp add` refuses to overwrite an existing entry and exits non-zero
82
+ * ("MCP server uploads already exists in local config"). That is the steady
83
+ * state for anyone re-running `uploads install`, not a failure — recognize it
84
+ * so the run stays green and the footer tells them how to re-add if they want
85
+ * a fresh token in the header.
86
+ */
87
+ function alreadyConfigured(result) {
88
+ return !result.ok && /already exists/i.test(result.error ?? "");
89
+ }
76
90
  function mcpCommand(name, url, bearer) {
77
91
  return [
78
92
  "claude",
@@ -101,7 +115,7 @@ function peekToken(globals) {
101
115
  return undefined;
102
116
  }
103
117
  }
104
- function printHumanSteps(results, redact, verbose) {
118
+ function printHumanSteps(results, redact, verbose, mcpName) {
105
119
  for (const [step, r] of Object.entries(results)) {
106
120
  const cmd = redact(r.command.join(" "));
107
121
  if (r.skipped === "dry-run") {
@@ -110,6 +124,10 @@ function printHumanSteps(results, redact, verbose) {
110
124
  else if (r.skipped === "sign-in") {
111
125
  process.stdout.write(`${step}: skipped — ${redact(r.error ?? "needs sign-in")}\n`);
112
126
  }
127
+ else if (r.skipped === "already-configured") {
128
+ process.stdout.write(`${step}: already configured — "${mcpName}" is registered in Claude Code (nothing to do)\n` +
129
+ ` To re-register (e.g. with a new token): claude mcp remove ${mcpName} && uploads install mcp\n`);
130
+ }
113
131
  else if (r.ok) {
114
132
  process.stdout.write(`${step}: ok\n`);
115
133
  if (verbose && r.output) {
@@ -189,7 +207,12 @@ export async function runInstall(args, opts, help = false) {
189
207
  const command = mcpCommand(name, url, token || "<token>");
190
208
  if (human)
191
209
  process.stdout.write("Installing MCP server…\n");
192
- results.mcp = dryRun ? { command, ok: true, skipped: "dry-run" } : runStep(run, command);
210
+ const step = dryRun
211
+ ? { command, ok: true, skipped: "dry-run" }
212
+ : runStep(run, command);
213
+ results.mcp = alreadyConfigured(step)
214
+ ? { command, ok: true, skipped: "already-configured", output: step.error }
215
+ : step;
193
216
  }
194
217
  }
195
218
  if (target === "hooks" || target === "all") {
@@ -223,7 +246,7 @@ export async function runInstall(args, opts, help = false) {
223
246
  process.stdout.write(JSON.stringify({ ok: !failed, steps }, null, 2) + "\n");
224
247
  return failed ? 1 : 0;
225
248
  }
226
- printHumanSteps(results, redact, verbose);
249
+ printHumanSteps(results, redact, verbose, name);
227
250
  // Path-level detail for hooks (printHumanSteps only shows the synthetic step).
228
251
  if ((target === "hooks" || target === "all") &&
229
252
  (dryRun || (human && (verbose || hookWrites.some((w) => w.action !== "skipped"))))) {
@@ -9,9 +9,10 @@ import { runInstall, runStep } from "./install.js";
9
9
  const UPDATE_HELP = `uploads update — update the CLI and refresh agent integrations
10
10
 
11
11
  Upgrades the globally installed npm package, then re-runs \`uploads install\` so
12
- the agent skills and the MCP registration match the new version. Skills and the
13
- MCP registration drift on their own, so this refreshes them even when the CLI is
14
- already current.
12
+ the agent skills match the new version. Skills drift on their own, so this
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.
15
16
 
16
17
  Usage:
17
18
  uploads update [options]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.31.0",
3
+ "version": "0.32.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,