nemar-cli 0.10.1-PR1333.6f5d7fe → 0.10.1-PR1333.9a5fdee

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.
Files changed (2) hide show
  1. package/dist/index.js +2 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -99,7 +99,7 @@ ${t}`,ADe,ODe,FDe=(e,t,r)=>{let n=r===""?"":`with ${r.trim()}() `,o=kDe.bind(nul
99
99
  `)}var Qw=!1;var{spawn:q9e}=globalThis.Bun;async function k(e,t={}){let r={...process.env,GIT_TERMINAL_PROMPT:"0",...t.env};for(let u of t.unsetEnv??[])delete r[u];let n=q9e({cmd:e,cwd:t.cwd,stdout:"pipe",stderr:"pipe",env:r}),o=!1,i;if(t.timeout)i=setTimeout(()=>{o=!0,n.kill()},t.timeout);if(Zw()){let u=t.cwd?` (cwd=${t.cwd})`:"";ef(l.dim(`$ ${e.join(" ")}${u}`))}let s=await new Response(n.stdout).text(),a=await new Response(n.stderr).text(),c=await n.exited;if(i)clearTimeout(i);if(Zw()){if(s.trim())ef(l.dim(s.trimEnd()));if(a.trim())ef(l.yellow(a.trimEnd()));ef(l.dim(`(exit ${c})`))}if(o)return{stdout:s,stderr:a||`Command timed out after ${Math.round(t.timeout/1000)}s`,exitCode:c??1,timedOut:!0};return{stdout:s,stderr:a,exitCode:c,timedOut:!1}}var ir=F(()=>{We()});function M9e(){if(process.platform==="darwin")return"macos";if(process.platform==="win32")return"windows";return"linux"}function EW(e,t=M9e()){return e.installInstructions[t]}function L9e(e){let t=e.match(/(\d+\.\d+(?:\.\d+)?)/);return t?t[1]:void 0}function U9e(){let e=Number(process.env.TEST_PROBE_TIMEOUT_MS);return Number.isFinite(e)&&e>0?e:B9e}async function SW(e){try{let{exitCode:t,stdout:r,timedOut:n}=await k(e.cmd,{timeout:U9e()});if(n)return{available:!1,timedOut:!0};if(t!==0)return{available:!1};return{available:!0,version:L9e(r)}}catch(t){let r=t instanceof Error?t.message:String(t);if(!r.includes("ENOENT")&&!r.includes("not found"))console.error(` Warning: ${e.name} check failed unexpectedly: ${r}`);return{available:!1}}}async function tf(){let e=Object.entries(DW);return Promise.all(e.map(async([t,r])=>{let{available:n,version:o,timedOut:i}=await SW(r);return{key:t,name:r.name,purpose:r.purpose,required:r.required,available:n,version:o,timedOut:i,installInstruction:EW(r)}}))}async function $W(){let t=(await tf()).filter((r)=>r.required&&!r.available);if(t.length===0)return[];console.warn(`
100
100
  Heads up: some tools NEMAR needs for uploading/validating aren't installed yet:`);for(let r of t)console.warn(` - ${r.name} (${r.purpose})`),console.warn(` Install: ${r.installInstruction}`);return console.warn("Run `nemar doctor` anytime to re-check. You can finish signup without them.\n"),t}async function sa(e){let r=j9e[e].map((s)=>DW[s]).filter(Boolean),n=await Promise.all(r.map(async(s)=>{let a=await SW(s);return{check:s,available:a.available,timedOut:a.timedOut}}));for(let s of n)if(s.timedOut&&s.check.required)console.warn(`Warning: ${s.check.name} did not respond to a version check in time; continuing anyway.`);let o=n.filter((s)=>!s.available&&!s.timedOut&&s.check.required).map((s)=>({tool:s.check.name,installInstruction:EW(s.check)}));if(o.length===0)return;let i=[`
101
101
  Missing required tools:`];for(let s of o)i.push(` ${s.tool} not installed`),i.push(` Install: ${s.installInstruction}`);throw Error(i.join(`
102
- `))}var DW,j9e,B9e=4000;var rf=F(()=>{ir();DW={git:{name:"git",purpose:"version control for dataset metadata",cmd:["git","--version"],required:!0,installInstructions:{macos:"brew install git (or install Xcode Command Line Tools)",linux:"sudo apt-get install git OR sudo dnf install git",windows:"winget install Git.Git (or https://git-scm.com/download/win)"}},"git-annex":{name:"git-annex",purpose:"large-file data transfer (upload/download)",cmd:["git-annex","version"],required:!0,installInstructions:{macos:"brew install git-annex",linux:"sudo apt-get install git-annex OR sudo dnf install git-annex",windows:"Download the installer from https://git-annex.branchable.com/install/Windows/"}},gh:{name:"GitHub CLI (gh)",purpose:"GitHub authentication + repository operations",cmd:["gh","--version"],required:!0,installInstructions:{macos:"brew install gh",linux:"See https://cli.github.com/manual/installation",windows:"winget install GitHub.cli (or: scoop install gh)"}},aws:{name:"AWS CLI",purpose:"S3 data upload/download",cmd:["aws","--version"],required:!0,installInstructions:{macos:"brew install awscli",linux:"pip install awscli OR sudo snap install aws-cli --classic",windows:"winget install Amazon.AWSCLI (or the MSI from https://aws.amazon.com/cli/)"}},deno:{name:"Deno",purpose:"BIDS validation",cmd:["deno","--version"],required:!0,installInstructions:{macos:"brew install deno (or: curl -fsSL https://deno.land/install.sh | sh)",linux:"curl -fsSL https://deno.land/install.sh | sh",windows:"winget install DenoLand.Deno (or: irm https://deno.land/install.ps1 | iex)"}}},j9e={upload:["git-annex","gh","aws","deno"],download:["gh"],clone:["git","gh"],push:["git-annex","gh"],publish:["gh","aws"],update:["git","gh"],release:["git","gh"]}});var RW;var CW=F(()=>{RW={name:"nemar-cli",version:"0.10.1-PR1333.6f5d7fe",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/","migrations:d1-check":"bun run scripts/d1-migration-check.ts","lint:fix":"biome check --fix src/",format:"biome format --write src/",typecheck:"tsc --noEmit && tsc --noEmit -p backend","generate-openapi":"bun run scripts/generate-openapi.ts",prepublishOnly:"bun run build",prepare:"husky",postinstall:"bun scripts/postinstall-check.mjs",deploy:"wrangler deploy",preview:"wrangler dev"},keywords:["nemar","bids","neuroimaging","eeg","emg","datalad","cli"],author:"NEMAR Team",license:"CC-BY-NC-ND-4.0",repository:{type:"git",url:"git+https://github.com/nemarOrg/nemar-cli.git"},bugs:{url:"https://github.com/nemarOrg/nemar-cli/issues"},homepage:"https://docs.nemar.org",engines:{bun:">=1.0.0"},files:["dist","scripts/postinstall-check.mjs","scripts/recover-datasets.json","scripts/withdrawn-datasets.json","README.md","LICENSE"],dependencies:{chalk:"^5.3.0",commander:"^12.1.0",conf:"^13.0.1",inquirer:"^9.2.15",ora:"^8.0.1",semver:"^7.8.5",zod:"^3.23.8"},"lint-staged":{"src/**/*.{ts,tsx}":["biome check --fix"],"backend/src/**/*.{ts,tsx}":["biome check --fix"]},devDependencies:{"@asteasolutions/zod-to-openapi":"^7.3.4","@biomejs/biome":"1.9.4","@types/bun":"latest","@types/inquirer":"^9.0.7","@types/semver":"^7.8.0",ajv:"^8.20.0","ajv-formats":"^3.0.1",bcryptjs:"^3.0.3",husky:"^9.1.7","lint-staged":"^16.3.2",typescript:"^5.5.4",wrangler:"^4.70.0",yaml:"^2.8.4"}}});var si;var nf=F(()=>{CW();si=RW.version});import{mkdirSync as G9e,readdirSync as z9e,unlinkSync as H9e,writeFileSync as W9e}from"fs";import{join as eD}from"path";function PW(){tl=!0}function aa(){return tl}function IW(e,t=process.env){return e.includes("--debug")||t.NEMAR_DEBUG==="1"}function sf(e){FW=e,br(`Step: ${e}`)}function TW(){OW=!0}function NW(){return OW}function br(e){if(tl)process.stderr.write(`[debug] ${e}
102
+ `))}var DW,j9e,B9e=4000;var rf=F(()=>{ir();DW={git:{name:"git",purpose:"version control for dataset metadata",cmd:["git","--version"],required:!0,installInstructions:{macos:"brew install git (or install Xcode Command Line Tools)",linux:"sudo apt-get install git OR sudo dnf install git",windows:"winget install Git.Git (or https://git-scm.com/download/win)"}},"git-annex":{name:"git-annex",purpose:"large-file data transfer (upload/download)",cmd:["git-annex","version"],required:!0,installInstructions:{macos:"brew install git-annex",linux:"sudo apt-get install git-annex OR sudo dnf install git-annex",windows:"Download the installer from https://git-annex.branchable.com/install/Windows/"}},gh:{name:"GitHub CLI (gh)",purpose:"GitHub authentication + repository operations",cmd:["gh","--version"],required:!0,installInstructions:{macos:"brew install gh",linux:"See https://cli.github.com/manual/installation",windows:"winget install GitHub.cli (or: scoop install gh)"}},aws:{name:"AWS CLI",purpose:"S3 data upload/download",cmd:["aws","--version"],required:!0,installInstructions:{macos:"brew install awscli",linux:"pip install awscli OR sudo snap install aws-cli --classic",windows:"winget install Amazon.AWSCLI (or the MSI from https://aws.amazon.com/cli/)"}},deno:{name:"Deno",purpose:"BIDS validation",cmd:["deno","--version"],required:!0,installInstructions:{macos:"brew install deno (or: curl -fsSL https://deno.land/install.sh | sh)",linux:"curl -fsSL https://deno.land/install.sh | sh",windows:"winget install DenoLand.Deno (or: irm https://deno.land/install.ps1 | iex)"}}},j9e={upload:["git-annex","gh","aws","deno"],download:["gh"],clone:["git","gh"],push:["git-annex","gh"],publish:["gh","aws"],update:["git","gh"],release:["git","gh"]}});var RW;var CW=F(()=>{RW={name:"nemar-cli",version:"0.10.1-PR1333.9a5fdee",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/","migrations:d1-check":"bun run scripts/d1-migration-check.ts","lint:fix":"biome check --fix src/",format:"biome format --write src/",typecheck:"tsc --noEmit && tsc --noEmit -p backend","generate-openapi":"bun run scripts/generate-openapi.ts",prepublishOnly:"bun run build",prepare:"husky",postinstall:"bun scripts/postinstall-check.mjs",deploy:"wrangler deploy",preview:"wrangler dev"},keywords:["nemar","bids","neuroimaging","eeg","emg","datalad","cli"],author:"NEMAR Team",license:"CC-BY-NC-ND-4.0",repository:{type:"git",url:"git+https://github.com/nemarOrg/nemar-cli.git"},bugs:{url:"https://github.com/nemarOrg/nemar-cli/issues"},homepage:"https://docs.nemar.org",engines:{bun:">=1.0.0"},files:["dist","scripts/postinstall-check.mjs","scripts/recover-datasets.json","scripts/withdrawn-datasets.json","README.md","LICENSE"],dependencies:{chalk:"^5.3.0",commander:"^12.1.0",conf:"^13.0.1",inquirer:"^9.2.15",ora:"^8.0.1",semver:"^7.8.5",zod:"^3.23.8"},"lint-staged":{"src/**/*.{ts,tsx}":["biome check --fix"],"backend/src/**/*.{ts,tsx}":["biome check --fix"]},devDependencies:{"@asteasolutions/zod-to-openapi":"^7.3.4","@biomejs/biome":"1.9.4","@types/bun":"latest","@types/inquirer":"^9.0.7","@types/semver":"^7.8.0",ajv:"^8.20.0","ajv-formats":"^3.0.1",bcryptjs:"^3.0.3",husky:"^9.1.7","lint-staged":"^16.3.2",typescript:"^5.5.4",wrangler:"^4.70.0",yaml:"^2.8.4"}}});var si;var nf=F(()=>{CW();si=RW.version});import{mkdirSync as G9e,readdirSync as z9e,unlinkSync as H9e,writeFileSync as W9e}from"fs";import{join as eD}from"path";function PW(){tl=!0}function aa(){return tl}function IW(e,t=process.env){return e.includes("--debug")||t.NEMAR_DEBUG==="1"}function sf(e){FW=e,br(`Step: ${e}`)}function TW(){OW=!0}function NW(){return OW}function br(e){if(tl)process.stderr.write(`[debug] ${e}
103
103
  `)}function X9e(e){let t={};for(let[r,n]of Object.entries(e))t[r]=J9e.has(r.toLowerCase())?"[REDACTED]":n;return t}function jW(e){let t=e.indexOf("@");if(t<=0)return"***";return`${e[0]}***${e.slice(t)}`}function tD(e,t,r=0){if(r>Z9e)return"[REDACTED: depth]";if(Array.isArray(t))return t.map((n)=>tD(e,n,r+1));if(t&&typeof t==="object"){let n={};for(let[o,i]of Object.entries(t))n[o]=tD(o,i,r+1);return n}if(typeof t==="string"){if(Q9e.test(e))return"[REDACTED]";return t.replace(qW,jW)}return t}function rD(e){return e.replace(eje,"Bearer [REDACTED]").replace(tje,"[REDACTED_AWS_KEY]").replace(rje,"$1[REDACTED]").replace(nje,"$1$2=[REDACTED]").replace(qW,jW)}function oje(e){return rD(e)}function kW(e,t=Y9e){if(e===void 0)return;let r=e;try{let n=JSON.parse(e);r=JSON.stringify(tD("",n))}catch{}if(r=rD(r),Buffer.byteLength(r,"utf8")>t)r=`${Buffer.from(r,"utf8").subarray(0,t).toString("utf8")}... [truncated]`;return r}function q0(e){if(!tl)return;let t=oje(e.url);try{let r={...e,url:t,requestHeaders:e.requestHeaders?X9e(e.requestHeaders):void 0,requestBody:kW(e.requestBody),responseBody:kW(e.responseBody)};N0.push(r),br(`${e.method} ${t} -> ${e.status??"ERROR"} (${e.durationMs}ms)`)}catch(r){let n=P(r);N0.push({method:e.method,url:t,status:e.status,durationMs:e.durationMs,note:`redaction failed: ${n}`}),br(`redaction failed for ${e.method} ${t}: ${n}`)}}async function ije(){let e=[];try{let t=process.versions.bun?`Bun ${process.versions.bun}`:`Node ${process.version}`;e.push(`CLI version: ${si}`),e.push(`OS/Arch: ${process.platform}/${process.arch}`),e.push(`Runtime: ${t}`)}catch(t){e.push(`(could not read CLI/OS/runtime info: ${P(t)})`)}e.push(""),e.push("Active account:");try{let t=Ze();if(t.apiKey){if(t.username)e.push(` Username: ${t.username}`);if(e.push(` API URL: ${t.apiUrl}`),t.role)e.push(` Role: ${t.role}`);e.push(` Sandbox completed: ${!!t.sandboxCompleted}`)}else e.push(" (not authenticated)")}catch(t){e.push(` (could not read account config: ${P(t)})`)}e.push(""),e.push("External tools:");try{let t=await tf();for(let r of t){let n=r.available?r.version??"installed":r.timedOut?"(timed out)":"not installed";e.push(` ${r.name}: ${n}`)}}catch(t){e.push(` (could not probe external tools: ${P(t)})`)}return e}async function nD(){return(await ije()).join(`
104
104
  `)}async function MW(){if(!tl||T0!==void 0)return;try{T0=await nD()}catch(e){let t=P(e);T0=`(could not build environment report: ${t})`,br(`environment snapshot failed: ${t}`)}}function LW(e){if(AW.has(e))return{flag:e};for(let t of AW){if(!e.startsWith(t)||e.length<=t.length)continue;if(t.length===2)return{flag:t,kind:"short-attached"};if(e[t.length]==="=")return{flag:t,kind:"long-eq"}}return null}function sje(e){let t=[];for(let r=0;r<e.length;r++){let n=e[r],o=LW(n);if(!o){t.push(n);continue}if(o.kind==="long-eq")t.push(`${o.flag}=[REDACTED]`);else if(o.kind==="short-attached")t.push(`${o.flag}[REDACTED]`);else if(t.push(n),r+1<e.length)t.push("[REDACTED]"),r++}return t}function aje(e){let t=[];for(let r=0;r<e.length;r++){let n=e[r],o=LW(n);if(!o){t.push(n);continue}if(!o.kind&&r+1<e.length)r++}return t}function cje(e){let t=[];for(let n of aje(e)){if(n.startsWith("-"))continue;if(t.push(n),t.length===2)break}return(t.length>0?t.join("-"):"nemar").replace(/[^a-zA-Z0-9_-]/g,"_")}function lje(e,t){let r=[];if(r.push("NEMAR CLI Debug Log"),r.push(`Generated: ${new Date().toISOString()}`),r.push(`Command: nemar ${rD(sje(e).join(" "))}`),r.push(""),r.push(T0??"(environment snapshot unavailable)"),r.push(""),r.push("HTTP requests:"),N0.length===0)r.push(" (none)");else N0.forEach((n,o)=>{if(r.push(` [${o+1}] ${n.method} ${n.url} -> ${n.status??"ERROR"} (${n.durationMs}ms)`),n.error)r.push(` Error: ${n.error}`);if(n.note)r.push(` Note: ${n.note}`);if(n.requestHeaders)r.push(` Request headers: ${JSON.stringify(n.requestHeaders)}`);if(n.requestBody)r.push(` Request body: ${n.requestBody}`);if(n.responseBody)r.push(` Response body: ${n.responseBody}`)});return r.push(""),r.push(`Failing step: ${FW??"(none recorded)"}`),r.push(`Exit code: ${t}`),`${r.join(`
105
105
  `)}
@@ -496,7 +496,7 @@ ${o} finding(s). Preview the remediation with: nemar admin doctor fix <check> --
496
496
  ${l.bold(e)}: ${l.green(`${s.fixed} fixed`)}, ${s.skipped} skipped, ${s.failed>0?l.red(`${s.failed} failed`):"0 failed"}${c}`)}if(s.failed>0)process.exit(1)});async function KUe(e,t,r){let n={fixed:0,skipped:0,failed:0,resolved:0},o=[];for(let[i,s]of e.entries()){let a;try{let c=await t(s,i);if(a=c.results,a.length===0)n.resolved++;else n.fixed+=c.fixed,n.skipped+=c.skipped,n.failed+=c.failed}catch(c){n.failed++,a=[{dataset_id:s,status:"failed",message:P(c)}]}o.push(...a),r?.onDataset?.(s,a)}return{totals:n,results:o}}Ng.command("kinds").description("Check that operational accounts carry their expected account kind (read-only)").action(async()=>{if(!Z())return;let e=Object.entries(J5),t=w(`Checking ${e.length} operational account kind(s)...`).start(),r=[],n=[];try{for(let[o,i]of e)try{let{user:s}=await iK(o);if(s.account_kind!==i)r.push(`${o}: expected '${i}', found '${s.account_kind??"unknown"}'`)}catch(s){if(s instanceof q&&s.statusCode===404)n.push(o);else throw s}if(t.stop(),r.length===0&&n.length===0){console.log(l.green(`All ${e.length} operational account(s) carry their expected kind.`));return}for(let o of r)console.log(` ${l.yellow("kind mismatch")} ${o}`);for(let o of n)console.log(` ${l.yellow("account absent")} ${o}`);console.log(l.yellow(`
497
497
  ${r.length+n.length} finding(s). Fix a mismatch with \`nemar admin kind <username> <kind>\`.`))}catch(o){t.fail("Doctor kinds check failed"),console.error(l.red(P(o))),process.exit(1)}});le.addCommand(Ng);var S2=new ie("email-preferences").description("Manage email notification preferences");S2.command("show").description("Show email notification preferences").option("--user <username>","(owner only) show another user's preferences").action(async(e)=>{if(!Pe())console.error(l.red("Not authenticated. Run: nemar auth login")),process.exit(1);let t=w("Fetching email preferences...").start();try{let r=await jK(e.user),n=r.username?`${r.username}'s`:"Your";t.succeed(`${n} email notification preferences:`),console.log();let o=[{key:"user_approval",label:"User approval notifications"},{key:"publication_request",label:"Publication request notifications"},{key:"announcements",label:"Announcement emails"}];for(let i of o){let a=r[i.key]?l.green("enabled"):l.dim("disabled");console.log(` ${i.label.padEnd(40)} ${a}`)}console.log(),console.log(l.dim(" Use 'nemar admin email-preferences update' to change settings."))}catch(r){t.fail("Failed to fetch preferences"),console.error(l.red(P(r)))}});S2.command("update").description("Update email notification preferences").option("--user-approval <bool>","Enable/disable user approval notifications").option("--publication-request <bool>","Enable/disable publication request notifications").option("--announcements <bool>","Enable/disable announcement emails").option("--all <bool>","Enable/disable all notifications").option("--user <username>","(owner only) update another user's preferences").action(async(e)=>{if(!Pe())console.error(l.red("Not authenticated. Run: nemar auth login")),process.exit(1);function t(o){if(o===void 0)return;let i=o.toLowerCase();if(i==="true"||i==="1"||i==="on"||i==="yes")return!0;if(i==="false"||i==="0"||i==="off"||i==="no")return!1;console.error(l.red(`Invalid boolean value: "${o}". Use true/false, on/off, yes/no.`)),process.exit(1)}let r={};if(e.all!==void 0){let o=t(e.all);r.user_approval=o,r.publication_request=o,r.announcements=o}else{let o=t(e.userApproval),i=t(e.publicationRequest),s=t(e.announcements);if(o===void 0&&i===void 0&&s===void 0)console.error(l.red("No preferences specified.")),console.log(" --user-approval <bool> User approval notifications"),console.log(" --publication-request <bool> Publication request notifications"),console.log(" --announcements <bool> Announcement emails"),console.log(" --all <bool> All notifications"),process.exit(1);if(o!==void 0)r.user_approval=o;if(i!==void 0)r.publication_request=i;if(s!==void 0)r.announcements=s}let n=w("Updating email preferences...").start();try{let o=await MK(r,e.user),i=o.username?`${o.username}'s`:"Your";n.succeed(`${i} email preferences updated:`),console.log(),console.log(` User approval: ${o.user_approval?l.green("enabled"):l.dim("disabled")}`),console.log(` Publication request: ${o.publication_request?l.green("enabled"):l.dim("disabled")}`),console.log(` Announcements: ${o.announcements?l.green("enabled"):l.dim("disabled")}`)}catch(o){n.fail("Failed to update preferences"),console.error(l.red(P(o)))}});le.addCommand(S2);var qg=new ie("notice").description("Manage system notices displayed to CLI users");qg.command("list").description("List all notices (including expired)").action(async()=>{if(!Pe())console.error(l.red("Not authenticated. Run: nemar auth login")),process.exit(1);let e=w("Fetching notices...").start();try{let{notices:t}=await fY();if(t.length===0){e.succeed("No notices found.");return}e.succeed(`${t.length} notice(s):`),console.log();for(let r of t){let o={critical:l.red.bold,warning:l.yellow,maintenance:l.magenta,announcement:l.green,tip:l.blue,info:l.blue}[r.level]||l.white,i=r.expires_at&&new Date(r.expires_at)<new Date?l.dim(" (expired)"):"";if(console.log(` ${l.dim(`#${r.id}`)} ${o(`[${r.level.toUpperCase()}]`)} ${l.dim(`scope:${r.scope}`)}${i}`),console.log(` ${r.message}`),console.log(` ${l.dim(`Created: ${r.created_at}`)}`),r.expires_at)console.log(` ${l.dim(`Expires: ${r.expires_at}`)}`);console.log()}}catch(t){ze(t,e,"Failed to fetch notices")}});qg.command("set").description("Create a new system notice").requiredOption("-m, --message <text>","Notice message text").option("-l, --level <level>","Notice level: tip, announcement, maintenance, warning, critical","tip").option("-s, --scope <scope>","Target scope: all, admins, members","all").option("-e, --expires <datetime>","Expiry datetime (ISO 8601)").action(async(e)=>{if(!Pe())console.error(l.red("Not authenticated. Run: nemar auth login")),process.exit(1);if(![...DD,"info"].includes(e.level))console.error(l.red(`Invalid level: ${e.level}. Use ${DD.join(", ")}.`)),process.exit(1);if(!["all","admins","members"].includes(e.scope))console.error(l.red(`Invalid scope: ${e.scope}. Use all, admins, or members.`)),process.exit(1);let t=w("Creating notice...").start();try{let r=await pY({message:e.message,level:e.level,scope:e.scope,expires_at:e.expires});if(t.succeed(`Notice created (ID: ${r.id})`),console.log(` Level: ${r.level}`),console.log(` Scope: ${r.scope}`),console.log(` Message: ${r.message}`),r.expires_at)console.log(` Expires: ${r.expires_at}`)}catch(r){ze(r,t,"Failed to create notice")}});qg.command("clear <id>").description("Delete a notice by ID").option($e,Ce).option(Ue,Ie).action(async(e,t)=>{if(!Pe())console.error(l.red("Not authenticated. Run: nemar auth login")),process.exit(1);let r=Number.parseInt(e,10);if(Number.isNaN(r))console.error(l.red("Invalid notice ID")),process.exit(1);if(!await Se(`Delete notice #${r}?`,t,!1))return;let o=w("Deleting notice...").start();try{await mY(r),o.succeed(`Notice #${r} deleted`)}catch(i){ze(i,o,"Failed to delete notice")}});le.addCommand(qg);le.command("notify").description("Send an email to a group or a single user").option("--to <group>","Recipient group: all, admins, members").option("--user <username>","Send to a single user by username").requiredOption("--subject <text>","Email subject line").option("--body <text>","Email body (markdown)").option("--body-file <path>","Read email body from file (markdown)").option("--dry-run","Preview recipients without sending").option($e,Ce).option(Ue,Ie).action(async(e)=>{if(!Pe())console.error(l.red("Not authenticated. Run: nemar auth login")),process.exit(1);if(e.to&&e.user)console.error(l.red("--to and --user are mutually exclusive. Provide exactly one.")),process.exit(1);if(!e.to&&!e.user)console.error(l.red("Provide either --to <group> or --user <username>.")),process.exit(1);if(e.to&&!["all","admins","members"].includes(e.to))console.error(l.red(`Invalid group: ${e.to}. Use all, admins, or members.`)),process.exit(1);let t;if(e.bodyFile){if(!Og(e.bodyFile))console.error(l.red(`File not found: ${e.bodyFile}`)),process.exit(1);t=qUe(e.bodyFile,"utf-8")}else if(e.body)t=e.body;else console.error(l.red("Email body required. Use --body or --body-file.")),process.exit(1);let r=e.user?`user:${e.user}`:e.to,n=e.user?{user:e.user,subject:e.subject,body:t}:{to:e.to,subject:e.subject,body:t};if(e.dryRun){let a=w("Checking recipients...").start();try{let c=await xD({...n,dry_run:!0});if("dry_run"in c){a.succeed(`Dry run: ${c.recipient_count} recipient(s) for "${c.recipient_group}"`),console.log();for(let u of c.recipients)console.log(` ${u}`)}}catch(c){ze(c,a,"Failed to check recipients")}return}console.log(l.bold("Email preview:")),console.log(` To: ${l.cyan(r)}`),console.log(` Subject: ${e.subject}`),console.log(` Body: ${t.length>100?`${t.substring(0,100)}...`:t}`),console.log();let o=e.user?`Send this email to ${e.user}?`:"Send this broadcast email?";if(!await Se(o,e,!1))return;let s=w(e.user?`Sending to ${e.user}...`:"Sending broadcast...").start();try{let a=await xD(n);if("broadcast_id"in a)if(a.failure_count>0){s.warn(`Email send: ${a.recipient_count} delivered, ${a.failure_count} failed`);for(let c of a.failed_recipients)console.log(l.red(` Failed: ${c}`))}else{let c=e.user?`Email sent to ${e.user}`:`Broadcast sent to ${a.recipient_count} recipient(s)`;s.succeed(`${c} (ID: ${a.broadcast_id})`)}}catch(a){ze(a,s,"Failed to send email")}});var YUe=1500;function JUe(e){switch(e.kind){case"ok":return l.green(`ok ${e.schema_version}`);case"stale":return l.yellow(`stale ${e.schema_version}`);case"missing":return l.red("missing");case"error":return l.red(`error ${e.status||"?"}: ${e.message}`)}}function XUe(e){return e.kind==="stale"||e.kind==="missing"}function QUe(e){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");return new RegExp(`^${r}$`)}var iZ=new ie("summary").description("summary.json coverage across published dataset versions");iZ.command("check").description("Report which (dataset_id, version) pairs have stale or missing summary.json").option("--fix","Dispatch generate-manifest for every stale/missing version").option("--id <id>","Limit to a single dataset_id (exact match)").option("--match <glob>","Limit to dataset_ids matching a glob pattern: `*` matches any run of chars, `?` matches one char. Examples: `on*`, `nm00010?`, `*99999`. Can be combined with --id (id wins if both passed).").option("--only-stale","Print only rows that are not ok (suppresses ok rows)").option("--json","Emit the full report as JSON instead of a table").option($e,Ce).option(Ue,Ie).action(async(e)=>{if(!Z())return;let t=w("Building coverage report...").start(),r;try{r=await LK()}catch(m){ze(m,t,"Failed to fetch coverage report");return}t.succeed(`Coverage: target=schema ${r.target_schema}, versions=${r.totals.versions}`);let n=r.versions,o=null;if(e.id){if(n=n.filter((m)=>m.dataset_id===e.id),o=`dataset_id=${e.id}`,n.length===0){console.log(l.yellow(`No rows for ${o}`));return}}else if(e.match){let m=QUe(e.match);if(n=n.filter((p)=>m.test(p.dataset_id)),o=`--match ${e.match}`,n.length===0){console.log(l.yellow(`No rows match ${o}`));return}console.log(l.dim(`Scoped to ${n.length} version(s) matching ${e.match}`))}if(e.json){console.log(JSON.stringify(o?{...r,versions:n,totals:GQ(n)}:r,null,2));return}let i=e.onlyStale?n.filter((m)=>m.state.kind!=="ok"):n;if(i.length===0)console.log(l.green("All versions at target schema \u2014 no drift."));else{let m=Math.max(...i.map((g)=>g.dataset_id.length),10),p=Math.max(...i.map((g)=>g.version.length),7);for(let g of i)console.log(` ${g.dataset_id.padEnd(m)} ${g.version.padEnd(p)} ${JUe(g.state)}`)}let s=o?GQ(n):r.totals;if(console.log(),console.log(` Total: ${s.versions} ${l.green(`ok=${s.ok}`)} ${l.yellow(`stale=${s.stale}`)} ${l.red(`missing=${s.missing}`)} ${l.red(`error=${s.error}`)}`),!e.fix){if(s.stale+s.missing>0)console.log(l.dim(`
498
498
  Run with --fix to dispatch generate-manifest for the ${s.stale+s.missing} drifted version(s).`));return}let a=n.filter((m)=>XUe(m.state));if(a.length===0){console.log(l.green(`
499
- Nothing to dispatch.`));return}let c=o?` (scoped to ${o})`:"",u=await Se(`Dispatch generate-manifest for ${a.length} version(s)${c}? Each dispatch queues a runner job on nemarDatasets/.github.`,e);if(u!=="confirmed"){console.log(l.dim(u==="declined"?"Skipped":"Cancelled"));return}let d=0,f=0;console.log();for(let[m,p]of a.entries()){let g=`${p.dataset_id}@${p.version}`,y=w(`[${m+1}/${a.length}] dispatching ${g}...`).start();try{await BK(p.dataset_id,p.version),y.succeed(`[${m+1}/${a.length}] dispatched ${g}`),d++}catch(x){y.fail(`[${m+1}/${a.length}] ${g}: ${P(x)}`),f++}if(m<a.length-1)await new Promise((x)=>setTimeout(x,YUe))}if(console.log(),console.log(f===0?l.green(`All ${d} dispatches succeeded.`):l.yellow(`${d} succeeded, ${f} failed.`)),console.log(l.dim("Workflow runs will appear on https://github.com/nemarDatasets/.github/actions. Re-run `nemar admin summary check` once they complete to verify drift cleared.")),f>0)process.exitCode=1});function GQ(e){return{versions:e.length,ok:e.filter((t)=>t.state.kind==="ok").length,stale:e.filter((t)=>t.state.kind==="stale").length,missing:e.filter((t)=>t.state.kind==="missing").length,error:e.filter((t)=>t.state.kind==="error").length}}le.addCommand(iZ);var sZ=new ie("zarr-catalog").description("Top-level Zarr discovery catalog (zarr-catalog.json)");sZ.command("publish").description("Rebuild and republish zarr-catalog.json to this environment's bucket").action(async()=>{if(!Z())return;let e=w("Publishing zarr-catalog.json...").start();try{let t=await UK();e.succeed(`Published zarr-catalog.json: ${t.count} dataset(s), ${t.bytes} bytes`)}catch(t){ze(t,e,"Failed to publish zarr catalog"),process.exit(1)}});le.addCommand(sZ);var aZ=new ie("backfill-names").description("Fill missing researcher names from users' public ORCID records (dry run by default)");aZ.option("--apply","Write the names (without this flag, only report what would change)").option("--limit <n>","Users per batch (server clamps to [1,100])","25").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=Number.parseInt(e.limit??"25",10)||25,r=e.apply===!0,n=w(r?"Backfilling names from ORCID...":"Checking ORCID for missing names...").start(),o;try{o=await WK({apply:r,limit:t}),n.stop()}catch(i){n.fail("Name backfill failed"),console.error(l.red(P(i))),process.exit(1);return}if(o.lookup_failed>0||(o.write_failed??0)>0||o.remaining===null)process.exitCode=1;if(e.json){console.log(JSON.stringify(o,null,2));return}if(console.log(),!o.apply)console.log(l.yellow("DRY RUN \u2014 nothing was written. Re-run with --apply."));console.log(l.cyan(`scanned=${o.scanned} ${o.apply?"filled":"would_fill"}=${o.apply?o.filled:o.would_fill} no_public_name=${o.no_public_name} lookup_failed=${o.lookup_failed} write_failed=${o.write_failed??0} remaining=${o.remaining??"unknown"}`));for(let i of o.results){let s=i.username??`id ${i.id} <${i.email}>`;if(i.outcome==="filled"||i.outcome==="would_fill"){let a=i.outcome==="filled"?l.green("filled "):l.cyan("would fill");console.log(` ${a} ${s}: ${i.given_name} ${i.family_name} (${i.orcid})`)}else if(i.outcome==="no_public_name")console.log(` ${l.yellow("no name ")} ${s}: ORCID ${i.orcid} does not publish a full name`);else if(i.outcome==="write_failed")console.log(` ${l.red("write ")} ${s}: ${i.error}; retry the batch`);else console.log(` ${l.red("error ")} ${s}: ${i.error}`)}if(o.warning)console.log(l.yellow(` Warning: ${o.warning}`));if(o.no_public_name>0)console.log(),console.log(l.dim("These accounts must make their name public on their ORCID record and sign in again; NEMAR cannot type a name in for an ORCID-linked account, because the record is re-read on every sign-in and would overwrite it. An account with no verified ORCID can set its name in Settings (ADR 0042)."))});le.addCommand(aZ);function ZUe(e){return typeof e==="object"&&e!==null&&Array.isArray(e.errors)}function eVe(e){return typeof e==="object"&&e!==null&&Array.isArray(e.errors)}var cZ=new ie("import-issue-triage").description("Close recovered import-failure issues and retire stale cause labels (dry run by default)");cZ.option("--apply","Perform the changes (without this flag, only report what would change)").option("--limit <n>","Issues per batch (server clamps to [1,30])","15").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=e.limit??"15",r=t.trim()===""?Number.NaN:Number(t);if(!Number.isInteger(r)||r<1){console.error(l.red(`Invalid --limit ${JSON.stringify(t)}: expected an integer >= 1.`)),process.exitCode=1;return}let n=e.apply===!0,o=w(n?"Triaging import-failure issues...":"Checking import-failure issues...").start(),i;try{i=await GK({apply:n,limit:r}),o.stop()}catch(s){o.fail("Import-failure issue triage failed"),console.error(l.red(P(s)));let a=s instanceof q?s.details:void 0,c=ZUe(a)?a.errors:[];if(e.json)console.log(JSON.stringify(s instanceof q?s.rawBody??{errors:c}:{errors:c},null,2));else for(let u of c)console.error(`${l.red("ERROR".padEnd(15))} #${u.issue} ${u.dataset_id??""} ${u.stage}: ${u.error}`);process.exitCode=1;return}if(i.errors.length>0)process.exitCode=1;if(e.json){console.log(JSON.stringify(i,null,2));return}if(console.log(),!i.applied)console.log(l.yellow("DRY RUN \u2014 nothing was written. Re-run with --apply."));for(let s of i.plan){let a=s.failed?"FAILED ":i.applied?"":"WOULD ",c=`#${s.issueNumber} ${s.datasetId??"(unknown)"}`;if(s.kind==="close"){let u=`${a}CLOSE`.padEnd(15);console.log(`${s.failed?l.red(u):l.green(u)} ${c} ${l.dim(s.reason)}`)}else if(s.kind==="relabel"){let u=`${a}RELABEL`.padEnd(15);console.log(`${s.failed?l.red(u):l.yellow(u)} ${c} ${l.dim(s.reason)}`)}else console.log(`${l.dim("KEEP".padEnd(15))} ${c} ${l.dim(s.reason)}`)}for(let s of i.rollups){let c=(s.outcome==="failed"?"FAILED RELEASE":s.outcome==="released"?"RELEASE":i.mode==="per-dataset"?"WOULD RELEASE":"ROLLUP").padEnd(15);console.log(`${s.outcome==="failed"?l.red(c):l.magenta(c)} #${s.number} ${l.dim(s.title)}`)}for(let s of i.errors)console.log(`${l.red("ERROR".padEnd(15))} #${s.issue} ${s.dataset_id??""} ${s.stage}: ${s.error}`);if(console.log(),console.log(l.cyan(`open=${i.openIssues} mode=${i.mode} examined=${i.examined} attempted=${i.attempted} ${i.applied?"closed":"would_close"}=${i.closed} ${i.applied?"relabelled":"would_relabel"}=${i.relabelled} kept=${i.kept} errors=${i.errors.length} remaining=${i.remaining}`)),i.remaining>0)console.log(l.dim(` ${i.remaining} candidate(s) outside this run's window; the window rotates daily.`));if(i.mode==="rollup")console.log(l.dim(" Rollup mode: new failures with a cause that already has a rollup join it instead of opening their own issue."));if(i.rollupsReleased>0)console.log(l.dim(` ${i.applied?"Released":"Would release"} ${i.rollupsReleased} rollup(s): the backlog has drained, so per-dataset filing resumes.`));if(i.audit_failed)console.log(l.yellow(` Changes were applied but the audit row failed to write: ${i.audit_failed}`));if(!i.applied&&(i.closed>0||i.relabelled>0||i.rollupsReleased>0))console.log(l.dim(" Re-run with --apply to perform these changes."))});le.addCommand(cZ);var lZ=new ie("import-coverage").description("Check whether the import pipeline is keeping up with OpenNeuro (dry run by default)"),Ag=20,zQ=5,tVe={created:"create",refreshed:"refresh",relabelled:"relabel",closed:"close"};lZ.option("--apply","File, update or close the tracking issue (production only)").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=e.apply===!0,r=w("Checking import coverage...").start(),n;try{n=await zK({apply:t}),r.stop()}catch(c){r.fail("Import coverage check failed"),console.error(l.red(P(c)));let u=c instanceof q?c.details:void 0,d=eVe(u)?u.errors:[];if(e.json)console.log(JSON.stringify(c instanceof q?c.rawBody??{errors:d}:{errors:d},null,2));else for(let f of d)console.error(`${l.red("ERROR".padEnd(12))} ${f.stage}: ${f.error}`);process.exitCode=c instanceof q&&c.statusCode===502?2:1;return}if(n.status==="unknown")process.exitCode=2;else if(n.status!=="healthy"||n.errors.length>0)process.exitCode=1;if(e.json){console.log(JSON.stringify(n,null,2));return}let o=n.backlog,i=o.neverAttempted.length+o.untracked.length;console.log();let s=`${n.status.toUpperCase()}${n.kind?` (${n.kind})`:""}`;console.log(n.status==="healthy"?l.green(s):l.red(l.bold(s))),console.log(n.reason),console.log(),console.log(l.cyan(`auto_import=${n.enabled?"enabled":l.red("DISABLED")} last_dispatch=${n.dispatchAgeHours===null?"never":n.dispatchAgeHours<0?"dated in the future":`${n.dispatchAgeHours}h ago`}${n.lastDispatchSourceId?` (${n.lastDispatchSourceId})`:""} dispatch_lost=${n.dispatchLost}`));let a=n.importedInScan+n.inFlight+n.terminal+i+o.tracked.length+o.blocklisted.length;if(console.log(l.cyan(`discovered=${n.discovered} in_scan=${n.importedInScan} in_flight=${n.inFlight} terminal=${n.terminal} tracked=${o.tracked.length} blocklisted=${o.blocklisted.length}`)),console.log(l.cyan(`outstanding=${i} (never_attempted=${o.neverAttempted.length} untracked=${o.untracked.length})`)),n.importedNotInScan>0)console.log(l.dim(` ${n.importedNotInScan} of ${n.imported} mirror(s) are no longer in the scan (upstream removal, unreadable snapshot, or a modality retag). Drift, not a gap.`));if(a!==n.discovered)console.log(l.yellow(` Counts do not balance (${a} accounted for vs ${n.discovered} discovered): this run did not get a complete view, so treat the verdict as unreliable.`));if(o.neverAttempted.length>0){let c=o.neverAttempted.slice(0,Ag).join(", "),u=o.neverAttempted.length-Ag;console.log(l.dim(` never attempted: ${c}${u>0?` ... and ${u} more`:""}`))}if(o.untracked.length>0){let c=o.untracked.slice(0,Ag).join(", "),u=o.untracked.length-Ag;console.log(l.dim(` stale row nothing owns: ${c}${u>0?` ... and ${u} more`:""}`))}for(let c of n.errors){let u=`${c.stage}: ${c.error}`;console.log(c.stage==="anomaly"?`${l.yellow("ANOMALY".padEnd(12))} ${u}`:`${l.red("ERROR".padEnd(12))} ${u}`)}if(n.issue){let c=n.applied?n.issue.action:`would ${tVe[n.issue.action]??n.issue.action}`,u=n.issue.number===null?"":` #${n.issue.number}`;if(console.log(l.dim(` Issue: ${c}${u} on nemarDatasets/.github`)),n.issue.labelError)console.log(l.yellow(` The body was updated but its labels were not: ${n.issue.labelError}`));if(n.issue.commentError)console.log(l.yellow(` The issue changed but its comment failed: ${n.issue.commentError}`))}if(n.audit_failed)console.log(l.yellow(` Changes applied but the audit row failed to write: ${n.audit_failed}`));if(!n.applied&&n.issue)console.log(l.dim(" Re-run with --apply to perform this change."))});le.addCommand(lZ);var uZ=new ie("import-weekly").description("Read this week's import summary (dry run by default; the cron posts it on Mondays)");function rVe(e){return e.coverageStatus==="alarm"||e.coverageStatus==="unknown"||e.autoImportEnabled===!1||e.dispatchLost===!0||e.issuesClosed===null||e.errors.length>0}function xs(e){return e==null?l.yellow("unknown"):String(e)}uZ.option("--apply","Actually file the issue (production only; the cron normally does this)").option("--json","Output raw JSON instead of the human summary").option("--body","Also print the issue body exactly as it would be posted").action(async(e)=>{if(!Z())return;let t=e.apply===!0,r=w("Building the weekly import summary...").start(),n;try{n=await HK({apply:t}),r.stop()}catch(s){r.fail("Weekly import summary failed"),console.error(l.red(P(s))),process.exitCode=1;return}let o=n.facts;if(o===null)process.exitCode=0;else if(o.errors.length>0)process.exitCode=2;else if(rVe(o))process.exitCode=1;if(e.json){console.log(JSON.stringify(n,null,2));return}if(o===null){console.log(),console.log(l.yellow(`Nothing computed: ${n.gateReason}`)),console.log(l.dim(" The report is built once a week; --apply files it."));return}console.log(),console.log(l.bold(`Import summary ${o.week}`)),console.log(l.dim(`${o.windowStart} to ${o.windowEnd} (UTC)`)),console.log(),console.log(l.cyan(`imported_this_week=${xs(o.importedThisWeek)} total=${xs(o.importedTotal)} in_scope=${xs(o.discovered)} not_in_scope=${xs(o.importedNotInScan)}`));let i=o.coverageStatus===null?l.yellow("unknown"):o.coverageStatus==="healthy"?l.green(o.coverageStatus):l.red(o.coverageStatus);if(console.log(l.cyan(`coverage=${i} outstanding=${xs(o.outstanding)}`)),console.log(l.cyan(`auto_import=${o.autoImportEnabled===null?l.yellow("unknown"):o.autoImportEnabled?"enabled":l.red("DISABLED")} last_dispatch=${o.dispatchPhrase??l.yellow("unknown")} dispatch_lost=${o.dispatchLost===null?l.yellow("unknown"):o.dispatchLost}`)),console.log(l.cyan(`open_failures=${xs(o.openFailureTotal)} parked=${o.parked===null?l.yellow("unknown"):o.parked.length} closed_this_week=${xs(o.issuesClosed)} relabelled=${xs(o.issuesRelabelled)}`)),o.failuresByCause!==null){let s=Object.entries(o.failuresByCause).filter(([,a])=>a>0);for(let[a,c]of s)console.log(l.dim(` ${a}: ${c}`))}if(o.parked!==null&&o.parked.length>0){for(let a of o.parked.slice(0,zQ))console.log(l.dim(` parked ${a.datasetId} (${a.reason??"unknown"}): ${a.parkedDays==null?"unknown":`${a.parkedDays} days`}`));let s=o.parked.length-zQ;if(s>0)console.log(l.dim(` ... and ${s} more parked`))}for(let s of o.errors)console.log(`${l.yellow("UNKNOWN".padEnd(10))} ${s.stage}: ${s.error}`);if(console.log(),!n.posted){if(console.log(l.yellow(`Not posted: ${n.gateReason}`)),!t)console.log(l.dim(" Re-run with --apply to file it (production only)."))}else console.log(l.green(`Posted${n.issue?.number?` #${n.issue.number}`:""}${n.closedPrevious?`, closed #${n.closedPrevious}`:""}`));if(e.body)console.log(),console.log(l.dim("--- rendered issue body ---")),console.log(n.renderedBody??l.yellow("(no body was rendered)"))});le.addCommand(uZ);var dZ=new ie("backfill-usernames").description("Give username-less accounts a username from their name (dry run by default)");dZ.option("--apply","Write the usernames and send verification messages").option("--limit <n>","Users per batch (server clamps to [1,100])","25").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=Number.parseInt(e.limit??"25",10)||25,r=e.apply===!0,n=w(r?"Assigning usernames...":"Checking which accounts need a username...").start(),o;try{o=await KK({apply:r,limit:t}),n.stop()}catch(i){n.fail("Username backfill failed"),console.error(l.red(P(i))),process.exit(1);return}if(o.lookup_failed>0||o.conflict>0||(o.exhausted??0)>0||(o.write_failed??0)>0||(o.verify_failed??0)>0||(o.verify_rate_limited??0)>0||o.remaining===null)process.exitCode=1;if(e.json){console.log(JSON.stringify(o,null,2));return}if(console.log(),!o.apply)console.log(l.yellow("DRY RUN \u2014 nothing was written. Re-run with --apply."));console.log(l.cyan(`scanned=${o.scanned} ${o.apply?"assigned":"would_assign"}=${o.apply?o.assigned:o.would_assign} single_name=${o.single_name} no_name=${o.no_name} lookup_failed=${o.lookup_failed} conflict=${o.conflict} exhausted=${o.exhausted??0} write_failed=${o.write_failed??0} verify_sent=${o.verify_sent} verify_failed=${o.verify_failed??0} verify_rate_limited=${o.verify_rate_limited??0} remaining=${o.remaining??"unknown"}`));for(let i of o.results){let s=`id ${i.id} <${i.email}>`,a=[i.given_name,i.family_name].filter(Boolean).join(" ");if(i.outcome==="assigned"||i.outcome==="would_assign"){let c=i.outcome==="assigned"?l.green("assigned "):l.cyan("would give"),u=i.outcome==="assigned"?` [verify: ${i.verify}]`:"";console.log(` ${c} ${s}: ${l.cyan(i.username??"?")} (${a})${u}`)}else if(i.outcome==="single_name")console.log(` ${l.yellow("one name ")} ${s}: only "${a}" on record; pick a username by hand`);else if(i.outcome==="no_name")console.log(` ${l.yellow("no name ")} ${s}: run 'nemar admin backfill-names --apply' first`);else if(i.outcome==="conflict")console.log(` ${l.yellow("conflict ")} ${s}: ${i.error}`);else if(i.outcome==="exhausted")console.log(` ${l.yellow("exhausted ")} ${s}: ${i.error}; pick a username by hand`);else if(i.outcome==="write_failed")console.log(` ${l.red("write ")} ${s}: ${i.error}; retry the batch`);else console.log(` ${l.red("error ")} ${s}: ${i.error}`)}for(let i of o.verify_retries??[]){let s=i.verify==="sent"?l.green("re-sent "):i.verify==="not_attempted"?l.cyan("would send"):l.yellow(`verify ${i.verify}`);console.log(` ${s} id ${i.id} <${i.email}>`)}if(o.warning)console.log(l.yellow(` Warning: ${o.warning}`));if(o.single_name>0||o.no_name>0)console.log(),console.log(l.dim("Accounts with one name or none are listed, never guessed at: a username derived from an email address is a handle the person never chose."))});le.addCommand(dZ);var fZ=new ie("duplicates").description("Report live accounts sharing an ORCID iD, email, or GitHub handle");function nVe(e){let t=e.username??l.dim("(no username)"),r=[];if(e.canonical)r.push(l.green("canonical"));if(e.identity_conflict===1)r.push(l.yellow("flagged"));if(e.has_oauth_identity)r.push(l.dim("orcid-login"));let n=r.length>0?` [${r.join(", ")}]`:"",o=`created ${e.created_at} datasets=${e.dataset_count}`;console.log(` id ${String(e.id).padEnd(5)} ${t} <${e.email}> ${o}${n}`)}fZ.option("--json","Output raw JSON instead of the human report").option("--clear <id>","Clear one account's identity-conflict flag (refuses while the collision remains)").action(async(e)=>{if(!Z())return;if(e.clear!==void 0){let n=Number.parseInt(e.clear,10);if(!Number.isInteger(n)||n<=0){console.error(l.red(`Invalid user id: ${e.clear}`)),process.exit(1);return}let o=w(`Clearing identity-conflict flag on user ${n}...`).start();try{let i=await JK(n);if(o.stop(),e.json){console.log(JSON.stringify(i,null,2));return}console.log(i.cleared?l.green(`Cleared the identity-conflict flag on user ${n}.`):l.dim(`User ${n} was not flagged; nothing to clear.`))}catch(i){o.fail(i instanceof q&&i.statusCode===409?"Still colliding; flag not cleared":"Failed to clear the identity-conflict flag"),console.error(l.red(P(i))),process.exit(1)}return}let t=w("Checking for duplicate accounts...").start(),r;try{r=await YK(),t.stop()}catch(n){t.fail("Duplicate report failed"),console.error(l.red(P(n))),process.exit(1);return}if(e.json){console.log(JSON.stringify(r,null,2));return}if(console.log(),r.groups.length===0){if(console.log(l.green("No duplicate accounts.")),r.flagged_count>0)console.log(l.yellow(`${r.flagged_count} account(s) still carry an identity-conflict flag with no remaining collision.`)),console.log(l.dim(" Clear each with 'nemar admin duplicates --clear <id>'."));return}console.log(l.cyan(`${r.group_count} duplicate group(s); ${r.flagged_count} account(s) flagged`));for(let n of r.groups){console.log(),console.log(` ${l.bold(n.kind)} ${n.value}`);for(let o of n.accounts)nVe(o)}console.log(),console.log(l.dim("The fix is self-service on the surviving account: change its email or GitHub username, or unlink/re-link its ORCID iD, in Settings on nemar.org. Merging two accounts is manual.")),process.exitCode=1});le.addCommand(fZ);We();lo();Rn();$c();Rc();Ku();We();$c();Rc();var oVe=Dt["cli.gaps.section.title"];function pZ(e){if(e.gaps===void 0)return[e.unknownReason??Dt["cli.gaps.unknown"]];let t=Qm(e.gaps,{orcidVerified:e.orcidVerified}).map(Dq);if(e.sandboxCompleted===!1)t.push(Zm());return t.length>0?t:[Dt["gaps.none"]]}function iVe(e){let t=pZ(e);return t.length===1&&t[0]===Dt["gaps.none"]}function Pf(e){let t=e.gaps===void 0||iVe(e);console.log(),console.log(l.bold(oVe));for(let r of pZ(e))console.log(` ${t?l.dim(r):l.yellow(r)}`)}function jg(e,t,r={}){let n=r.indent??" ";console.log(),console.log(`${n}${e}`);for(let o of t)console.log(`${n} ${l.yellow(o.label)} ${vv(o)}`)}kc();us();Ct();async function $2(e){return A("/auth/login",{method:"POST",body:JSON.stringify({api_key:e})})}async function mZ(e){return A("/auth/resend-verification",{method:"POST",body:JSON.stringify({email:e})})}async function hZ(e,t){return A("/auth/retrieve-key",{method:"POST",body:JSON.stringify({email:e,password:t})})}async function gZ(e){return A("/auth/request-key-regeneration",{method:"POST",body:JSON.stringify({email:e})})}async function Hn(){return(await A("/users/me",{},!0,Z5)).user}async function yZ(e){return A("/sandbox/complete",{method:"POST",body:JSON.stringify({dataset_id:e})},!0)}async function xZ(){return A("/sandbox/reset",{method:"POST"},!0)}async function Mg(){return A("/sandbox/status",{},!0)}async function bZ(e){return A("/users/me/upload-access/request",{method:"POST",body:JSON.stringify({why:e})},!0)}async function _Z(e){return A("/auth/email/change/request",{method:"POST",body:JSON.stringify({email:e})},!0)}async function vZ(e,t){return A("/auth/email/change/verify",{method:"POST",body:JSON.stringify({email:e,code:t})},!0)}async function C2(e){return A("/auth/profile",{method:"PATCH",body:JSON.stringify(e)},!0)}async function wZ(e){return A("/auth/orcid/cli-start",{method:"POST",body:JSON.stringify({mode:e})},!0)}async function DZ(){return A("/auth/orcid/unlink",{method:"POST"},!0)}async function EZ(e){return A("/auth/device/start",{method:"POST",body:JSON.stringify({machine_name:e})},!1,gq)}function sVe(e){return typeof e==="string"&&Wm.options.includes(e)}function aVe(e){if(typeof e!=="object"||e===null)return;let t=e.message;return typeof t==="string"?t:void 0}async function SZ(e,t){try{return{status:"success",data:await A("/auth/device/token",{method:"POST",body:JSON.stringify({device_code:e}),signal:t},!1,xq)}}catch(r){if(r instanceof q&&r.statusCode===400){let n=yq.safeParse(r.rawBody);if(n.success){let o=n.data;if(o.error==="authorization_pending")return{status:"pending"};if(o.error==="slow_down")return{status:"slow_down"};return{status:"terminal",error:o.error,reason:o.reason,message:o.message}}if(sVe(r.code)&&r.code!=="authorization_pending"&&r.code!=="slow_down")return{status:"terminal",error:r.code,reason:void 0,message:aVe(r.rawBody)??mq[r.code]}}throw r}}async function $Z(){return A("/auth/keys",{},!0,Km)}async function CZ(e){return A("/auth/keys",{method:"POST",body:JSON.stringify({name:e})},!0,Ym)}async function Lg(e){return A(`/auth/keys/${e}`,{method:"DELETE"},!0)}async function RZ(e,t){return A(`/auth/keys/${e}`,{method:"DELETE",headers:{Authorization:`Bearer ${t}`}},!1)}async function kZ(){return A("/auth/profile/username-suggestion",{},!0,tq)}Ct();ai();import{spawn as cVe}from"child_process";function lVe(e,t){if(e==="darwin")return{command:"open",args:[t]};if(e==="win32")return{command:"cmd",args:["/c","start","",t]};if(e==="linux")return{command:"xdg-open",args:[t]};return null}function Bg(e){if(process.env.NEMAR_NO_BROWSER==="1")return!1;let t=lVe(process.platform,e);if(!t)return!1;try{let r=cVe(t.command,t.args,{stdio:"ignore",detached:!0});return r.on("error",(n)=>{br(`openInBrowser: ${t.command} failed: ${n instanceof Error?n.message:n}`)}),r.unref(),!0}catch(r){return br(`openInBrowser: could not spawn ${t.command}: ${r instanceof Error?r.message:r}`),!1}}yl();So();ai();We();Jm();import{hostname as uVe}from"os";Ct();ai();function dVe(e){let t=e.replace(/[\x00-\x1f\x7f]/g,"").replace(/\s+/g," ").trim();return t?t.slice(0,pq):xv}function fVe(){try{return dVe(uVe())}catch{return xv}}function pVe(e,t,r=yv){return e+(t+r)*1000}function FZ(e){switch(e.kind){case"cancelled":return{lines:["Sign-in canceled. Run `nemar auth login` to try again."],exitCode:130};case"denied":case"expired":case"invalid":return{lines:[e.message],exitCode:1};case"unreachable":return{lines:[`NEMAR could not be reached: ${e.detail}`,"Run `nemar auth login` again once you're back online."],exitCode:1};case"server_error":return{lines:[`NEMAR keeps answering with an error (${e.detail}). Try again in a few minutes; if it keeps happening, run \`nemar auth login --debug\` and report the log.`],exitCode:1};case"start_failed":return{lines:[`Could not start sign-in: ${e.detail}`],exitCode:1}}}var mVe=15000,AZ=1000,hVe=5000,OZ=3;function gVe(e){if(e instanceof q&&e.statusCode!==0)return`HTTP ${e.statusCode}`;return P(e)}function yVe(e){if(typeof AbortSignal.any==="function")return AbortSignal.any(e);let t=new AbortController;for(let r of e){if(r.aborted){t.abort();break}r.addEventListener("abort",()=>t.abort(),{once:!0})}return t.signal}function xVe(e,t){return new Promise((r)=>{if(t.aborted){r();return}let n=setTimeout(()=>{t.removeEventListener("abort",o),r()},e);function o(){clearTimeout(n),r()}t.addEventListener("abort",o,{once:!0})})}async function bVe(e,t={}){let r=Date.now(),n=pVe(r,e.expires_in,t.graceSeconds??yv),o=Math.max(e.interval*1000,AZ),i=new AbortController,s=()=>i.abort();process.on("SIGINT",s);let a=0;try{while(!0){if(i.signal.aborted)return{kind:"cancelled"};if(Date.now()>=n)return{kind:"unreachable",detail:"gave up waiting to reach NEMAR"};if(await xVe(o,i.signal),i.signal.aborted)return{kind:"cancelled"};let c=yVe([i.signal,AbortSignal.timeout(mVe)]),u;try{u=await SZ(e.device_code,c),a=0}catch(d){if(i.signal.aborted)return{kind:"cancelled"};if(d instanceof q&&d.statusCode===0){console.log(l.dim(" NEMAR is unreachable; retrying...")),a=0;continue}let f=gVe(d);if(a+=1,br(`poll error ${a}/${OZ}: ${f} -- ${P(d)}`),console.log(l.dim(` NEMAR answered with an error (${f}); retrying...`)),a>=OZ)return{kind:"server_error",detail:f};continue}if(u.status==="pending")continue;if(u.status==="slow_down"){o=Math.max(o+hVe,e.interval*1000,AZ);continue}if(u.status==="success")return{...u.data,kind:"success"};if(u.error==="expired_token")return{kind:"expired",message:u.message};if(u.error==="access_denied")return{kind:"denied",message:u.message};return{kind:"invalid",message:u.message}}}finally{process.off("SIGINT",s)}}async function R2(e={}){let t;try{t=await EZ(fVe())}catch(r){return{kind:"start_failed",detail:P(r)}}if(console.log(` ${t.verification_uri_complete}`),console.log(` Code: ${l.cyan(t.user_code)}`),console.log(l.dim(" (if the page asks for it)")),console.log(),e.open!==!1&&Bg(t.verification_uri_complete))console.log(l.dim(" (trying to open your browser; use the link above if it doesn't)")),console.log();return bVe(t)}rf();import{appendFileSync as _Ve,existsSync as bs,mkdirSync as PZ,readFileSync as Vg,writeFileSync as vVe}from"fs";import{homedir as wVe}from"os";import{join as Ug}from"path";var{spawn:IZ}=globalThis.Bun;function fa(){let e=Ug(wVe(),".ssh");return{sshDir:e,privateKey:Ug(e,"nemar_ed25519"),publicKey:Ug(e,"nemar_ed25519.pub"),configFile:Ug(e,"config")}}function TZ(){let e=fa();return bs(e.privateKey)&&bs(e.publicKey)}async function NZ(e){let t=fa();if(!bs(t.sshDir))try{PZ(t.sshDir,{mode:448})}catch(r){return{success:!1,error:`Cannot create ~/.ssh directory: ${r.message}`}}if(bs(t.privateKey))try{return{success:!0,publicKey:Vg(t.publicKey,"utf-8").trim()}}catch(r){return{success:!1,error:`NEMAR SSH key exists but cannot read public key: ${r.message}`}}try{let r=IZ({cmd:["ssh-keygen","-t","ed25519","-f",t.privateKey,"-N","","-C",`nemar-cli-${e}`],stdout:"pipe",stderr:"pipe"}),n=await new Response(r.stderr).text();if(await r.exited!==0)return{success:!1,error:`ssh-keygen failed: ${n}`};return{success:!0,publicKey:Vg(t.publicKey,"utf-8").trim()}}catch(r){return{success:!1,error:`Failed to generate SSH key: ${r.message}`}}}function qZ(){let e=fa();if(!bs(e.publicKey))return null;try{return Vg(e.publicKey,"utf-8").trim()}catch{return null}}function DVe(){let e=fa();if(!bs(e.configFile))return!1;try{let t=Vg(e.configFile,"utf-8");return t.includes("IdentityFile ~/.ssh/nemar_ed25519")||t.includes(`IdentityFile ${e.privateKey}`)}catch{return!1}}function jZ(){let e=fa();if(!bs(e.sshDir))try{PZ(e.sshDir,{mode:448})}catch(r){return{success:!1,error:`Cannot create ~/.ssh directory: ${r.message}`}}if(DVe())return{success:!0};let t=`
499
+ Nothing to dispatch.`));return}let c=o?` (scoped to ${o})`:"",u=await Se(`Dispatch generate-manifest for ${a.length} version(s)${c}? Each dispatch queues a runner job on nemarDatasets/.github.`,e);if(u!=="confirmed"){console.log(l.dim(u==="declined"?"Skipped":"Cancelled"));return}let d=0,f=0;console.log();for(let[m,p]of a.entries()){let g=`${p.dataset_id}@${p.version}`,y=w(`[${m+1}/${a.length}] dispatching ${g}...`).start();try{await BK(p.dataset_id,p.version),y.succeed(`[${m+1}/${a.length}] dispatched ${g}`),d++}catch(x){y.fail(`[${m+1}/${a.length}] ${g}: ${P(x)}`),f++}if(m<a.length-1)await new Promise((x)=>setTimeout(x,YUe))}if(console.log(),console.log(f===0?l.green(`All ${d} dispatches succeeded.`):l.yellow(`${d} succeeded, ${f} failed.`)),console.log(l.dim("Workflow runs will appear on https://github.com/nemarDatasets/.github/actions. Re-run `nemar admin summary check` once they complete to verify drift cleared.")),f>0)process.exitCode=1});function GQ(e){return{versions:e.length,ok:e.filter((t)=>t.state.kind==="ok").length,stale:e.filter((t)=>t.state.kind==="stale").length,missing:e.filter((t)=>t.state.kind==="missing").length,error:e.filter((t)=>t.state.kind==="error").length}}le.addCommand(iZ);var sZ=new ie("zarr-catalog").description("Top-level Zarr discovery catalog (zarr-catalog.json)");sZ.command("publish").description("Rebuild and republish zarr-catalog.json to this environment's bucket").action(async()=>{if(!Z())return;let e=w("Publishing zarr-catalog.json...").start();try{let t=await UK();e.succeed(`Published zarr-catalog.json: ${t.count} dataset(s), ${t.bytes} bytes`)}catch(t){ze(t,e,"Failed to publish zarr catalog"),process.exit(1)}});le.addCommand(sZ);var aZ=new ie("backfill-names").description("Fill missing researcher names from users' public ORCID records (dry run by default)");aZ.option("--apply","Write the names (without this flag, only report what would change)").option("--limit <n>","Users per batch (server clamps to [1,100])","25").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=Number.parseInt(e.limit??"25",10)||25,r=e.apply===!0,n=w(r?"Backfilling names from ORCID...":"Checking ORCID for missing names...").start(),o;try{o=await WK({apply:r,limit:t}),n.stop()}catch(i){n.fail("Name backfill failed"),console.error(l.red(P(i))),process.exit(1);return}if(o.lookup_failed>0||(o.write_failed??0)>0||o.remaining===null)process.exitCode=1;if(e.json){console.log(JSON.stringify(o,null,2));return}if(console.log(),!o.apply)console.log(l.yellow("DRY RUN \u2014 nothing was written. Re-run with --apply."));console.log(l.cyan(`scanned=${o.scanned} ${o.apply?"filled":"would_fill"}=${o.apply?o.filled:o.would_fill} no_public_name=${o.no_public_name} lookup_failed=${o.lookup_failed} write_failed=${o.write_failed??0} remaining=${o.remaining??"unknown"}`));for(let i of o.results){let s=i.username??`id ${i.id} <${i.email}>`;if(i.outcome==="filled"||i.outcome==="would_fill"){let a=i.outcome==="filled"?l.green("filled "):l.cyan("would fill");console.log(` ${a} ${s}: ${i.given_name} ${i.family_name} (${i.orcid})`)}else if(i.outcome==="no_public_name")console.log(` ${l.yellow("no name ")} ${s}: ORCID ${i.orcid} does not publish a full name`);else if(i.outcome==="write_failed")console.log(` ${l.red("write ")} ${s}: ${i.error}; retry the batch`);else console.log(` ${l.red("error ")} ${s}: ${i.error}`)}if(o.warning)console.log(l.yellow(` Warning: ${o.warning}`));if(o.no_public_name>0)console.log(),console.log(l.dim("These accounts must make their name public on their ORCID record and sign in again; NEMAR cannot type a name in for an ORCID-linked account, because the record is re-read on every sign-in and would overwrite it. An account with no verified ORCID can set its name in Settings (ADR 0042)."))});le.addCommand(aZ);function ZUe(e){return typeof e==="object"&&e!==null&&Array.isArray(e.errors)}function eVe(e){return typeof e==="object"&&e!==null&&Array.isArray(e.errors)}var cZ=new ie("import-issue-triage").description("Close recovered import-failure issues and retire stale cause labels (dry run by default)");cZ.option("--apply","Perform the changes (without this flag, only report what would change)").option("--limit <n>","Issues per batch (server clamps to [1,30])","15").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=e.limit??"15",r=t.trim()===""?Number.NaN:Number(t);if(!Number.isInteger(r)||r<1){console.error(l.red(`Invalid --limit ${JSON.stringify(t)}: expected an integer >= 1.`)),process.exitCode=1;return}let n=e.apply===!0,o=w(n?"Triaging import-failure issues...":"Checking import-failure issues...").start(),i;try{i=await GK({apply:n,limit:r}),o.stop()}catch(s){o.fail("Import-failure issue triage failed"),console.error(l.red(P(s)));let a=s instanceof q?s.details:void 0,c=ZUe(a)?a.errors:[];if(e.json)console.log(JSON.stringify(s instanceof q?s.rawBody??{errors:c}:{errors:c},null,2));else for(let u of c)console.error(`${l.red("ERROR".padEnd(15))} #${u.issue} ${u.dataset_id??""} ${u.stage}: ${u.error}`);process.exitCode=1;return}if(i.errors.length>0)process.exitCode=1;if(e.json){console.log(JSON.stringify(i,null,2));return}if(console.log(),!i.applied)console.log(l.yellow("DRY RUN \u2014 nothing was written. Re-run with --apply."));for(let s of i.plan){let a=s.failed?"FAILED ":i.applied?"":"WOULD ",c=`#${s.issueNumber} ${s.datasetId??"(unknown)"}`;if(s.kind==="close"){let u=`${a}CLOSE`.padEnd(15);console.log(`${s.failed?l.red(u):l.green(u)} ${c} ${l.dim(s.reason)}`)}else if(s.kind==="relabel"){let u=`${a}RELABEL`.padEnd(15);console.log(`${s.failed?l.red(u):l.yellow(u)} ${c} ${l.dim(s.reason)}`)}else console.log(`${l.dim("KEEP".padEnd(15))} ${c} ${l.dim(s.reason)}`)}for(let s of i.rollups){let c=(s.outcome==="failed"?"FAILED RELEASE":s.outcome==="released"?"RELEASE":i.mode==="per-dataset"?"WOULD RELEASE":"ROLLUP").padEnd(15);console.log(`${s.outcome==="failed"?l.red(c):l.magenta(c)} #${s.number} ${l.dim(s.title)}`)}for(let s of i.errors)console.log(`${l.red("ERROR".padEnd(15))} #${s.issue} ${s.dataset_id??""} ${s.stage}: ${s.error}`);if(console.log(),console.log(l.cyan(`open=${i.openIssues} mode=${i.mode} examined=${i.examined} attempted=${i.attempted} ${i.applied?"closed":"would_close"}=${i.closed} ${i.applied?"relabelled":"would_relabel"}=${i.relabelled} kept=${i.kept} errors=${i.errors.length} remaining=${i.remaining}`)),i.remaining>0)console.log(l.dim(` ${i.remaining} candidate(s) outside this run's window; the window rotates daily.`));if(i.mode==="rollup")console.log(l.dim(" Rollup mode: new failures with a cause that already has a rollup join it instead of opening their own issue."));if(i.rollupsReleased>0)console.log(l.dim(` ${i.applied?"Released":"Would release"} ${i.rollupsReleased} rollup(s): the backlog has drained, so per-dataset filing resumes.`));if(i.audit_failed)console.log(l.yellow(` Changes were applied but the audit row failed to write: ${i.audit_failed}`));if(!i.applied&&(i.closed>0||i.relabelled>0||i.rollupsReleased>0))console.log(l.dim(" Re-run with --apply to perform these changes."))});le.addCommand(cZ);var lZ=new ie("import-coverage").description("Check whether the import pipeline is keeping up with OpenNeuro (dry run by default)"),Ag=20,zQ=5,tVe={created:"create",refreshed:"refresh",relabelled:"relabel",closed:"close"};lZ.option("--apply","File, update or close the tracking issue (production only)").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=e.apply===!0,r=w("Checking import coverage...").start(),n;try{n=await zK({apply:t}),r.stop()}catch(c){r.fail("Import coverage check failed"),console.error(l.red(P(c)));let u=c instanceof q?c.details:void 0,d=eVe(u)?u.errors:[];if(e.json)console.log(JSON.stringify(c instanceof q?c.rawBody??{errors:d}:{errors:d},null,2));else for(let f of d)console.error(`${l.red("ERROR".padEnd(12))} ${f.stage}: ${f.error}`);process.exitCode=c instanceof q&&c.statusCode===502?2:1;return}if(n.status==="unknown")process.exitCode=2;else if(n.status!=="healthy"||n.errors.length>0)process.exitCode=1;if(e.json){console.log(JSON.stringify(n,null,2));return}let o=n.backlog,i=o.neverAttempted.length+o.untracked.length;console.log();let s=`${n.status.toUpperCase()}${n.kind?` (${n.kind})`:""}`;console.log(n.status==="healthy"?l.green(s):l.red(l.bold(s))),console.log(n.reason),console.log(),console.log(l.cyan(`auto_import=${n.enabled?"enabled":l.red("DISABLED")} last_dispatch=${n.dispatchAgeHours===null?"never":n.dispatchAgeHours<0?"dated in the future":`${n.dispatchAgeHours}h ago`}${n.lastDispatchSourceId?` (${n.lastDispatchSourceId})`:""} dispatch_lost=${n.dispatchLost}`));let a=n.importedInScan+n.inFlight+n.terminal+i+o.tracked.length+o.blocklisted.length;if(console.log(l.cyan(`discovered=${n.discovered} in_scan=${n.importedInScan} in_flight=${n.inFlight} terminal=${n.terminal} tracked=${o.tracked.length} blocklisted=${o.blocklisted.length}`)),console.log(l.cyan(`outstanding=${i} (never_attempted=${o.neverAttempted.length} untracked=${o.untracked.length})`)),n.importedNotInScan>0)console.log(l.dim(` ${n.importedNotInScan} of ${n.imported} mirror(s) are no longer in the scan (upstream removal, unreadable snapshot, or a modality retag). Drift, not a gap.`));if(a!==n.discovered)console.log(l.yellow(` Counts do not balance (${a} accounted for vs ${n.discovered} discovered): this run did not get a complete view, so treat the verdict as unreliable.`));if(o.neverAttempted.length>0){let c=o.neverAttempted.slice(0,Ag).join(", "),u=o.neverAttempted.length-Ag;console.log(l.dim(` never attempted: ${c}${u>0?` ... and ${u} more`:""}`))}if(o.untracked.length>0){let c=o.untracked.slice(0,Ag).join(", "),u=o.untracked.length-Ag;console.log(l.dim(` stale row nothing owns: ${c}${u>0?` ... and ${u} more`:""}`))}for(let c of n.errors){let u=`${c.stage}: ${c.error}`;console.log(c.stage==="anomaly"?`${l.yellow("ANOMALY".padEnd(12))} ${u}`:`${l.red("ERROR".padEnd(12))} ${u}`)}if(n.issue){let c=n.applied?n.issue.action:`would ${tVe[n.issue.action]??n.issue.action}`,u=n.issue.number===null?"":` #${n.issue.number}`;if(console.log(l.dim(` Issue: ${c}${u} on nemarDatasets/.github`)),n.issue.labelError)console.log(l.yellow(` The body was updated but its labels were not: ${n.issue.labelError}`));if(n.issue.commentError)console.log(l.yellow(` The issue changed but its comment failed: ${n.issue.commentError}`))}if(n.audit_failed)console.log(l.yellow(` Changes applied but the audit row failed to write: ${n.audit_failed}`));if(!n.applied&&n.issue)console.log(l.dim(" Re-run with --apply to perform this change."))});le.addCommand(lZ);var uZ=new ie("import-weekly").description("Read this week's import summary (dry run by default; the cron posts it on Mondays)");function rVe(e){return e.coverageStatus==="alarm"||e.coverageStatus==="unknown"||e.autoImportEnabled===!1||e.dispatchLost===!0||e.issuesClosed===null||e.errors.length>0}function xs(e){return e==null?l.yellow("unknown"):String(e)}uZ.option("--apply","Actually file the issue (production only; the cron normally does this)").option("--json","Output raw JSON instead of the human summary").option("--body","Also print the issue body exactly as it would be posted").action(async(e)=>{if(!Z())return;let t=e.apply===!0,r=w("Building the weekly import summary...").start(),n;try{n=await HK({apply:t}),r.stop()}catch(s){r.fail("Weekly import summary failed"),console.error(l.red(P(s))),process.exitCode=1;return}let o=n.facts;if(o===null)process.exitCode=0;else if(o.errors.length>0)process.exitCode=2;else if(rVe(o))process.exitCode=1;if(e.json){console.log(JSON.stringify(n,null,2));return}if(o===null){console.log(),console.log(l.yellow(`Nothing computed: ${n.gateReason}`)),console.log(l.dim(" The report is built once a week; --apply files it."));return}console.log(),console.log(l.bold(`Import summary ${o.week}`)),console.log(l.dim(`${o.windowStart} to ${o.windowEnd} (UTC)`)),console.log(),console.log(l.cyan(`imported_this_week=${xs(o.importedThisWeek)} total=${xs(o.importedTotal)} in_scope=${xs(o.discovered)} not_in_scope=${xs(o.importedNotInScan)}`));let i=o.coverageStatus===null?l.yellow("unknown"):o.coverageStatus==="healthy"?l.green(o.coverageStatus):l.red(o.coverageStatus);if(console.log(l.cyan(`coverage=${i} outstanding=${xs(o.outstanding)}`)),console.log(l.cyan(`auto_import=${o.autoImportEnabled===null?l.yellow("unknown"):o.autoImportEnabled?"enabled":l.red("DISABLED")} last_dispatch=${o.dispatchPhrase??l.yellow("unknown")} dispatch_lost=${o.dispatchLost===null?l.yellow("unknown"):o.dispatchLost}`)),console.log(l.cyan(`open_failures=${xs(o.openFailureTotal)} parked=${o.parked===null?l.yellow("unknown"):o.parked.length} closed_this_week=${xs(o.issuesClosed)} relabelled=${xs(o.issuesRelabelled)}`)),o.failuresByCause!==null){let s=Object.entries(o.failuresByCause).filter(([,a])=>a>0);for(let[a,c]of s)console.log(l.dim(` ${a}: ${c}`))}if(o.parked!==null&&o.parked.length>0){for(let a of o.parked.slice(0,zQ))console.log(l.dim(` parked ${a.datasetId} (${a.reason??"unknown"}): ${a.parkedDays==null?"unknown":`${a.parkedDays} days`}`));let s=o.parked.length-zQ;if(s>0)console.log(l.dim(` ... and ${s} more parked`))}for(let s of o.errors)console.log(`${l.yellow("UNKNOWN".padEnd(10))} ${s.stage}: ${s.error}`);if(console.log(),!n.posted&&!t)console.log(l.dim("Dry run: nothing was filed.")),console.log(l.dim(" Re-run with --apply to file it (production only)."));else if(!n.posted)console.log(l.yellow(`Not posted: ${n.gateReason}`));else console.log(l.green(`Posted${n.issue?.number?` #${n.issue.number}`:""}${n.closedPrevious?`, closed #${n.closedPrevious}`:""}`));if(e.body)console.log(),console.log(l.dim("--- rendered issue body ---")),console.log(n.renderedBody??l.yellow("(no body was rendered)"))});le.addCommand(uZ);var dZ=new ie("backfill-usernames").description("Give username-less accounts a username from their name (dry run by default)");dZ.option("--apply","Write the usernames and send verification messages").option("--limit <n>","Users per batch (server clamps to [1,100])","25").option("--json","Output raw JSON instead of the human summary").action(async(e)=>{if(!Z())return;let t=Number.parseInt(e.limit??"25",10)||25,r=e.apply===!0,n=w(r?"Assigning usernames...":"Checking which accounts need a username...").start(),o;try{o=await KK({apply:r,limit:t}),n.stop()}catch(i){n.fail("Username backfill failed"),console.error(l.red(P(i))),process.exit(1);return}if(o.lookup_failed>0||o.conflict>0||(o.exhausted??0)>0||(o.write_failed??0)>0||(o.verify_failed??0)>0||(o.verify_rate_limited??0)>0||o.remaining===null)process.exitCode=1;if(e.json){console.log(JSON.stringify(o,null,2));return}if(console.log(),!o.apply)console.log(l.yellow("DRY RUN \u2014 nothing was written. Re-run with --apply."));console.log(l.cyan(`scanned=${o.scanned} ${o.apply?"assigned":"would_assign"}=${o.apply?o.assigned:o.would_assign} single_name=${o.single_name} no_name=${o.no_name} lookup_failed=${o.lookup_failed} conflict=${o.conflict} exhausted=${o.exhausted??0} write_failed=${o.write_failed??0} verify_sent=${o.verify_sent} verify_failed=${o.verify_failed??0} verify_rate_limited=${o.verify_rate_limited??0} remaining=${o.remaining??"unknown"}`));for(let i of o.results){let s=`id ${i.id} <${i.email}>`,a=[i.given_name,i.family_name].filter(Boolean).join(" ");if(i.outcome==="assigned"||i.outcome==="would_assign"){let c=i.outcome==="assigned"?l.green("assigned "):l.cyan("would give"),u=i.outcome==="assigned"?` [verify: ${i.verify}]`:"";console.log(` ${c} ${s}: ${l.cyan(i.username??"?")} (${a})${u}`)}else if(i.outcome==="single_name")console.log(` ${l.yellow("one name ")} ${s}: only "${a}" on record; pick a username by hand`);else if(i.outcome==="no_name")console.log(` ${l.yellow("no name ")} ${s}: run 'nemar admin backfill-names --apply' first`);else if(i.outcome==="conflict")console.log(` ${l.yellow("conflict ")} ${s}: ${i.error}`);else if(i.outcome==="exhausted")console.log(` ${l.yellow("exhausted ")} ${s}: ${i.error}; pick a username by hand`);else if(i.outcome==="write_failed")console.log(` ${l.red("write ")} ${s}: ${i.error}; retry the batch`);else console.log(` ${l.red("error ")} ${s}: ${i.error}`)}for(let i of o.verify_retries??[]){let s=i.verify==="sent"?l.green("re-sent "):i.verify==="not_attempted"?l.cyan("would send"):l.yellow(`verify ${i.verify}`);console.log(` ${s} id ${i.id} <${i.email}>`)}if(o.warning)console.log(l.yellow(` Warning: ${o.warning}`));if(o.single_name>0||o.no_name>0)console.log(),console.log(l.dim("Accounts with one name or none are listed, never guessed at: a username derived from an email address is a handle the person never chose."))});le.addCommand(dZ);var fZ=new ie("duplicates").description("Report live accounts sharing an ORCID iD, email, or GitHub handle");function nVe(e){let t=e.username??l.dim("(no username)"),r=[];if(e.canonical)r.push(l.green("canonical"));if(e.identity_conflict===1)r.push(l.yellow("flagged"));if(e.has_oauth_identity)r.push(l.dim("orcid-login"));let n=r.length>0?` [${r.join(", ")}]`:"",o=`created ${e.created_at} datasets=${e.dataset_count}`;console.log(` id ${String(e.id).padEnd(5)} ${t} <${e.email}> ${o}${n}`)}fZ.option("--json","Output raw JSON instead of the human report").option("--clear <id>","Clear one account's identity-conflict flag (refuses while the collision remains)").action(async(e)=>{if(!Z())return;if(e.clear!==void 0){let n=Number.parseInt(e.clear,10);if(!Number.isInteger(n)||n<=0){console.error(l.red(`Invalid user id: ${e.clear}`)),process.exit(1);return}let o=w(`Clearing identity-conflict flag on user ${n}...`).start();try{let i=await JK(n);if(o.stop(),e.json){console.log(JSON.stringify(i,null,2));return}console.log(i.cleared?l.green(`Cleared the identity-conflict flag on user ${n}.`):l.dim(`User ${n} was not flagged; nothing to clear.`))}catch(i){o.fail(i instanceof q&&i.statusCode===409?"Still colliding; flag not cleared":"Failed to clear the identity-conflict flag"),console.error(l.red(P(i))),process.exit(1)}return}let t=w("Checking for duplicate accounts...").start(),r;try{r=await YK(),t.stop()}catch(n){t.fail("Duplicate report failed"),console.error(l.red(P(n))),process.exit(1);return}if(e.json){console.log(JSON.stringify(r,null,2));return}if(console.log(),r.groups.length===0){if(console.log(l.green("No duplicate accounts.")),r.flagged_count>0)console.log(l.yellow(`${r.flagged_count} account(s) still carry an identity-conflict flag with no remaining collision.`)),console.log(l.dim(" Clear each with 'nemar admin duplicates --clear <id>'."));return}console.log(l.cyan(`${r.group_count} duplicate group(s); ${r.flagged_count} account(s) flagged`));for(let n of r.groups){console.log(),console.log(` ${l.bold(n.kind)} ${n.value}`);for(let o of n.accounts)nVe(o)}console.log(),console.log(l.dim("The fix is self-service on the surviving account: change its email or GitHub username, or unlink/re-link its ORCID iD, in Settings on nemar.org. Merging two accounts is manual.")),process.exitCode=1});le.addCommand(fZ);We();lo();Rn();$c();Rc();Ku();We();$c();Rc();var oVe=Dt["cli.gaps.section.title"];function pZ(e){if(e.gaps===void 0)return[e.unknownReason??Dt["cli.gaps.unknown"]];let t=Qm(e.gaps,{orcidVerified:e.orcidVerified}).map(Dq);if(e.sandboxCompleted===!1)t.push(Zm());return t.length>0?t:[Dt["gaps.none"]]}function iVe(e){let t=pZ(e);return t.length===1&&t[0]===Dt["gaps.none"]}function Pf(e){let t=e.gaps===void 0||iVe(e);console.log(),console.log(l.bold(oVe));for(let r of pZ(e))console.log(` ${t?l.dim(r):l.yellow(r)}`)}function jg(e,t,r={}){let n=r.indent??" ";console.log(),console.log(`${n}${e}`);for(let o of t)console.log(`${n} ${l.yellow(o.label)} ${vv(o)}`)}kc();us();Ct();async function $2(e){return A("/auth/login",{method:"POST",body:JSON.stringify({api_key:e})})}async function mZ(e){return A("/auth/resend-verification",{method:"POST",body:JSON.stringify({email:e})})}async function hZ(e,t){return A("/auth/retrieve-key",{method:"POST",body:JSON.stringify({email:e,password:t})})}async function gZ(e){return A("/auth/request-key-regeneration",{method:"POST",body:JSON.stringify({email:e})})}async function Hn(){return(await A("/users/me",{},!0,Z5)).user}async function yZ(e){return A("/sandbox/complete",{method:"POST",body:JSON.stringify({dataset_id:e})},!0)}async function xZ(){return A("/sandbox/reset",{method:"POST"},!0)}async function Mg(){return A("/sandbox/status",{},!0)}async function bZ(e){return A("/users/me/upload-access/request",{method:"POST",body:JSON.stringify({why:e})},!0)}async function _Z(e){return A("/auth/email/change/request",{method:"POST",body:JSON.stringify({email:e})},!0)}async function vZ(e,t){return A("/auth/email/change/verify",{method:"POST",body:JSON.stringify({email:e,code:t})},!0)}async function C2(e){return A("/auth/profile",{method:"PATCH",body:JSON.stringify(e)},!0)}async function wZ(e){return A("/auth/orcid/cli-start",{method:"POST",body:JSON.stringify({mode:e})},!0)}async function DZ(){return A("/auth/orcid/unlink",{method:"POST"},!0)}async function EZ(e){return A("/auth/device/start",{method:"POST",body:JSON.stringify({machine_name:e})},!1,gq)}function sVe(e){return typeof e==="string"&&Wm.options.includes(e)}function aVe(e){if(typeof e!=="object"||e===null)return;let t=e.message;return typeof t==="string"?t:void 0}async function SZ(e,t){try{return{status:"success",data:await A("/auth/device/token",{method:"POST",body:JSON.stringify({device_code:e}),signal:t},!1,xq)}}catch(r){if(r instanceof q&&r.statusCode===400){let n=yq.safeParse(r.rawBody);if(n.success){let o=n.data;if(o.error==="authorization_pending")return{status:"pending"};if(o.error==="slow_down")return{status:"slow_down"};return{status:"terminal",error:o.error,reason:o.reason,message:o.message}}if(sVe(r.code)&&r.code!=="authorization_pending"&&r.code!=="slow_down")return{status:"terminal",error:r.code,reason:void 0,message:aVe(r.rawBody)??mq[r.code]}}throw r}}async function $Z(){return A("/auth/keys",{},!0,Km)}async function CZ(e){return A("/auth/keys",{method:"POST",body:JSON.stringify({name:e})},!0,Ym)}async function Lg(e){return A(`/auth/keys/${e}`,{method:"DELETE"},!0)}async function RZ(e,t){return A(`/auth/keys/${e}`,{method:"DELETE",headers:{Authorization:`Bearer ${t}`}},!1)}async function kZ(){return A("/auth/profile/username-suggestion",{},!0,tq)}Ct();ai();import{spawn as cVe}from"child_process";function lVe(e,t){if(e==="darwin")return{command:"open",args:[t]};if(e==="win32")return{command:"cmd",args:["/c","start","",t]};if(e==="linux")return{command:"xdg-open",args:[t]};return null}function Bg(e){if(process.env.NEMAR_NO_BROWSER==="1")return!1;let t=lVe(process.platform,e);if(!t)return!1;try{let r=cVe(t.command,t.args,{stdio:"ignore",detached:!0});return r.on("error",(n)=>{br(`openInBrowser: ${t.command} failed: ${n instanceof Error?n.message:n}`)}),r.unref(),!0}catch(r){return br(`openInBrowser: could not spawn ${t.command}: ${r instanceof Error?r.message:r}`),!1}}yl();So();ai();We();Jm();import{hostname as uVe}from"os";Ct();ai();function dVe(e){let t=e.replace(/[\x00-\x1f\x7f]/g,"").replace(/\s+/g," ").trim();return t?t.slice(0,pq):xv}function fVe(){try{return dVe(uVe())}catch{return xv}}function pVe(e,t,r=yv){return e+(t+r)*1000}function FZ(e){switch(e.kind){case"cancelled":return{lines:["Sign-in canceled. Run `nemar auth login` to try again."],exitCode:130};case"denied":case"expired":case"invalid":return{lines:[e.message],exitCode:1};case"unreachable":return{lines:[`NEMAR could not be reached: ${e.detail}`,"Run `nemar auth login` again once you're back online."],exitCode:1};case"server_error":return{lines:[`NEMAR keeps answering with an error (${e.detail}). Try again in a few minutes; if it keeps happening, run \`nemar auth login --debug\` and report the log.`],exitCode:1};case"start_failed":return{lines:[`Could not start sign-in: ${e.detail}`],exitCode:1}}}var mVe=15000,AZ=1000,hVe=5000,OZ=3;function gVe(e){if(e instanceof q&&e.statusCode!==0)return`HTTP ${e.statusCode}`;return P(e)}function yVe(e){if(typeof AbortSignal.any==="function")return AbortSignal.any(e);let t=new AbortController;for(let r of e){if(r.aborted){t.abort();break}r.addEventListener("abort",()=>t.abort(),{once:!0})}return t.signal}function xVe(e,t){return new Promise((r)=>{if(t.aborted){r();return}let n=setTimeout(()=>{t.removeEventListener("abort",o),r()},e);function o(){clearTimeout(n),r()}t.addEventListener("abort",o,{once:!0})})}async function bVe(e,t={}){let r=Date.now(),n=pVe(r,e.expires_in,t.graceSeconds??yv),o=Math.max(e.interval*1000,AZ),i=new AbortController,s=()=>i.abort();process.on("SIGINT",s);let a=0;try{while(!0){if(i.signal.aborted)return{kind:"cancelled"};if(Date.now()>=n)return{kind:"unreachable",detail:"gave up waiting to reach NEMAR"};if(await xVe(o,i.signal),i.signal.aborted)return{kind:"cancelled"};let c=yVe([i.signal,AbortSignal.timeout(mVe)]),u;try{u=await SZ(e.device_code,c),a=0}catch(d){if(i.signal.aborted)return{kind:"cancelled"};if(d instanceof q&&d.statusCode===0){console.log(l.dim(" NEMAR is unreachable; retrying...")),a=0;continue}let f=gVe(d);if(a+=1,br(`poll error ${a}/${OZ}: ${f} -- ${P(d)}`),console.log(l.dim(` NEMAR answered with an error (${f}); retrying...`)),a>=OZ)return{kind:"server_error",detail:f};continue}if(u.status==="pending")continue;if(u.status==="slow_down"){o=Math.max(o+hVe,e.interval*1000,AZ);continue}if(u.status==="success")return{...u.data,kind:"success"};if(u.error==="expired_token")return{kind:"expired",message:u.message};if(u.error==="access_denied")return{kind:"denied",message:u.message};return{kind:"invalid",message:u.message}}}finally{process.off("SIGINT",s)}}async function R2(e={}){let t;try{t=await EZ(fVe())}catch(r){return{kind:"start_failed",detail:P(r)}}if(console.log(` ${t.verification_uri_complete}`),console.log(` Code: ${l.cyan(t.user_code)}`),console.log(l.dim(" (if the page asks for it)")),console.log(),e.open!==!1&&Bg(t.verification_uri_complete))console.log(l.dim(" (trying to open your browser; use the link above if it doesn't)")),console.log();return bVe(t)}rf();import{appendFileSync as _Ve,existsSync as bs,mkdirSync as PZ,readFileSync as Vg,writeFileSync as vVe}from"fs";import{homedir as wVe}from"os";import{join as Ug}from"path";var{spawn:IZ}=globalThis.Bun;function fa(){let e=Ug(wVe(),".ssh");return{sshDir:e,privateKey:Ug(e,"nemar_ed25519"),publicKey:Ug(e,"nemar_ed25519.pub"),configFile:Ug(e,"config")}}function TZ(){let e=fa();return bs(e.privateKey)&&bs(e.publicKey)}async function NZ(e){let t=fa();if(!bs(t.sshDir))try{PZ(t.sshDir,{mode:448})}catch(r){return{success:!1,error:`Cannot create ~/.ssh directory: ${r.message}`}}if(bs(t.privateKey))try{return{success:!0,publicKey:Vg(t.publicKey,"utf-8").trim()}}catch(r){return{success:!1,error:`NEMAR SSH key exists but cannot read public key: ${r.message}`}}try{let r=IZ({cmd:["ssh-keygen","-t","ed25519","-f",t.privateKey,"-N","","-C",`nemar-cli-${e}`],stdout:"pipe",stderr:"pipe"}),n=await new Response(r.stderr).text();if(await r.exited!==0)return{success:!1,error:`ssh-keygen failed: ${n}`};return{success:!0,publicKey:Vg(t.publicKey,"utf-8").trim()}}catch(r){return{success:!1,error:`Failed to generate SSH key: ${r.message}`}}}function qZ(){let e=fa();if(!bs(e.publicKey))return null;try{return Vg(e.publicKey,"utf-8").trim()}catch{return null}}function DVe(){let e=fa();if(!bs(e.configFile))return!1;try{let t=Vg(e.configFile,"utf-8");return t.includes("IdentityFile ~/.ssh/nemar_ed25519")||t.includes(`IdentityFile ${e.privateKey}`)}catch{return!1}}function jZ(){let e=fa();if(!bs(e.sshDir))try{PZ(e.sshDir,{mode:448})}catch(r){return{success:!1,error:`Cannot create ~/.ssh directory: ${r.message}`}}if(DVe())return{success:!0};let t=`
500
500
  # NEMAR CLI SSH key (auto-generated)
501
501
  Host github.com
502
502
  AddKeysToAgent yes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nemar-cli",
3
- "version": "0.10.1-PR1333.6f5d7fe",
3
+ "version": "0.10.1-PR1333.9a5fdee",
4
4
  "description": "CLI for NEMAR (Neuroelectromagnetic Data Archive and Tools Resource) dataset management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",