From 64ee35431af118ca13bad78a832c8c9bf45a5fff Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Mon, 2 Feb 2026 15:49:40 +0100 Subject: [PATCH 1/4] feat(node): add upload endpoint flags --- .changeset/bright-singers-burn.md | 5 +++ packages/mcp-server/README.md | 2 +- packages/mcp-server/src/server.ts | 2 +- packages/node/src/cli/commands/upload.ts | 47 ++++++++++++++++++++--- packages/node/test/e2e/cli/upload.test.ts | 2 +- 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 .changeset/bright-singers-burn.md diff --git a/.changeset/bright-singers-burn.md b/.changeset/bright-singers-burn.md new file mode 100644 index 00000000..ccc7e561 --- /dev/null +++ b/.changeset/bright-singers-burn.md @@ -0,0 +1,5 @@ +--- +"@transloadit/node": minor +--- + +Add upload CLI flags for create/resume tus endpoints and support expected uploads for out-of-band tus flows. diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index e3f230c7..279132ec 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -91,7 +91,7 @@ the request body stays small and no extra MCP/LLM token budget is consumed. - `path` inputs only work when the MCP server can read the same filesystem (local/stdio). - Hosted MCP cannot access your disk. Use `url`/`base64` for small files, or upload locally with: - `npx @transloadit/node upload ./file.ext --assembly --field :original` + `npx -y @transloadit/node upload ./file.ext --create-upload-endpoint --assembly --field :original` - For remote flows, create the Assembly with `expected_uploads` so it stays open for out‑of‑band tus uploads. diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 949004ff..433bdd77 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -581,7 +581,7 @@ export const createTransloaditMcpServer = ( } catch (error) { if (isErrnoException(error) && error.code === 'ENOENT') { return buildToolError('mcp_file_not_found', error.message, { - hint: 'Path inputs only work when the MCP server can read local files. For hosted MCP, use url/base64 or upload via `npx @transloadit/node upload`.', + hint: 'Path inputs only work when the MCP server can read local files. For hosted MCP, use url/base64 or upload via `npx -y @transloadit/node upload`.', }) } throw error diff --git a/packages/node/src/cli/commands/upload.ts b/packages/node/src/cli/commands/upload.ts index 01afc990..ce3826ba 100644 --- a/packages/node/src/cli/commands/upload.ts +++ b/packages/node/src/cli/commands/upload.ts @@ -7,15 +7,36 @@ import { UnauthenticatedCommand } from './BaseCommand.ts' export interface UploadOptions { file: string - tusEndpoint: string + createUploadEndpoint?: string + resumeUploadEndpoint?: string assemblyUrl: string field?: string } +const deriveEndpointFromUploadUrl = (uploadUrl: string): string => { + const url = new URL(uploadUrl) + url.pathname = url.pathname.replace(/\/[^/]*$/, '/') + return url.toString() +} + export async function upload( output: IOutputCtl, - { file, tusEndpoint, assemblyUrl, field = ':original' }: UploadOptions, + { + file, + createUploadEndpoint, + resumeUploadEndpoint, + assemblyUrl, + field = ':original', + }: UploadOptions, ): Promise { + const tusEndpoint = + createUploadEndpoint ?? + (resumeUploadEndpoint ? deriveEndpointFromUploadUrl(resumeUploadEndpoint) : undefined) + + if (!tusEndpoint) { + throw new Error('Provide --create-upload-endpoint or --resume-upload-endpoint.') + } + const stream = fs.createReadStream(file) const streamsMap = { [field]: { path: file, stream }, @@ -32,6 +53,7 @@ export async function upload( requestedChunkSize: Number.POSITIVE_INFINITY, uploadConcurrency: 1, onProgress: () => {}, + uploadUrls: resumeUploadEndpoint ? { [field]: resumeUploadEndpoint } : undefined, }) const uploadUrl = uploadUrls[field] @@ -42,6 +64,7 @@ export async function upload( field, assembly_url: assemblyUrl, tus_endpoint: tusEndpoint, + resume_upload_endpoint: resumeUploadEndpoint, upload_url: uploadUrl, }) } @@ -54,23 +77,36 @@ export class UploadCommand extends UnauthenticatedCommand { description: 'Upload a local file to a tus endpoint for an Assembly', details: ` Upload a local file to a tus endpoint and attach it to an existing Assembly. + Use --create-upload-endpoint for new uploads or --resume-upload-endpoint to resume. `, examples: [ [ 'Upload a file to an Assembly', - 'transloadit upload ./video.mp4 https://api2.transloadit.com/resumable --assembly https://api2.transloadit.com/assemblies/ASSEMBLY_ID', + 'transloadit upload ./video.mp4 --create-upload-endpoint https://api2.transloadit.com/resumable/files/ --assembly https://api2.transloadit.com/assemblies/ASSEMBLY_ID', + ], + [ + 'Resume a file upload', + 'transloadit upload ./video.mp4 --resume-upload-endpoint https://api2.transloadit.com/resumable/files/UPLOAD_ID --assembly https://api2.transloadit.com/assemblies/ASSEMBLY_ID', ], ], }) file = Option.String({ required: true }) - tusEndpoint = Option.String({ required: true }) + tusEndpoint = Option.String({ required: false }) assemblyUrl = Option.String('--assembly', { description: 'Assembly URL to attach this upload to', required: true, }) + createUploadEndpoint = Option.String('--create-upload-endpoint', { + description: 'Tus create endpoint (e.g. https://api2.transloadit.com/resumable/files/)', + }) + + resumeUploadEndpoint = Option.String('--resume-upload-endpoint', { + description: 'Tus upload URL to resume (e.g. https://.../resumable/files/)', + }) + field = Option.String('--field', { description: 'Field name for the upload (default: :original)', }) @@ -79,7 +115,8 @@ export class UploadCommand extends UnauthenticatedCommand { try { await upload(this.output, { file: this.file, - tusEndpoint: this.tusEndpoint, + createUploadEndpoint: this.createUploadEndpoint ?? this.tusEndpoint, + resumeUploadEndpoint: this.resumeUploadEndpoint, assemblyUrl: this.assemblyUrl, field: this.field, }) diff --git a/packages/node/test/e2e/cli/upload.test.ts b/packages/node/test/e2e/cli/upload.test.ts index e3b9be2e..36a628c3 100644 --- a/packages/node/test/e2e/cli/upload.test.ts +++ b/packages/node/test/e2e/cli/upload.test.ts @@ -26,7 +26,7 @@ describe('CLI upload', () => { await writeFile(filePath, 'hello from CLI upload', 'utf8') const { stdout, stderr } = await runCli( - `upload ${filePath} ${tusEndpoint} --assembly ${assemblyUrl} --field :original --json`, + `upload ${filePath} --create-upload-endpoint ${tusEndpoint} --assembly ${assemblyUrl} --field :original --json`, ) expect(stderr).toEqual('') From c5c92deb8e8c01d1665f4e8232cb547d99f1fa26 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Mon, 2 Feb 2026 15:53:43 +0100 Subject: [PATCH 2/4] chore: update transloadit parity baseline --- docs/fingerprint/transloadit-baseline.json | 11 +++++++---- docs/fingerprint/transloadit-baseline.package.json | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/fingerprint/transloadit-baseline.json b/docs/fingerprint/transloadit-baseline.json index 264fe0d8..29c3a56a 100644 --- a/docs/fingerprint/transloadit-baseline.json +++ b/docs/fingerprint/transloadit-baseline.json @@ -3,7 +3,7 @@ "tarball": { "filename": "transloadit-4.3.0.tgz", "sizeBytes": 1229255, - "sha256": "0b1adb20b160254719eedb2dbcd6f28c7561b2258840643f20867fa664090531" + "sha256": "74e3fc20bfe6e9f4d483ef7b753d4739bb75c28523124e12176562cf222d997d" }, "packageJson": { "name": "transloadit", @@ -13,7 +13,10 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": ["dist", "src"] + "files": [ + "dist", + "src" + ] }, "files": [ { @@ -678,8 +681,8 @@ }, { "path": "package.json", - "sizeBytes": 2392, - "sha256": "da426af5fcb55e65975b94d1e31b01b3e046ee561db0742e0e2a621d6a837b8b" + "sizeBytes": 2391, + "sha256": "2b4873c0c78ff09cd5c0787991b51e25fa947db35c3b9c0c36e6dd9a5c2003bd" }, { "path": "dist/alphalib/types/robots/_index.d.ts.map", diff --git a/docs/fingerprint/transloadit-baseline.package.json b/docs/fingerprint/transloadit-baseline.package.json index 78ea9f72..eaa69fa2 100644 --- a/docs/fingerprint/transloadit-baseline.package.json +++ b/docs/fingerprint/transloadit-baseline.package.json @@ -19,7 +19,7 @@ "dependencies": { "@aws-sdk/client-s3": "^3.891.0", "@aws-sdk/s3-request-presigner": "^3.891.0", - "@transloadit/sev-logger": "^0.0.15", + "@transloadit/sev-logger": "^0.1.9", "@transloadit/utils": "^4.3.0", "clipanion": "^4.0.0-rc.4", "debug": "^4.4.3", From 7e1cb75c096089496c02fca08a8d5008005f84bf Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Mon, 2 Feb 2026 16:03:18 +0100 Subject: [PATCH 3/4] chore: format transloadit baseline --- docs/fingerprint/transloadit-baseline.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/fingerprint/transloadit-baseline.json b/docs/fingerprint/transloadit-baseline.json index 29c3a56a..8ea7e82a 100644 --- a/docs/fingerprint/transloadit-baseline.json +++ b/docs/fingerprint/transloadit-baseline.json @@ -13,10 +13,7 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ] + "files": ["dist", "src"] }, "files": [ { From ebe010859d1139e19f69e8d4591a20e50ca7fc9e Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Mon, 2 Feb 2026 16:16:44 +0100 Subject: [PATCH 4/4] chore: refresh transloadit parity baseline --- docs/fingerprint/transloadit-baseline.json | 69 +++++++++++++------ .../transloadit-baseline.package.json | 2 +- packages/transloadit/package.json | 2 +- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/docs/fingerprint/transloadit-baseline.json b/docs/fingerprint/transloadit-baseline.json index 8ea7e82a..fde572d7 100644 --- a/docs/fingerprint/transloadit-baseline.json +++ b/docs/fingerprint/transloadit-baseline.json @@ -1,13 +1,13 @@ { "packageDir": "/home/kvz/code/node-sdk/packages/transloadit", "tarball": { - "filename": "transloadit-4.3.0.tgz", - "sizeBytes": 1229255, - "sha256": "74e3fc20bfe6e9f4d483ef7b753d4739bb75c28523124e12176562cf222d997d" + "filename": "transloadit-4.3.1.tgz", + "sizeBytes": 1232790, + "sha256": "089e952a8e6665659675b0647fed80d99d3f4252a0f0845863a9ed218ee9d166" }, "packageJson": { "name": "transloadit", - "version": "4.3.0", + "version": "4.3.1", "main": "./dist/Transloadit.js", "exports": { ".": "./dist/Transloadit.js", @@ -388,8 +388,8 @@ }, { "path": "dist/cli/commands/index.js", - "sizeBytes": 1794, - "sha256": "cea0e51dbb809beef425325c681fc3ce087a082f02ff66b6474001a11b2fbd37" + "sizeBytes": 1896, + "sha256": "64bc6458f6f1cd1db339646519dcbab942ccabe965b44d50b1e74dac9ded060a" }, { "path": "dist/inputFiles.js", @@ -583,8 +583,8 @@ }, { "path": "dist/Transloadit.js", - "sizeBytes": 36726, - "sha256": "1b3ded5575fb9e02032831df6f5ca10b6c33b0181b59cf44a195248f78bd68ef" + "sizeBytes": 36883, + "sha256": "ef4717bcc8930b750f5d8466097eec96b5f636eeeffe314cb5bde141562ae4d5" }, { "path": "dist/alphalib/tryCatch.js", @@ -611,6 +611,11 @@ "sizeBytes": 2710, "sha256": "0da0cf7c28a54af82ac125af0129f885b111b9e48cd64c477865d5438e29974d" }, + { + "path": "dist/cli/commands/upload.js", + "sizeBytes": 3831, + "sha256": "c9267bff59d4b62ed455773ff56ee915a8c196b46c095ef4e9f2ee9542077a9a" + }, { "path": "dist/alphalib/types/robots/video-adaptive.js", "sizeBytes": 6059, @@ -679,7 +684,7 @@ { "path": "package.json", "sizeBytes": 2391, - "sha256": "2b4873c0c78ff09cd5c0787991b51e25fa947db35c3b9c0c36e6dd9a5c2003bd" + "sha256": "156ee2b53de31d01d43e3a9d5e8299c6e512d63d6ef63ced6428a0685b51effe" }, { "path": "dist/alphalib/types/robots/_index.d.ts.map", @@ -1414,12 +1419,12 @@ { "path": "dist/cli/commands/index.d.ts.map", "sizeBytes": 198, - "sha256": "391845fbe56809711ea35e1667befbf872dd9072875c6983b0990b247d5496cc" + "sha256": "d9bef1f3fe0f9ff442cdd7e4929e0ec89c711911bc732d337bc557c880a5a6cd" }, { "path": "dist/cli/commands/index.js.map", - "sizeBytes": 1640, - "sha256": "52cc8c7351fa5905ce7541db198cef4fc55f504a868ca673e843ee8dcb988d16" + "sizeBytes": 1735, + "sha256": "d57ecf1ffc0409c294f83829b8deef9e7e369987439d0f6d089db41ddcf8e901" }, { "path": "dist/inputFiles.d.ts.map", @@ -1803,13 +1808,13 @@ }, { "path": "dist/Transloadit.d.ts.map", - "sizeBytes": 6364, - "sha256": "d04fe4e23e6f9c46f828838b60d0e3999a0d3f33f7e7ff0e193d280e5d6e8da5" + "sizeBytes": 6406, + "sha256": "51f9faaa4e12826a77c2ab0818cbb850a044e6d27d686c02578b8ff14c414313" }, { "path": "dist/Transloadit.js.map", - "sizeBytes": 26804, - "sha256": "42b0aada7680ba8686ce130b06b70fff5a0c75f2f81aa28f834eaea49fd58a4a" + "sizeBytes": 26946, + "sha256": "33b7676224bfd7d0b2a6c6e448cce67d0929eda5fefc05beb70f57550e6a93d5" }, { "path": "dist/alphalib/tryCatch.d.ts.map", @@ -1861,6 +1866,16 @@ "sizeBytes": 1547, "sha256": "5565507cacaccfbae0ed8af22fa55d0f2e1764db6d5b512ca275d2dc0baa52c7" }, + { + "path": "dist/cli/commands/upload.d.ts.map", + "sizeBytes": 768, + "sha256": "787cbbb01c715dc37945cf86faa3429f220a6bdf4fdbd9b7a0d37b9413eb1406" + }, + { + "path": "dist/cli/commands/upload.js.map", + "sizeBytes": 2896, + "sha256": "ddf7dac4f6d709cd551705e61fb68626d2c54752ca8289dea58831ca0da615c7" + }, { "path": "dist/alphalib/types/robots/video-adaptive.d.ts.map", "sizeBytes": 3703, @@ -2733,8 +2748,8 @@ }, { "path": "src/cli/commands/index.ts", - "sizeBytes": 1711, - "sha256": "a4646e7d078b97e32d7a3c0c0f61aeb32898d1b25bda89ba20703a23b302f6f2" + "sizeBytes": 1808, + "sha256": "f0ab91c3da3ee8de42cc005ef25d4f6972746606082b4030e596d4c946f4963e" }, { "path": "dist/inputFiles.d.ts", @@ -3123,13 +3138,13 @@ }, { "path": "dist/Transloadit.d.ts", - "sizeBytes": 11723, - "sha256": "dee5f012aaf6faef6ca2154f3566c97aeaaf95ff07433e2573628e215dbbf9d3" + "sizeBytes": 11847, + "sha256": "5a280d61733d751cb2c9137fb19557dab9e4b2cc8e48c89bfad7474ce0bc0e06" }, { "path": "src/Transloadit.ts", - "sizeBytes": 41153, - "sha256": "198560ba943a5c33862e8b735b66a2bb7483d76d29e43efdc7354283217202f1" + "sizeBytes": 41433, + "sha256": "02d6ca96421b0ec39e31c4fb40a06537f2a61ba87787f9e904907f6463f854ea" }, { "path": "dist/alphalib/tryCatch.d.ts", @@ -3181,6 +3196,16 @@ "sizeBytes": 3439, "sha256": "4bf3de4456a3aa53d370f4568a0ab1c5423ac8f251d83c62b593fe7d8f814a9d" }, + { + "path": "dist/cli/commands/upload.d.ts", + "sizeBytes": 869, + "sha256": "6078873d1b89afa675ea4aa6bd6b87b7ae14147d7b1982b8d89f2d5ef55376aa" + }, + { + "path": "src/cli/commands/upload.ts", + "sizeBytes": 3927, + "sha256": "19ba88a9bccab46628a9c1c0e909ab7cf5a2605b5a08c85ec320d2ff46a4da9f" + }, { "path": "dist/alphalib/types/robots/video-adaptive.d.ts", "sizeBytes": 213318, diff --git a/docs/fingerprint/transloadit-baseline.package.json b/docs/fingerprint/transloadit-baseline.package.json index eaa69fa2..89e73f4d 100644 --- a/docs/fingerprint/transloadit-baseline.package.json +++ b/docs/fingerprint/transloadit-baseline.package.json @@ -1,6 +1,6 @@ { "name": "transloadit", - "version": "4.3.0", + "version": "4.3.1", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": [ diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index eaa69fa2..89e73f4d 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -1,6 +1,6 @@ { "name": "transloadit", - "version": "4.3.0", + "version": "4.3.1", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": [