@buildinternet/uploads 0.47.0 → 0.48.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 +2 -0
- package/dist/client.d.ts +17 -0
- package/dist/commands/mcp.js +2 -2
- package/dist/commands.d.ts +10 -8
- package/dist/commands.js +22 -4
- package/dist/format-usage.d.ts +5 -0
- package/dist/format-usage.js +14 -4
- package/dist/mcp/server.d.ts +8 -5
- package/dist/mcp/server.js +12 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -204,6 +204,8 @@ 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
|
+
The MCP Registry lists this server as `sh.uploads/mcp`.
|
|
208
|
+
|
|
207
209
|
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
210
|
|
|
209
211
|
## Programmatic use
|
package/dist/client.d.ts
CHANGED
|
@@ -439,6 +439,23 @@ export interface UsageResult {
|
|
|
439
439
|
storageRemainingBytes?: number;
|
|
440
440
|
maxUploadsPerPeriod?: number;
|
|
441
441
|
uploadsRemaining?: number;
|
|
442
|
+
/** Bytes still on hosted storage (shared-lane residue). */
|
|
443
|
+
sharedBytes?: number;
|
|
444
|
+
/** "shared" = BYO bucket active: the storage cap meters only hosted
|
|
445
|
+
* residue; the customer's own bucket is unmetered. */
|
|
446
|
+
storageBudgetBasis?: "total" | "shared";
|
|
447
|
+
/** Bearer-safe lane summary (issue #775; servers ≥ this field's release). */
|
|
448
|
+
storage?: {
|
|
449
|
+
mode: "shared" | "byo";
|
|
450
|
+
/** Demoted former-active lanes that still serve previously uploaded files. */
|
|
451
|
+
fallbackLanes: number;
|
|
452
|
+
health: {
|
|
453
|
+
ok: boolean;
|
|
454
|
+
code?: string;
|
|
455
|
+
message?: string;
|
|
456
|
+
since?: string;
|
|
457
|
+
};
|
|
458
|
+
};
|
|
442
459
|
/** File scopes of the presented token (servers ≥ this field's release). */
|
|
443
460
|
scopes?: Array<TokenScope>;
|
|
444
461
|
/**
|
package/dist/commands/mcp.js
CHANGED
|
@@ -2,7 +2,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
2
2
|
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/server/validators/ajv";
|
|
3
3
|
import { parseCommandArgs } from "../cli-args.js";
|
|
4
4
|
import { resolveApiUrl } from "../config.js";
|
|
5
|
-
import { createMcpServer } from "../mcp/server.js";
|
|
5
|
+
import { createMcpServer, MCP_SERVER_ICONS } from "../mcp/server.js";
|
|
6
6
|
import { createUploadsMcpTools } from "../mcp/tools.js";
|
|
7
7
|
import { packageVersion } from "../package-version.js";
|
|
8
8
|
import { writeCommandHelp } from "../cli-style.js";
|
|
@@ -35,7 +35,7 @@ export async function runMcp(args, opts, help = false) {
|
|
|
35
35
|
// `@cfworker/json-schema` provider instead).
|
|
36
36
|
const validator = new AjvJsonSchemaValidator();
|
|
37
37
|
const handle = serveStdio(() => createMcpServer({
|
|
38
|
-
serverInfo: { name: "uploads", version: packageVersion() },
|
|
38
|
+
serverInfo: { name: "uploads", version: packageVersion(), icons: MCP_SERVER_ICONS },
|
|
39
39
|
tools: createUploadsMcpTools({ globals: opts.globals }),
|
|
40
40
|
apiUrl: resolveApiUrl(opts.globals),
|
|
41
41
|
validator,
|
package/dist/commands.d.ts
CHANGED
|
@@ -615,16 +615,18 @@ export interface DoctorReport {
|
|
|
615
615
|
/** Workspace/token mismatch warning (also present in hints). */
|
|
616
616
|
warning?: string;
|
|
617
617
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
* a fabricated mode.
|
|
618
|
+
* Storage-lane summary (issue #775): the usage endpoint carries a
|
|
619
|
+
* bearer-safe `storage` object (mode + fallback-lane count + health), so
|
|
620
|
+
* doctor reports it from the same call it already makes. `checked` is
|
|
621
|
+
* false when usage failed or the server predates the field — then `note`
|
|
622
|
+
* falls back to the honest "can't check from here" line (the full
|
|
623
|
+
* projection on `GET /me/workspaces/:name/storage` stays session-gated).
|
|
625
624
|
*/
|
|
626
625
|
storage: {
|
|
627
|
-
checked:
|
|
626
|
+
checked: boolean;
|
|
627
|
+
mode?: "shared" | "byo";
|
|
628
|
+
fallbackLanes?: number;
|
|
629
|
+
healthy?: boolean;
|
|
628
630
|
note: string;
|
|
629
631
|
};
|
|
630
632
|
hints: string[];
|
package/dist/commands.js
CHANGED
|
@@ -3483,6 +3483,10 @@ export async function buildDoctorReport(config, client, detectRoots) {
|
|
|
3483
3483
|
}
|
|
3484
3484
|
}
|
|
3485
3485
|
let usage;
|
|
3486
|
+
let storage = {
|
|
3487
|
+
checked: false,
|
|
3488
|
+
note: "not checked from the CLI — this server doesn't report a storage summary on the usage endpoint; sign in on the web (Account → workspace → Settings) to view mode and verification status",
|
|
3489
|
+
};
|
|
3486
3490
|
let scopes;
|
|
3487
3491
|
if (authOk) {
|
|
3488
3492
|
try {
|
|
@@ -3493,6 +3497,23 @@ export async function buildDoctorReport(config, client, detectRoots) {
|
|
|
3493
3497
|
objects: snap.objects,
|
|
3494
3498
|
uploadsInPeriod: snap.uploadsInPeriod,
|
|
3495
3499
|
};
|
|
3500
|
+
if (snap.storage) {
|
|
3501
|
+
const laneNote = snap.storage.fallbackLanes > 0
|
|
3502
|
+
? ` (${snap.storage.fallbackLanes} previous lane${snap.storage.fallbackLanes === 1 ? "" : "s"} still serving old files)`
|
|
3503
|
+
: "";
|
|
3504
|
+
const healthNote = snap.storage.health.ok
|
|
3505
|
+
? ""
|
|
3506
|
+
: " — not working; rotate credentials on the web settings page";
|
|
3507
|
+
storage = {
|
|
3508
|
+
checked: true,
|
|
3509
|
+
mode: snap.storage.mode,
|
|
3510
|
+
fallbackLanes: snap.storage.fallbackLanes,
|
|
3511
|
+
healthy: snap.storage.health.ok,
|
|
3512
|
+
note: (snap.storage.mode === "byo" ? "your bucket" : "hosted storage") +
|
|
3513
|
+
laneNote +
|
|
3514
|
+
healthNote,
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3496
3517
|
scopes = snap.scopes;
|
|
3497
3518
|
if (scopes && !scopes.includes("files:delete")) {
|
|
3498
3519
|
hints.push("token lacks files:delete (`uploads delete` will be forbidden) — re-run `uploads login` for a full-scope token");
|
|
@@ -3522,10 +3543,7 @@ export async function buildDoctorReport(config, client, detectRoots) {
|
|
|
3522
3543
|
usage,
|
|
3523
3544
|
scopes,
|
|
3524
3545
|
warning: mismatch,
|
|
3525
|
-
storage
|
|
3526
|
-
checked: false,
|
|
3527
|
-
note: "not checked from the CLI — storage settings (shared vs. bring-your-own-bucket) live behind a signed-in session; sign in on the web (Account → workspace → Settings) to view mode and verification status",
|
|
3528
|
-
},
|
|
3546
|
+
storage,
|
|
3529
3547
|
hints,
|
|
3530
3548
|
browser,
|
|
3531
3549
|
};
|
package/dist/format-usage.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export type UsageSnapshotLike = {
|
|
|
9
9
|
storageRemainingBytes?: number;
|
|
10
10
|
maxUploadsPerPeriod?: number;
|
|
11
11
|
uploadsRemaining?: number;
|
|
12
|
+
/** Bytes still on hosted storage (shared-lane residue). */
|
|
13
|
+
sharedBytes?: number;
|
|
14
|
+
/** "shared" = BYO bucket active: the storage cap meters only hosted
|
|
15
|
+
* residue; bytes in the customer's own bucket are unmetered. */
|
|
16
|
+
storageBudgetBasis?: "total" | "shared";
|
|
12
17
|
/** Catalog plan id when the API reports it (`free` | `pro`). */
|
|
13
18
|
plan?: string;
|
|
14
19
|
};
|
package/dist/format-usage.js
CHANGED
|
@@ -11,9 +11,14 @@ import { formatByteSize, formatMarketedBytes } from "./format-bytes.js";
|
|
|
11
11
|
import { BRAND } from "./cli-brand.js";
|
|
12
12
|
/** True when the API reported any cumulative workspace quota. */
|
|
13
13
|
export function isUsageMetered(result) {
|
|
14
|
-
return (usagePct(result
|
|
14
|
+
return (usagePct(storageMeteredBytes(result), result.maxStorageBytes) !== null ||
|
|
15
15
|
usagePct(result.uploadsInPeriod, result.maxUploadsPerPeriod) !== null);
|
|
16
16
|
}
|
|
17
|
+
/** The usage number the storage cap actually meters — hosted residue only
|
|
18
|
+
* when a BYO bucket is active (`storageBudgetBasis: "shared"`). */
|
|
19
|
+
function storageMeteredBytes(result) {
|
|
20
|
+
return result.storageBudgetBasis === "shared" ? (result.sharedBytes ?? 0) : result.bytes;
|
|
21
|
+
}
|
|
17
22
|
/** 0–100, one decimal. Missing/invalid caps → no bar. Matches web `usagePct`. */
|
|
18
23
|
export function usagePct(value, max) {
|
|
19
24
|
if (typeof max !== "number" || !(max > 0) || !Number.isFinite(value))
|
|
@@ -112,21 +117,26 @@ export function formatUsageHuman(result, opts = {}) {
|
|
|
112
117
|
const label = planLabel(result.plan);
|
|
113
118
|
if (label)
|
|
114
119
|
lines.push(`plan: ${label}`);
|
|
115
|
-
const
|
|
120
|
+
const byoActive = result.storageBudgetBasis === "shared";
|
|
121
|
+
const meteredBytes = storageMeteredBytes(result);
|
|
122
|
+
const storagePct = usagePct(meteredBytes, result.maxStorageBytes);
|
|
116
123
|
if (storagePct !== null && result.maxStorageBytes != null) {
|
|
117
124
|
// Caps (and remaining-against-cap) use SI marketed formatting so Free's
|
|
118
125
|
// 250_000_000 reads as "250 MB", not binary "238.4 MB". Used bytes share
|
|
119
126
|
// the same base on this line so the three numbers stay coherent.
|
|
120
|
-
const detail = `${formatMarketedBytes(
|
|
127
|
+
const detail = `${formatMarketedBytes(meteredBytes)} / ${formatMarketedBytes(result.maxStorageBytes)}` +
|
|
121
128
|
(result.storageRemainingBytes != null
|
|
122
129
|
? ` (${formatMarketedBytes(result.storageRemainingBytes)} free)`
|
|
123
130
|
: "");
|
|
124
131
|
const bar = formatProgressBar(storagePct, { width, color });
|
|
125
|
-
lines.push(`storage: ${bar} ${detail}`);
|
|
132
|
+
lines.push(`storage: ${bar} ${detail}${byoActive ? " on hosted storage" : ""}`);
|
|
126
133
|
}
|
|
127
134
|
else {
|
|
128
135
|
lines.push(`storage: ${formatByteSize(result.bytes)}`);
|
|
129
136
|
}
|
|
137
|
+
if (byoActive) {
|
|
138
|
+
lines.push("note: your own bucket is unmetered — the storage quota only counts files on hosted storage");
|
|
139
|
+
}
|
|
130
140
|
lines.push(`objects: ${formatCount(result.objects)}`);
|
|
131
141
|
const uploadsPct = usagePct(result.uploadsInPeriod, result.maxUploadsPerPeriod);
|
|
132
142
|
if (uploadsPct !== null && result.maxUploadsPerPeriod != null) {
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* The stdio transport comes from `@modelcontextprotocol/server/stdio`; logs
|
|
18
18
|
* must never go to stdout.
|
|
19
19
|
*/
|
|
20
|
-
import { McpServer, type jsonSchemaValidator } from "@modelcontextprotocol/server";
|
|
20
|
+
import { McpServer, type Implementation, type jsonSchemaValidator } from "@modelcontextprotocol/server";
|
|
21
21
|
export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, type ToolArgs, } from "./args.js";
|
|
22
22
|
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
23
23
|
export { mapBounded } from "../async.js";
|
|
@@ -81,11 +81,14 @@ export interface McpTool {
|
|
|
81
81
|
outputSchema?: Record<string, unknown>;
|
|
82
82
|
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Brand mark on `serverInfo.icons` (MCP `Icon`, spec 2026-07-28). PNG is the
|
|
86
|
+
* type clients that render icons MUST support. The file is the site's
|
|
87
|
+
* apple-touch-icon (180×180 pixel chevron), already public on uploads.sh.
|
|
88
|
+
*/
|
|
89
|
+
export declare const MCP_SERVER_ICONS: NonNullable<Implementation["icons"]>;
|
|
84
90
|
export declare function createMcpServer(opts: {
|
|
85
|
-
serverInfo:
|
|
86
|
-
name: string;
|
|
87
|
-
version: string;
|
|
88
|
-
};
|
|
91
|
+
serverInfo: Implementation;
|
|
89
92
|
tools: McpTool[];
|
|
90
93
|
/** API base for telemetry (honors uploads --api-url). */
|
|
91
94
|
apiUrl?: string;
|
package/dist/mcp/server.js
CHANGED
|
@@ -138,6 +138,18 @@ function wrapHandler(tool, apiUrl) {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Brand mark on `serverInfo.icons` (MCP `Icon`, spec 2026-07-28). PNG is the
|
|
143
|
+
* type clients that render icons MUST support. The file is the site's
|
|
144
|
+
* apple-touch-icon (180×180 pixel chevron), already public on uploads.sh.
|
|
145
|
+
*/
|
|
146
|
+
export const MCP_SERVER_ICONS = [
|
|
147
|
+
{
|
|
148
|
+
src: "https://uploads.sh/apple-touch-icon.png",
|
|
149
|
+
mimeType: "image/png",
|
|
150
|
+
sizes: ["180x180"],
|
|
151
|
+
},
|
|
152
|
+
];
|
|
141
153
|
export function createMcpServer(opts) {
|
|
142
154
|
const { serverInfo, tools, apiUrl, validator } = opts;
|
|
143
155
|
const server = new McpServer(serverInfo, {
|
package/package.json
CHANGED