nemar-cli 0.3.7-dev.113 → 0.3.7-dev.118

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
@@ -372,14 +372,73 @@ graph TB
372
372
  REL -->|Archive| DOI
373
373
  ```
374
374
 
375
+ ## Testing
376
+
377
+ ### Running Tests
378
+
379
+ ```bash
380
+ # All tests
381
+ bun test
382
+
383
+ # Specific test file
384
+ bun test test/cli.test.ts
385
+ bun test test/api.test.ts
386
+ ```
387
+
388
+ ### Zenodo Sandbox Tests
389
+
390
+ DOI workflows can be tested using Zenodo's sandbox environment without affecting production:
391
+
392
+ ```bash
393
+ # Run Zenodo sandbox tests (requires configuration)
394
+ RUN_ZENODO_TESTS=true TEST_DATASET_ID=nm099999 bun test test/zenodo-sandbox.test.ts
395
+ ```
396
+
397
+ **Setup requirements:**
398
+ 1. Create account on [sandbox.zenodo.org](https://sandbox.zenodo.org)
399
+ 2. Generate API token with `deposit:write` scope
400
+ 3. Add to `test/.env.test`:
401
+ ```bash
402
+ ZENODO_SANDBOX_API_KEY=your_sandbox_token
403
+ RUN_ZENODO_TESTS=true
404
+ TEST_DATASET_ID=nm099999
405
+ ```
406
+
407
+ **Test coverage:**
408
+ - Concept DOI creation and retrieval
409
+ - Version DOI publishing workflows
410
+ - Metadata updates (title, keywords, related identifiers)
411
+ - Error handling (401, 404, 400 responses)
412
+ - Rate limiting and request throttling
413
+ - Deposition lifecycle (create → upload → publish)
414
+ - File uploads with checksum verification
415
+
416
+ See [docs/development/zenodo-testing.md](docs/development/zenodo-testing.md) for comprehensive guide.
417
+
375
418
  ## Environment Variables
376
419
 
420
+ ### CLI Usage
421
+
377
422
  ```bash
378
423
  NEMAR_API_KEY # API key (alternative to login)
379
424
  NEMAR_API_URL # Custom API endpoint (default: https://api.nemar.org)
380
425
  NEMAR_NO_COLOR # Disable colored output
