submit.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { CliError } from '@jackwener/opencli/errors'; 3 import { PAPERREVIEW_DOMAIN, ensureApiSuccess, ensureSuccess, normalizeVenue, readPdfFile, requestJson, summarizeSubmission, uploadPresignedPdf, } from './utils.js'; 4 cli({ 5 site: 'paperreview', 6 name: 'submit', 7 description: 'Submit a PDF to paperreview.ai for review', 8 domain: PAPERREVIEW_DOMAIN, 9 strategy: Strategy.PUBLIC, 10 browser: false, 11 timeoutSeconds: 120, 12 args: [ 13 { name: 'pdf', positional: true, required: true, help: 'Path to the paper PDF' }, 14 { name: 'email', required: true, help: 'Email address for the submission' }, 15 { name: 'venue', help: 'Optional target venue such as ICLR or NeurIPS' }, 16 { name: 'dry-run', type: 'bool', default: false, help: 'Validate the input and stop before remote submission' }, 17 { name: 'prepare-only', type: 'bool', default: false, help: 'Request an upload slot but stop before uploading the PDF' }, 18 ], 19 columns: ['status', 'file', 'email', 'venue', 'token', 'review_url', 'message'], 20 footerExtra: (kwargs) => { 21 if (kwargs['dry-run'] === true) 22 return 'dry run only'; 23 if (kwargs['prepare-only'] === true) 24 return 'prepared only'; 25 return undefined; 26 }, 27 func: async (_page, kwargs) => { 28 const pdfFile = await readPdfFile(kwargs.pdf); 29 const email = String(kwargs.email ?? '').trim(); 30 const venue = normalizeVenue(kwargs.venue); 31 const dryRun = kwargs['dry-run'] === true; 32 const prepareOnly = kwargs['prepare-only'] === true; 33 if (!email) { 34 throw new CliError('ARGUMENT', 'An email address is required.', 'Pass --email <address>'); 35 } 36 if (dryRun) { 37 return summarizeSubmission({ 38 pdfFile, 39 email, 40 venue, 41 message: 'Input validation passed. No remote request was sent.', 42 dryRun: true, 43 }); 44 } 45 const { response: uploadUrlResponse, payload: uploadUrlPayload } = await requestJson('/api/get-upload-url', { 46 method: 'POST', 47 headers: { 'Content-Type': 'application/json' }, 48 body: JSON.stringify({ 49 filename: pdfFile.fileName, 50 venue, 51 }), 52 }); 53 ensureSuccess(uploadUrlResponse, uploadUrlPayload, 'Failed to request an upload URL.', 'Try again in a moment'); 54 ensureApiSuccess(uploadUrlPayload, 'paperreview.ai did not return a usable upload URL.', 'Try again in a moment'); 55 if (prepareOnly) { 56 return summarizeSubmission({ 57 pdfFile, 58 email, 59 venue, 60 message: 'Upload slot prepared. The PDF was not uploaded and no submission was confirmed.', 61 s3Key: uploadUrlPayload.s3_key, 62 status: 'prepared', 63 }); 64 } 65 await uploadPresignedPdf(uploadUrlPayload.presigned_url, pdfFile, uploadUrlPayload); 66 const confirmForm = new FormData(); 67 confirmForm.append('s3_key', uploadUrlPayload.s3_key); 68 confirmForm.append('venue', venue); 69 confirmForm.append('email', email); 70 const { response: confirmResponse, payload: confirmPayload } = await requestJson('/api/confirm-upload', { 71 method: 'POST', 72 body: confirmForm, 73 }); 74 ensureSuccess(confirmResponse, confirmPayload, 'Failed to confirm the upload with paperreview.ai.', 'Try again in a moment'); 75 ensureApiSuccess(confirmPayload, 'paperreview.ai did not confirm the submission.', 'Try again in a moment'); 76 return summarizeSubmission({ 77 pdfFile, 78 email, 79 venue, 80 token: confirmPayload.token, 81 message: confirmPayload.message, 82 s3Key: uploadUrlPayload.s3_key, 83 }); 84 }, 85 });