381
426
  ```
382
427
 
428
+ ### Testing
429
+
430
+ ```bash
431
+ # Required for Zenodo sandbox tests
432
+ ZENODO_SANDBOX_API_KEY # Sandbox Zenodo API token
433
+ RUN_ZENODO_TESTS # Enable Zenodo tests (set to "true")
434
+ TEST_DATASET_ID # Test dataset ID (default: nm099999)
435
+
436
+ # API testing
437
+ TEST_API_URL # Test API endpoint (dev environment)
438
+ TEST_ADMIN_API_KEY # Admin API key for tests
439
+ TEST_USER_API_KEY # User API key for tests
440
+ ```
441
+
383
442
  ## Troubleshooting
384
443
 
385
444
  ### Upload Issues
package/dist/index.js CHANGED
@@ -145,7 +145,7 @@ ${E.cyan("CI Status:")} ${D}
145
145
  `);let B=$.bids_validation.present,J=B?E.green("[x]"):E.red("[ ]");if(console.log(` ${J} BIDS Validation`),B){let X=$.bids_validation.status==="success"?E.green:$.bids_validation.status==="failure"?E.red:E.yellow;if(console.log(` Status: ${X($.bids_validation.status)}`),$.bids_validation.url)console.log(` URL: ${E.gray($.bids_validation.url)}`)}else console.log(` ${E.gray("Not deployed. Use 'nemar admin ci add' to deploy.")}`);let Q=$.version_check.present,Y=Q?E.green("[x]"):E.red("[ ]");if(console.log(` ${Y} Version Check`),!Q)console.log(` ${E.gray("Not deployed. Use 'nemar admin ci add' to deploy.")}`);console.log()}catch($){if($ instanceof u){if(F.fail($.message),$.statusCode===403)console.log(E.gray(" This command requires admin privileges"));else if($.statusCode===404)console.log(E.gray(" Dataset not found"))}else{F.fail("Failed to check CI status");let B=$ instanceof Error?$.message:String($);console.log(E.gray(` Error details: ${B}`))}}});C2.command("add").description("Deploy CI workflows to a dataset repository").argument("<dataset-id>","Dataset ID (e.g., nm000104)").option(_D,fD).option(tD,eD).action(async(D,F)=>{if(!w0())return;console.log(E.cyan(`
146
146
  Deploy CI workflows to: ${D}
147
147
  `)),console.log("This will add the following workflows:"),console.log(" 1. BIDS Validation (runs on PRs)"),console.log(" 2. Version Check (ensures version bump on PRs)"),console.log(" 3. PR Merge Handler (creates releases, publishes DOIs)"),console.log();let $=await rD(`Deploy CI workflows to ${D}?`,F);if($!=="confirmed"){console.log(E.gray($==="declined"?"Skipped":"Cancelled"));return}let B=C(`Deploying CI workflows to ${D}...`).start();try{let J=await K$(D);B.succeed("CI workflows deployed"),console.log();for(let Q of J.workflows_deployed)console.log(` ${E.green("[x]")} ${Q}`);console.log()}catch(J){if(J instanceof u){if(B.fail(J.message),J.statusCode===403)console.log(E.gray(" This command requires admin privileges"));else if(J.statusCode===404)console.log(E.gray(" Dataset not found"))}else{B.fail("Failed to deploy CI workflows");let Q=J instanceof Error?J.message:String(J);console.log(E.gray(` Error details: ${Q}`))}}});n0.addCommand(C2);var R2=new D0("doi").description("DOI management");R2.command("create").description("Create concept DOI for a dataset").argument("<dataset-id>","Dataset ID (e.g., nm000104)").option("--title <title>","DOI title (defaults to dataset name)").option("--description <desc>","DOI description").option("--sandbox","Use Zenodo sandbox for testing").option(_D,fD).option(tD,eD).action(async(D,F)=>{if(!w0())return;let $=C("Fetching dataset info...").start(),B;try{B=await e8(D),$.succeed(`Found dataset: ${B.name}`)}catch(X){if(X instanceof u){if($.fail(X.message),X.statusCode===404)console.log(E.gray(" Dataset not found"))}else $.fail("Failed to fetch dataset");return}try{let X=await A2(D);if(X.concept_doi){if(console.log(E.yellow(`
148
- Dataset already has a concept DOI:`)),console.log(` Concept DOI: ${E.cyan(X.concept_doi)}`),X.zenodo_concept_url)console.log(` Zenodo URL: ${X.zenodo_concept_url}`);return}}catch{}if(console.log(),console.log(E.cyan("Dataset Information:")),console.log(` ID: ${B.dataset_id}`),console.log(` Name: ${B.name}`),B.github_repo)console.log(` GitHub: ${B.github_repo}`);if(F.sandbox)console.log(` Mode: ${E.yellow("SANDBOX (test DOI)")}`);console.log(),console.log(E.red("WARNING: DOIs are PERMANENT and cannot be deleted!")),console.log(E.gray("The DOI will be pre-reserved but not published until the first version release.")),console.log();let J=F.sandbox?"Create test concept DOI on Zenodo sandbox?":"Create concept DOI on Zenodo?",Q=await rD(J,F);if(Q!=="confirmed"){console.log(E.gray(Q==="declined"?"Skipped":"Cancelled"));return}let Y=C("Creating concept DOI on Zenodo...").start();try{let X=await Cv(D,{title:F.title,description:F.description,sandbox:F.sandbox});Y.succeed("Concept DOI created successfully");let G=C("Applying branch protection...").start();try{let H=await V$(D);if(H.warnings&&H.warnings.length>0){G.warn("Branch protection applied with warnings");for(let W of H.warnings)console.log(E.yellow(` Warning: ${W}`))}else G.succeed("Branch protection applied")}catch(H){if(G.warn("Could not apply branch protection"),H instanceof u){if(console.log(E.gray(` ${H.message}`)),H.statusCode===403)console.log(E.gray(" Check admin credentials and permissions"))}else console.log(E.gray(` ${H instanceof Error?H.message:"Unknown error"}`));console.log(E.gray(" Manual setup: Go to GitHub repo Settings > Branches > Add rule"))}if(console.log(),console.log(E.green("DOI Information:")),console.log(` Concept DOI: ${E.cyan(X.concept_doi)}`),console.log(` Zenodo URL: ${X.zenodo_url}`),console.log(),console.log(E.yellow("Next steps:")),console.log(" 1. Set up automatic DOI publishing by running:"),console.log(E.gray(` ${X.setup_command}`)),console.log(" (paste the webhook token when prompted)"),console.log(),console.log(" 2. Update dataset_description.json with DatasetDOI field"),console.log(" 3. Create a PR and merge it to trigger version DOI publication"),console.log(),F.sandbox)console.log(E.gray("Note: This is a sandbox DOI and will not resolve in production."))}catch(X){if(X instanceof u){if(Y.fail(X.message),X.statusCode===403)console.log(E.gray(" This command requires admin privileges"))}else Y.fail("Failed to create concept DOI"),console.log(E.gray(` ${X instanceof Error?X.message:"Unknown error"}`))}});R2.command("info").description("Get DOI info for a dataset").argument("<dataset-id>","Dataset ID (e.g., nm000104)").action(async(D)=>{if(!w0())return;let F=C("Fetching DOI info...").start();try{let $=await A2(D);if(F.stop(),console.log(),console.log(E.cyan(`DOI Information for ${D}:`)),console.log(` Dataset Name: ${$.name}`),console.log(),$.concept_doi){if(console.log(E.green("Concept DOI:")),console.log(` DOI: ${$.concept_doi}`),console.log(` URL: https://doi.org/${$.concept_doi}`),$.zenodo_concept_url)console.log(` Zenodo: ${$.zenodo_concept_url}`)}else console.log(E.yellow("No concept DOI created yet")),console.log(E.gray(" Use 'nemar admin doi create' to create one"));if(console.log(),$.latest_version_doi){if(console.log(E.green("Latest Version DOI:")),console.log(` DOI: ${$.latest_version_doi}`),console.log(` URL: https://doi.org/${$.latest_version_doi}`),$.zenodo_latest_version_url)console.log(` Zenodo: ${$.zenodo_latest_version_url}`)}else if($.concept_doi)console.log(E.yellow("No version DOI published yet")),console.log(E.gray(" Version DOIs are created automatically on PR merge"))}catch($){if($ instanceof u){if(F.fail($.message),$.statusCode===404)console.log(E.gray(" Dataset not found"));else if($.statusCode===403)console.log(E.gray(" This command requires admin privileges"))}else F.fail("Failed to fetch DOI info")}});n0.addCommand(R2);var T$=new D0("publish").description("Publication workflow management");T$.command("list").description("List publication requests").option("-s, --status <status>","Filter by status (requested, approving, published, denied)").addHelpText("after",`
148
+ Dataset already has a concept DOI:`)),console.log(` Concept DOI: ${E.cyan(X.concept_doi)}`),X.zenodo_concept_url)console.log(` Zenodo URL: ${X.zenodo_concept_url}`);return}}catch{}if(console.log(),console.log(E.cyan("Dataset Information:")),console.log(` ID: ${B.dataset_id}`),console.log(` Name: ${B.name}`),B.github_repo)console.log(` GitHub: ${B.github_repo}`);if(F.sandbox)console.log(` Mode: ${E.yellow("SANDBOX (test DOI)")}`);if(console.log(),F.sandbox)console.log(E.yellow("\u2501".repeat(60))),console.log(E.yellow.bold(" SANDBOX MODE ENABLED")),console.log(E.yellow("\u2501".repeat(60))),console.log(E.yellow(" \u2022 Using Zenodo sandbox (sandbox.zenodo.org)")),console.log(E.yellow(" \u2022 DOI will NOT be indexed by DataCite")),console.log(E.yellow(" \u2022 DOI will NOT resolve in production")),console.log(E.yellow(" \u2022 Use this for testing workflows only")),console.log(E.yellow("\u2501".repeat(60))),console.log();console.log(E.red("WARNING: DOIs are PERMANENT and cannot be deleted!")),console.log(E.gray("The DOI will be pre-reserved but not published until the first version release.")),console.log();let J=F.sandbox?"Create TEST concept DOI on Zenodo SANDBOX?":"Create PERMANENT concept DOI on Zenodo PRODUCTION?",Q=await rD(J,F);if(Q!=="confirmed"){console.log(E.gray(Q==="declined"?"Skipped":"Cancelled"));return}let Y=C("Creating concept DOI on Zenodo...").start();try{let X=await Cv(D,{title:F.title,description:F.description,sandbox:F.sandbox});Y.succeed("Concept DOI created successfully");let G=C("Applying branch protection...").start();try{let H=await V$(D);if(H.warnings&&H.warnings.length>0){G.warn("Branch protection applied with warnings");for(let W of H.warnings)console.log(E.yellow(` Warning: ${W}`))}else G.succeed("Branch protection applied")}catch(H){if(G.warn("Could not apply branch protection"),H instanceof u){if(console.log(E.gray(` ${H.message}`)),H.statusCode===403)console.log(E.gray(" Check admin credentials and permissions"))}else console.log(E.gray(` ${H instanceof Error?H.message:"Unknown error"}`));console.log(E.gray(" Manual setup: Go to GitHub repo Settings > Branches > Add rule"))}if(console.log(),console.log(E.green("DOI Information:")),console.log(` Concept DOI: ${E.cyan(X.concept_doi)}`),console.log(` Zenodo URL: ${X.zenodo_url}`),console.log(),console.log(E.yellow("Next steps:")),console.log(" 1. Set up automatic DOI publishing by running:"),console.log(E.gray(` ${X.setup_command}`)),console.log(" (paste the webhook token when prompted)"),console.log(),console.log(" 2. Update dataset_description.json with DatasetDOI field"),console.log(" 3. Create a PR and merge it to trigger version DOI publication"),console.log(),F.sandbox)console.log(E.gray("Note: This is a sandbox DOI and will not resolve in production."))}catch(X){if(X instanceof u){if(Y.fail(X.message),X.statusCode===403)console.log(E.gray(" This command requires admin privileges"))}else Y.fail("Failed to create concept DOI"),console.log(E.gray(` ${X instanceof Error?X.message:"Unknown error"}`))}});R2.command("info").description("Get DOI info for a dataset").argument("<dataset-id>","Dataset ID (e.g., nm000104)").action(async(D)=>{if(!w0())return;let F=C("Fetching DOI info...").start();try{let $=await A2(D);if(F.stop(),console.log(),console.log(E.cyan(`DOI Information for ${D}:`)),console.log(` Dataset Name: ${$.name}`),console.log(),$.concept_doi){if(console.log(E.green("Concept DOI:")),console.log(` DOI: ${$.concept_doi}`),console.log(` URL: https://doi.org/${$.concept_doi}`),$.zenodo_concept_url){if(console.log(` Zenodo: ${$.zenodo_concept_url}`),$.zenodo_concept_url.includes("sandbox.zenodo.org"))console.log(),console.log(E.yellow("Mode: SANDBOX (test DOI)")),console.log(E.yellow(" This DOI is not indexed by DataCite and will not resolve in production"))}}else console.log(E.yellow("No concept DOI created yet")),console.log(E.gray(" Use 'nemar admin doi create' to create one"));if(console.log(),$.latest_version_doi){if(console.log(E.green("Latest Version DOI:")),console.log(` DOI: ${$.latest_version_doi}`),console.log(` URL: https://doi.org/${$.latest_version_doi}`),$.zenodo_latest_version_url)console.log(` Zenodo: ${$.zenodo_latest_version_url}`)}else if($.concept_doi)console.log(E.yellow("No version DOI published yet")),console.log(E.gray(" Version DOIs are created automatically on PR merge"))}catch($){if($ instanceof u){if(F.fail($.message),$.statusCode===404)console.log(E.gray(" Dataset not found"));else if($.statusCode===403)console.log(E.gray(" This command requires admin privileges"))}else F.fail("Failed to fetch DOI info")}});n0.addCommand(R2);var T$=new D0("publish").description("Publication workflow management");T$.command("list").description("List publication requests").option("-s, --status <status>","Filter by status (requested, approving, published, denied)").addHelpText("after",`
149
149
  Description:
150
150
  List all publication requests from users, with optional filtering by status.
151
151
  Shows dataset ID, status, requesting user, and current progress.
@@ -563,7 +563,7 @@ Examples:
563
563
  $ nemar sandbox # Run sandbox training
564
564
  $ nemar sandbox status # Check if training is completed
565
565
  $ nemar sandbox reset # Reset for re-training
566
- `).action(MGD);async function MGD(){if(console.log(),console.log(E.bold("NEMAR Sandbox Training")),console.log(E.gray("Verify your setup and learn the upload workflow")),console.log(),!PD()){console.log(E.red("Not authenticated")),console.log(E.gray("Run 'nemar auth login' first"));return}let D=sD();if(D.sandboxCompleted){console.log(E.green("Sandbox training already completed!")),console.log(E.gray(`Dataset ID: ${D.sandboxDatasetId}`)),console.log(),console.log("You can upload real datasets with:"),console.log(E.cyan(" nemar dataset upload ./your-dataset")),console.log(),console.log(E.gray("To re-run training, use: nemar sandbox reset"));return}console.log(E.bold("Step 1/6: Checking prerequisites..."));let F=C("Checking git-annex and SSH...").start(),$=await A$();if(!$.allPassed){F.fail("Prerequisites check failed"),console.log(),console.log(E.red("Missing requirements:"));for(let P of $.errors)console.log(E.yellow(` - ${P}`));if(!$.githubSSH.accessible)console.log(E.gray(" Run 'nemar auth setup-ssh' to configure SSH"));return}F.succeed("All prerequisites met");let B=C("Verifying GitHub CLI authentication...").start(),J=await L$(D.githubUsername);if(!J.authenticated){B.fail("GitHub CLI not authenticated"),console.log(E.red(` ${J.error}`)),console.log(),console.log("GitHub CLI is required for sandbox training. Install and authenticate:"),console.log(E.cyan(" brew install gh # or visit https://cli.github.com/")),console.log(E.cyan(" gh auth login"));return}if(D.githubUsername&&!J.matches)B.warn("GitHub CLI user mismatch"),console.log(E.yellow(` ${J.error}`)),console.log(),console.log("Your gh CLI is authenticated as a different GitHub account than your NEMAR account."),console.log("This may cause issues with repository access. To fix:"),console.log(E.cyan(` gh auth login # Login as ${D.githubUsername}`)),console.log(),console.log(E.yellow("WARNING: If upload fails with permission errors, this mismatch is the likely cause.")),console.log();else B.succeed(`GitHub CLI authenticated as ${J.username}`);console.log(),console.log(E.bold("Step 2/6: Generating test dataset..."));let Q=C("Creating minimal BIDS structure...").start(),Y;try{let P=Ky();Y=P.root;let VD=zy(P);Q.succeed(`Test dataset created (${C$(VD)})`),console.log(E.gray(` Location: ${Y}`))}catch(P){Q.fail("Failed to generate test dataset"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`));return}console.log(),console.log(E.bold("Step 3/6: Registering sandbox dataset..."));let X=C("Creating dataset on NEMAR...").start(),G,H,W,K,V,A;try{let P=await z$({name:"Sandbox Training Dataset",description:"Placeholder dataset for sandbox training",files:[{path:"sub-01/eeg/sub-01_task-rest_eeg.edf",size:512000,type:"data"},{path:"dataset_description.json",size:200,type:"metadata"},{path:"participants.tsv",size:50,type:"metadata"},{path:"README",size:500,type:"metadata"},{path:"sub-01/eeg/sub-01_task-rest_eeg.json",size:300,type:"metadata"}],sandbox:!0});G=P.dataset.dataset_id,H=P.dataset.ssh_url,W=P.dataset.github_url,K=P.s3_config,V=P.dataset.s3_prefix,A=P.upload_urls||{},X.succeed(`Sandbox dataset created: ${E.cyan(G)}`),console.log(E.gray(` GitHub: ${W}`)),await new Promise((VD)=>setTimeout(VD,1e4))}catch(P){if(X.fail("Failed to create sandbox dataset"),P instanceof u)console.log(E.red(` ${P.message}`));else console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`));A1(Y);return}let Z=C("Accepting GitHub repository invitation...").start(),U=W?.match(/github\.com\/([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/),L=U?U[1].replace(/\.git$/,""):null;if(!L){Z.fail("Invalid GitHub repository URL from backend"),console.log(E.red(` Received: ${W||"(empty)"}`)),console.log(E.red(" Expected format: https://github.com/owner/repo")),console.log(),console.log("This may indicate a backend issue. Please contact support."),A1(Y);return}let M=await M$(L);if(M.accepted)if(M.alreadyCollaborator)Z.succeed("Already a collaborator on this repository");else Z.succeed("GitHub invitation accepted");else Z.warn("Could not auto-accept invitation"),console.log(E.yellow(` ${M.error}`)),console.log(),console.log("You may need to accept the invitation manually:"),console.log(E.cyan(` https://github.com/${L}/invitations`)),console.log();console.log(),console.log(E.bold("Step 4/6: Initializing repository..."));let w=C("Setting up git-annex...").start(),I=D.username&&D.email?{name:D.username,email:D.email}:void 0;try{await Z$(Y,{author:I}),await U$(Y),await N$(Y,H),w.succeed("Repository initialized")}catch(P){w.fail("Failed to initialize repository"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`)),A1(Y);return}if(console.log(),console.log(E.bold("Step 5/6: Uploading to S3...")),Object.keys(A).length===0)console.log(E.yellow(" No data files to upload (metadata only)"));else{let P=C("Uploading test data...").start();try{let yD=0,g=Object.keys(A).length,ED=await R$(Y,A,{jobs:4,onProgress:(WD)=>{if(WD.status==="completed"||WD.status==="failed")yD++,P.text=`Uploading... ${yD}/${g} files`}});if(ED.failed.length>0){P.fail(`Upload failed for ${ED.failed.length} file(s)`);for(let WD of ED.failed)console.log(E.red(` Failed: ${WD}`));if(ED.error)console.log(E.red(` Error: ${ED.error}`));console.log(),console.log(E.yellow("Sandbox training aborted due to upload failures.")),console.log(E.gray("Please check your network connection and try again.")),A1(Y);return}P.succeed(`Uploaded ${ED.uploaded} file(s)`)}catch(yD){P.fail("Upload failed"),console.log(E.red(` ${yD instanceof Error?yD.message:"Unknown error"}`)),A1(Y);return}let VD=C("Registering file URLs...").start();try{let yD={};for(let ED of Object.keys(A))yD[ED]=`${K.public_url}/${V}/${ED}`;let g=await w$(Y,yD);if(!g.success){VD.fail(`URL registration failed for ${g.failed.length} file(s)`);for(let ED of g.failed)console.log(E.red(` Failed: ${ED}`));console.log(),console.log(E.yellow("Sandbox training aborted due to URL registration failures.")),console.log(E.gray("This may indicate a git-annex configuration issue.")),A1(Y);return}VD.succeed(`Registered ${g.registered} file URLs`)}catch(yD){VD.fail("Failed to register URLs"),console.log(E.red(` ${yD instanceof Error?yD.message:"Unknown error"}`)),A1(Y);return}}console.log(),console.log(E.bold("Step 6/6: Pushing to GitHub..."));let b=C("Saving and pushing...").start();try{await W5(Y,"Initial sandbox training upload",I),await E5(Y),b.succeed("Pushed to GitHub")}catch(P){b.fail("Failed to push to GitHub"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`)),A1(Y);return}let T=C("Finalizing...").start();try{await V$(G),await Ov(G),q0("sandboxCompleted",!0),q0("sandboxDatasetId",G),T.succeed("Sandbox training complete!")}catch(P){T.fail("Failed to finalize"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`)),A1(Y);return}A1(Y),console.log(),console.log(E.green.bold("Congratulations! Sandbox training completed successfully.")),console.log(),console.log("Your setup is verified and you're ready to upload real datasets:"),console.log(E.cyan(" nemar dataset upload ./your-dataset")),console.log(),console.log(E.gray(`Sandbox dataset: ${G}`))}v$.command("status").description("Check sandbox training completion status").option("--refresh","Fetch latest status from server").action(async(D)=>{if(!PD()){console.log(E.red("Not authenticated")),console.log(E.gray("Run 'nemar auth login' first"));return}if(D.refresh){let F=C("Checking status...").start();try{let $=await Iv();if(q0("sandboxCompleted",$.sandbox_completed),$.sandbox_dataset_id)q0("sandboxDatasetId",$.sandbox_dataset_id);if(F.stop(),$.sandbox_completed){if(console.log(E.green("Sandbox training: Completed")),console.log(E.gray(` Dataset ID: ${$.sandbox_dataset_id}`)),$.sandbox_completed_at)console.log(E.gray(` Completed: ${$.sandbox_completed_at}`))}else console.log(E.yellow("Sandbox training: Not completed")),console.log(),console.log("Run sandbox training with:"),console.log(E.cyan(" nemar sandbox"))}catch($){if(F.fail("Failed to check status"),$ instanceof u)console.log(E.red(` ${$.message}`))}}else{let F=sD();if(F.sandboxCompleted)console.log(E.green("Sandbox training: Completed")),console.log(E.gray(` Dataset ID: ${F.sandboxDatasetId}`));else console.log(E.yellow("Sandbox training: Not completed")),console.log(),console.log("Run sandbox training with:"),console.log(E.cyan(" nemar sandbox"))}});v$.command("reset").description("Reset sandbox training status for re-training").option(_D,fD).option(tD,eD).action(async(D)=>{if(!PD()){console.log(E.red("Not authenticated")),console.log(E.gray("Run 'nemar auth login' first"));return}if(!sD().sandboxCompleted){console.log(E.yellow("Sandbox training not yet completed")),console.log(E.gray("Nothing to reset"));return}let $=await rD("Reset sandbox training status? You will need to complete training again.",D);if($!=="confirmed"){console.log(E.gray($==="declined"?"Skipped":"Cancelled"));return}let B=C("Resetting sandbox status...").start();try{await jv(),z2("sandboxCompleted"),z2("sandboxDatasetId"),B.succeed("Sandbox status reset"),console.log(),console.log("Run sandbox training again with:"),console.log(E.cyan(" nemar sandbox"))}catch(J){if(B.fail("Failed to reset"),J instanceof u)console.log(E.red(` ${J.message}`));else console.log(E.red(` ${J instanceof Error?J.message:"Unknown error"}`))}});var Vy={name:"nemar-cli",version:"0.3.7-dev.113",description:"CLI for NEMAR (Neuroelectromagnetic Data Archive and Tools Resource) dataset management",type:"module",main:"dist/index.js",bin:{nemar:"dist/index.js"},scripts:{dev:"bun run src/index.ts",build:"bun build src/index.ts --outdir dist --target bun --minify && sed '1s|#!/usr/bin/env node|#!/usr/bin/env bun|' dist/index.js > dist/index.js.tmp && mv dist/index.js.tmp dist/index.js",test:"bun test",lint:"biome check src/","lint:fix":"biome check --fix src/",format:"biome format --write src/",typecheck:"tsc --noEmit",prepublishOnly:"bun run build","docs:generate":"bun run scripts/generate-docs.ts","docs:serve":"mkdocs serve","docs:build":"mkdocs build"},keywords:["nemar","bids","neuroimaging","eeg","emg","datalad","cli"],author:"NEMAR Team",license:"MIT",repository:{type:"git",url:"git+https://github.com/nemarDatasets/nemar-cli.git"},bugs:{url:"https://github.com/nemarDatasets/nemar-cli/issues"},homepage:"https://nemar-cli.pages.dev",engines:{bun:">=1.0.0"},files:["dist","README.md","LICENSE"],dependencies:{chalk:"^5.3.0",commander:"^12.1.0",conf:"^13.0.1",inquirer:"^9.2.15",ora:"^8.0.1",zod:"^3.23.8"},devDependencies:{"@biomejs/biome":"^1.9.4","@types/bcryptjs":"^3.0.0","@types/bun":"latest","@types/inquirer":"^9.0.7",bcryptjs:"^3.0.3",typescript:"^5.5.4"}};var Ay=Vy.version;var Z1=new D0;Z1.name("nemar").description(`CLI for NEMAR (Neuroelectromagnetic Data Archive and Tools Resource)
566
+ `).action(MGD);async function MGD(){if(console.log(),console.log(E.bold("NEMAR Sandbox Training")),console.log(E.gray("Verify your setup and learn the upload workflow")),console.log(),!PD()){console.log(E.red("Not authenticated")),console.log(E.gray("Run 'nemar auth login' first"));return}let D=sD();if(D.sandboxCompleted){console.log(E.green("Sandbox training already completed!")),console.log(E.gray(`Dataset ID: ${D.sandboxDatasetId}`)),console.log(),console.log("You can upload real datasets with:"),console.log(E.cyan(" nemar dataset upload ./your-dataset")),console.log(),console.log(E.gray("To re-run training, use: nemar sandbox reset"));return}console.log(E.bold("Step 1/6: Checking prerequisites..."));let F=C("Checking git-annex and SSH...").start(),$=await A$();if(!$.allPassed){F.fail("Prerequisites check failed"),console.log(),console.log(E.red("Missing requirements:"));for(let P of $.errors)console.log(E.yellow(` - ${P}`));if(!$.githubSSH.accessible)console.log(E.gray(" Run 'nemar auth setup-ssh' to configure SSH"));return}F.succeed("All prerequisites met");let B=C("Verifying GitHub CLI authentication...").start(),J=await L$(D.githubUsername);if(!J.authenticated){B.fail("GitHub CLI not authenticated"),console.log(E.red(` ${J.error}`)),console.log(),console.log("GitHub CLI is required for sandbox training. Install and authenticate:"),console.log(E.cyan(" brew install gh # or visit https://cli.github.com/")),console.log(E.cyan(" gh auth login"));return}if(D.githubUsername&&!J.matches)B.warn("GitHub CLI user mismatch"),console.log(E.yellow(` ${J.error}`)),console.log(),console.log("Your gh CLI is authenticated as a different GitHub account than your NEMAR account."),console.log("This may cause issues with repository access. To fix:"),console.log(E.cyan(` gh auth login # Login as ${D.githubUsername}`)),console.log(),console.log(E.yellow("WARNING: If upload fails with permission errors, this mismatch is the likely cause.")),console.log();else B.succeed(`GitHub CLI authenticated as ${J.username}`);console.log(),console.log(E.bold("Step 2/6: Generating test dataset..."));let Q=C("Creating minimal BIDS structure...").start(),Y;try{let P=Ky();Y=P.root;let VD=zy(P);Q.succeed(`Test dataset created (${C$(VD)})`),console.log(E.gray(` Location: ${Y}`))}catch(P){Q.fail("Failed to generate test dataset"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`));return}console.log(),console.log(E.bold("Step 3/6: Registering sandbox dataset..."));let X=C("Creating dataset on NEMAR...").start(),G,H,W,K,V,A;try{let P=await z$({name:"Sandbox Training Dataset",description:"Placeholder dataset for sandbox training",files:[{path:"sub-01/eeg/sub-01_task-rest_eeg.edf",size:512000,type:"data"},{path:"dataset_description.json",size:200,type:"metadata"},{path:"participants.tsv",size:50,type:"metadata"},{path:"README",size:500,type:"metadata"},{path:"sub-01/eeg/sub-01_task-rest_eeg.json",size:300,type:"metadata"}],sandbox:!0});G=P.dataset.dataset_id,H=P.dataset.ssh_url,W=P.dataset.github_url,K=P.s3_config,V=P.dataset.s3_prefix,A=P.upload_urls||{},X.succeed(`Sandbox dataset created: ${E.cyan(G)}`),console.log(E.gray(` GitHub: ${W}`)),await new Promise((VD)=>setTimeout(VD,1e4))}catch(P){if(X.fail("Failed to create sandbox dataset"),P instanceof u)console.log(E.red(` ${P.message}`));else console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`));A1(Y);return}let Z=C("Accepting GitHub repository invitation...").start(),U=W?.match(/github\.com\/([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/),L=U?U[1].replace(/\.git$/,""):null;if(!L){Z.fail("Invalid GitHub repository URL from backend"),console.log(E.red(` Received: ${W||"(empty)"}`)),console.log(E.red(" Expected format: https://github.com/owner/repo")),console.log(),console.log("This may indicate a backend issue. Please contact support."),A1(Y);return}let M=await M$(L);if(M.accepted)if(M.alreadyCollaborator)Z.succeed("Already a collaborator on this repository");else Z.succeed("GitHub invitation accepted");else Z.warn("Could not auto-accept invitation"),console.log(E.yellow(` ${M.error}`)),console.log(),console.log("You may need to accept the invitation manually:"),console.log(E.cyan(` https://github.com/${L}/invitations`)),console.log();console.log(),console.log(E.bold("Step 4/6: Initializing repository..."));let w=C("Setting up git-annex...").start(),I=D.username&&D.email?{name:D.username,email:D.email}:void 0;try{await Z$(Y,{author:I}),await U$(Y),await N$(Y,H),w.succeed("Repository initialized")}catch(P){w.fail("Failed to initialize repository"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`)),A1(Y);return}if(console.log(),console.log(E.bold("Step 5/6: Uploading to S3...")),Object.keys(A).length===0)console.log(E.yellow(" No data files to upload (metadata only)"));else{let P=C("Uploading test data...").start();try{let yD=0,g=Object.keys(A).length,ED=await R$(Y,A,{jobs:4,onProgress:(WD)=>{if(WD.status==="completed"||WD.status==="failed")yD++,P.text=`Uploading... ${yD}/${g} files`}});if(ED.failed.length>0){P.fail(`Upload failed for ${ED.failed.length} file(s)`);for(let WD of ED.failed)console.log(E.red(` Failed: ${WD}`));if(ED.error)console.log(E.red(` Error: ${ED.error}`));console.log(),console.log(E.yellow("Sandbox training aborted due to upload failures.")),console.log(E.gray("Please check your network connection and try again.")),A1(Y);return}P.succeed(`Uploaded ${ED.uploaded} file(s)`)}catch(yD){P.fail("Upload failed"),console.log(E.red(` ${yD instanceof Error?yD.message:"Unknown error"}`)),A1(Y);return}let VD=C("Registering file URLs...").start();try{let yD={};for(let ED of Object.keys(A))yD[ED]=`${K.public_url}/${V}/${ED}`;let g=await w$(Y,yD);if(!g.success){VD.fail(`URL registration failed for ${g.failed.length} file(s)`);for(let ED of g.failed)console.log(E.red(` Failed: ${ED}`));console.log(),console.log(E.yellow("Sandbox training aborted due to URL registration failures.")),console.log(E.gray("This may indicate a git-annex configuration issue.")),A1(Y);return}VD.succeed(`Registered ${g.registered} file URLs`)}catch(yD){VD.fail("Failed to register URLs"),console.log(E.red(` ${yD instanceof Error?yD.message:"Unknown error"}`)),A1(Y);return}}console.log(),console.log(E.bold("Step 6/6: Pushing to GitHub..."));let b=C("Saving and pushing...").start();try{await W5(Y,"Initial sandbox training upload",I),await E5(Y),b.succeed("Pushed to GitHub")}catch(P){b.fail("Failed to push to GitHub"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`)),A1(Y);return}let T=C("Finalizing...").start();try{await V$(G),await Ov(G),q0("sandboxCompleted",!0),q0("sandboxDatasetId",G),T.succeed("Sandbox training complete!")}catch(P){T.fail("Failed to finalize"),console.log(E.red(` ${P instanceof Error?P.message:"Unknown error"}`)),A1(Y);return}A1(Y),console.log(),console.log(E.green.bold("Congratulations! Sandbox training completed successfully.")),console.log(),console.log("Your setup is verified and you're ready to upload real datasets:"),console.log(E.cyan(" nemar dataset upload ./your-dataset")),console.log(),console.log(E.gray(`Sandbox dataset: ${G}`))}v$.command("status").description("Check sandbox training completion status").option("--refresh","Fetch latest status from server").action(async(D)=>{if(!PD()){console.log(E.red("Not authenticated")),console.log(E.gray("Run 'nemar auth login' first"));return}if(D.refresh){let F=C("Checking status...").start();try{let $=await Iv();if(q0("sandboxCompleted",$.sandbox_completed),$.sandbox_dataset_id)q0("sandboxDatasetId",$.sandbox_dataset_id);if(F.stop(),$.sandbox_completed){if(console.log(E.green("Sandbox training: Completed")),console.log(E.gray(` Dataset ID: ${$.sandbox_dataset_id}`)),$.sandbox_completed_at)console.log(E.gray(` Completed: ${$.sandbox_completed_at}`))}else console.log(E.yellow("Sandbox training: Not completed")),console.log(),console.log("Run sandbox training with:"),console.log(E.cyan(" nemar sandbox"))}catch($){if(F.fail("Failed to check status"),$ instanceof u)console.log(E.red(` ${$.message}`))}}else{let F=sD();if(F.sandboxCompleted)console.log(E.green("Sandbox training: Completed")),console.log(E.gray(` Dataset ID: ${F.sandboxDatasetId}`));else console.log(E.yellow("Sandbox training: Not completed")),console.log(),console.log("Run sandbox training with:"),console.log(E.cyan(" nemar sandbox"))}});v$.command("reset").description("Reset sandbox training status for re-training").option(_D,fD).option(tD,eD).action(async(D)=>{if(!PD()){console.log(E.red("Not authenticated")),console.log(E.gray("Run 'nemar auth login' first"));return}if(!sD().sandboxCompleted){console.log(E.yellow("Sandbox training not yet completed")),console.log(E.gray("Nothing to reset"));return}let $=await rD("Reset sandbox training status? You will need to complete training again.",D);if($!=="confirmed"){console.log(E.gray($==="declined"?"Skipped":"Cancelled"));return}let B=C("Resetting sandbox status...").start();try{await jv(),z2("sandboxCompleted"),z2("sandboxDatasetId"),B.succeed("Sandbox status reset"),console.log(),console.log("Run sandbox training again with:"),console.log(E.cyan(" nemar sandbox"))}catch(J){if(B.fail("Failed to reset"),J instanceof u)console.log(E.red(` ${J.message}`));else console.log(E.red(` ${J instanceof Error?J.message:"Unknown error"}`))}});var Vy={name:"nemar-cli",version:"0.3.7-dev.118",description:"CLI for NEMAR (Neuroelectromagnetic Data Archive and Tools Resource) dataset management",type:"module",main:"dist/index.js",bin:{nemar:"dist/index.js"},scripts:{dev:"bun run src/index.ts",build:"bun build src/index.ts --outdir dist --target bun --minify && sed '1s|#!/usr/bin/env node|#!/usr/bin/env bun|' dist/index.js > dist/index.js.tmp && mv dist/index.js.tmp dist/index.js",test:"bun test",lint:"biome check src/","lint:fix":"biome check --fix src/",format:"biome format --write src/",typecheck:"tsc --noEmit",prepublishOnly:"bun run build","docs:generate":"bun run scripts/generate-docs.ts","docs:serve":"mkdocs serve","docs:build":"mkdocs build"},keywords:["nemar","bids","neuroimaging","eeg","emg","datalad","cli"],author:"NEMAR Team",license:"MIT",repository:{type:"git",url:"git+https://github.com/nemarDatasets/nemar-cli.git"},bugs:{url:"https://github.com/nemarDatasets/nemar-cli/issues"},homepage:"https://nemar-cli.pages.dev",engines:{bun:">=1.0.0"},files:["dist","README.md","LICENSE"],dependencies:{chalk:"^5.3.0",commander:"^12.1.0",conf:"^13.0.1",inquirer:"^9.2.15",ora:"^8.0.1",zod:"^3.23.8"},devDependencies:{"@biomejs/biome":"^1.9.4","@types/bcryptjs":"^3.0.0","@types/bun":"latest","@types/inquirer":"^9.0.7",bcryptjs:"^3.0.3",typescript:"^5.5.4"}};var Ay=Vy.version;var Z1=new D0;Z1.name("nemar").description(`CLI for NEMAR (Neuroelectromagnetic Data Archive and Tools Resource)
567
567
 
568
568
  NEMAR is a curated repository for neurophysiology data in BIDS format.
569
569
  This CLI provides tools for uploading, downloading, and managing datasets.`).version(Ay,"-v, --version","Output the current version").option("--no-color","Disable colored output").option("--verbose","Enable verbose output").addHelpText("after",`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nemar-cli",
3
- "version": "0.3.7-dev.113",
3
+ "version": "0.3.7-dev.118",
4
4
  "description": "CLI for NEMAR (Neuroelectromagnetic Data Archive and Tools Resource) dataset management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",