envhound 0.1.2 → 0.1.4

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
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/logo.svg" width="128" height="128" alt="envhound logo: a snarling fox head that has tracked down a shell prompt">
2
+ <img src="assets/logo.svg" width="128" height="128" alt="envhound logo: a hound with long ears, and a shell prompt">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">envhound</h1>
package/dist/envhound.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{spawnSync as Sr}from"node:child_process";import{existsSync as kr,readFileSync as Cn,realpathSync as Er}from"node:fs";import{dirname as Ar,resolve as H}from"node:path";import{fileURLToPath as Or}from"node:url";import{parseArgs as Dr}from"node:util";var Q={name:"envhound",version:"0.1.2",description:"Find which startup file sets each environment variable, and audit your PATH",keywords:["environment","env","path","bashrc","profile","dotfiles","shell"],license:"MIT",author:"Michel Salib",repository:{type:"git",url:"git+https://github.com/michelsalib/envhound.git"},homepage:"https://github.com/michelsalib/envhound#readme",bugs:"https://github.com/michelsalib/envhound/issues",type:"module",bin:{envhound:"dist/envhound.js"},files:["dist"],engines:{node:">=20"},scripts:{dev:"bun src/cli.ts",build:"bun build src/cli.ts --target=node --minify --outfile dist/envhound.js",test:"bun test",typecheck:"tsc",prepublishOnly:"bun run typecheck && bun run build && bun test"},devDependencies:{"@types/bun":"^1.4.2","@types/node":"^26.6.4",typescript:"^7.0.2"}};import{statSync as jn}from"node:fs";function N(e,n){return e.assignments.filter((t)=>t.name===n&&!t.local)}function Me(e,n){let t=N(e,n).at(-1);if(!t)return;let s=e.final[n];if(t.result===void 0)return s===void 0?"unset":"not-effective";if(s===void 0)return"not-exported";return t.result===s?"effective":"not-effective"}function G(e){return e.via?.kind==="function"?e.via.at:e.at}var Kn=new Set(["PWD","OLDPWD","SHLVL","_"]);function re(e,n){return Object.keys(n).filter((t)=>t!=="_"&&n[t]!==void 0).sort().map((t)=>{let s=N(e,t),r=n[t];return{name:t,value:r,kind:s.length?"startup":(t in e.final)?"shell":"inherited",last:s.at(-1),count:s.length,status:Me(e,t),differsFromFresh:!Kn.has(t)&&t in e.final&&e.final[t]!==r}})}function me(e,n,t){return{name:n,assignments:N(e,n),status:Me(e,n),fresh:e.final[n],current:t[n],pathSteps:n==="PATH"?Le(e):void 0}}var B=(e)=>e===void 0?[]:e.split(":"),he=(e)=>[...new Set(e)];function Le(e){let n=e.initial.PATH??"",t=[{value:n,added:he(B(n)),removed:[],reordered:!1}];for(let s of N(e,"PATH")){let r=s.result??"",i=new Set(B(n)),a=new Set(B(r)),l=he(B(r).filter((o)=>!i.has(o))),c=he(B(n).filter((o)=>!a.has(o))),f=!l.length&&!c.length&&[...i].join(":")!==[...a].join(":");t.push({assignment:s,value:r,added:l,removed:c,reordered:f}),n=r}return t}function Wn(e){try{return jn(e).isDirectory()}catch{return!1}}function z(e,n){let t=new Map;for(let r of Le(e)){for(let i of r.removed)t.delete(i);for(let i of r.added)t.set(i,r.assignment??"initial")}let s=new Map;return B(n).map((r,i)=>{let a=t.get(r),l=i+1,c=s.get(r);if(c===void 0)s.set(r,l);return{index:l,dir:r,exists:r!==""&&Wn(r),duplicateOf:c,source:a===void 0?"inherited":a==="initial"?"initial":"startup",addedBy:a==="initial"?void 0:a}})}var Fn={list:"every exported variable and where it is set",blame:"every startup line that assigns a variable",path:"PATH entries, who added them, missing and duplicates",dotenv:"check .env files against your shell",set:"set variables for future login shells (or a .env file)",unset:"remove variables envhound set",edit:"interactive editor for variables, PATH and .env files",completion:"print a shell completion script",upgrade:"update envhound to the latest release"},Bn={"--json":"machine-readable output","--show-secrets":"don't mask secret values","--home":"act as if HOME were DIR","--file":"set/unset: edit this .env file","--yes":"write without asking","--dry-run":"show the diff, write nothing","--append":"path add: put DIR at the end","--help":"show help","--version":"show the version"},se=["bash","zsh","fish"],_e=new Set(["--home","--file","-f"]),zn={add:"put a directory in PATH",remove:"remove a directory envhound added"};function He(e,n){let t=e.at(-1)??"",s=e.slice(0,-1);if(_e.has(s.at(-1)??""))return[];let r=s.filter((a,l)=>!a.startsWith("-")&&!_e.has(s[l-1]??"")),i=[];if(t.startsWith("-"))i=Object.entries(Bn);else if(r.length===0)i=Object.entries(Fn);else if(r[0]==="blame"&&r.length===1)i=Object.keys(n).sort().map((a)=>[a]);else if(r[0]==="unset")i=Object.keys(n).sort().map((a)=>[a]);else if(r[0]==="set"&&!t.includes("="))i=Object.keys(n).filter((a)=>a!=="PATH").sort().map((a)=>[`${a}=`]);else if(r[0]==="path"&&r.length===1)i=Object.entries(zn);else if(r.length===1&&r[0]==="completion")i=se.map((a)=>[a]);return i.filter(([a])=>a.startsWith(t)).map(([a,l])=>l?`${a} ${l}`:a)}function Ne(e){switch(e){case"bash":return`# envhound completion for bash. Add to ~/.bashrc: eval "$(envhound completion bash)"
2
+ import{spawnSync as Sr}from"node:child_process";import{existsSync as kr,readFileSync as Cn,realpathSync as Er}from"node:fs";import{dirname as Ar,resolve as H}from"node:path";import{fileURLToPath as Tr}from"node:url";import{parseArgs as Or}from"node:util";var Q={name:"envhound",version:"0.1.4",description:"Find which startup file sets each environment variable, and audit your PATH",keywords:["environment","env","path","bashrc","profile","dotfiles","shell"],license:"MIT",author:"Michel Salib",repository:{type:"git",url:"git+https://github.com/michelsalib/envhound.git"},homepage:"https://github.com/michelsalib/envhound#readme",bugs:"https://github.com/michelsalib/envhound/issues",type:"module",bin:{envhound:"dist/envhound.js"},files:["dist"],engines:{node:">=20"},scripts:{dev:"bun src/cli.ts",build:"bun build src/cli.ts --target=node --minify --outfile dist/envhound.js",test:"bun test",typecheck:"tsc",prepublishOnly:"bun run typecheck && bun run build && bun test"},devDependencies:{"@types/bun":"^1.4.2","@types/node":"^26.6.4",typescript:"^7.0.2"}};import{statSync as jn}from"node:fs";function N(e,n){return e.assignments.filter((t)=>t.name===n&&!t.local)}function Me(e,n){let t=N(e,n).at(-1);if(!t)return;let s=e.final[n];if(t.result===void 0)return s===void 0?"unset":"not-effective";if(s===void 0)return"not-exported";return t.result===s?"effective":"not-effective"}function G(e){return e.via?.kind==="function"?e.via.at:e.at}var Kn=new Set(["PWD","OLDPWD","SHLVL","_"]);function re(e,n){return Object.keys(n).filter((t)=>t!=="_"&&n[t]!==void 0).sort().map((t)=>{let s=N(e,t),r=n[t];return{name:t,value:r,kind:s.length?"startup":(t in e.final)?"shell":"inherited",last:s.at(-1),count:s.length,status:Me(e,t),differsFromFresh:!Kn.has(t)&&t in e.final&&e.final[t]!==r}})}function me(e,n,t){return{name:n,assignments:N(e,n),status:Me(e,n),fresh:e.final[n],current:t[n],pathSteps:n==="PATH"?Le(e):void 0}}var B=(e)=>e===void 0?[]:e.split(":"),he=(e)=>[...new Set(e)];function Le(e){let n=e.initial.PATH??"",t=[{value:n,added:he(B(n)),removed:[],reordered:!1}];for(let s of N(e,"PATH")){let r=s.result??"",i=new Set(B(n)),a=new Set(B(r)),l=he(B(r).filter((o)=>!i.has(o))),c=he(B(n).filter((o)=>!a.has(o))),f=!l.length&&!c.length&&[...i].join(":")!==[...a].join(":");t.push({assignment:s,value:r,added:l,removed:c,reordered:f}),n=r}return t}function Wn(e){try{return jn(e).isDirectory()}catch{return!1}}function z(e,n){let t=new Map;for(let r of Le(e)){for(let i of r.removed)t.delete(i);for(let i of r.added)t.set(i,r.assignment??"initial")}let s=new Map;return B(n).map((r,i)=>{let a=t.get(r),l=i+1,c=s.get(r);if(c===void 0)s.set(r,l);return{index:l,dir:r,exists:r!==""&&Wn(r),duplicateOf:c,source:a===void 0?"inherited":a==="initial"?"initial":"startup",addedBy:a==="initial"?void 0:a}})}var Fn={list:"every exported variable and where it is set",blame:"every startup line that assigns a variable",path:"PATH entries, who added them, missing and duplicates",dotenv:"check .env files against your shell",set:"set variables for future login shells (or a .env file)",unset:"remove variables envhound set",edit:"interactive editor for variables, PATH and .env files",completion:"print a shell completion script",upgrade:"update envhound to the latest release"},Bn={"--json":"machine-readable output","--show-secrets":"don't mask secret values","--home":"act as if HOME were DIR","--file":"set/unset: edit this .env file","--yes":"write without asking","--dry-run":"show the diff, write nothing","--append":"path add: put DIR at the end","--help":"show help","--version":"show the version"},se=["bash","zsh","fish"],_e=new Set(["--home","--file","-f"]),zn={add:"put a directory in PATH",remove:"remove a directory envhound added"};function He(e,n){let t=e.at(-1)??"",s=e.slice(0,-1);if(_e.has(s.at(-1)??""))return[];let r=s.filter((a,l)=>!a.startsWith("-")&&!_e.has(s[l-1]??"")),i=[];if(t.startsWith("-"))i=Object.entries(Bn);else if(r.length===0)i=Object.entries(Fn);else if(r[0]==="blame"&&r.length===1)i=Object.keys(n).sort().map((a)=>[a]);else if(r[0]==="unset")i=Object.keys(n).sort().map((a)=>[a]);else if(r[0]==="set"&&!t.includes("="))i=Object.keys(n).filter((a)=>a!=="PATH").sort().map((a)=>[`${a}=`]);else if(r[0]==="path"&&r.length===1)i=Object.entries(zn);else if(r.length===1&&r[0]==="completion")i=se.map((a)=>[a]);return i.filter(([a])=>a.startsWith(t)).map(([a,l])=>l?`${a} ${l}`:a)}function Ne(e){switch(e){case"bash":return`# envhound completion for bash. Add to ~/.bashrc: eval "$(envhound completion bash)"
3
3
  _envhound() {
4
4
  local cur=\${COMP_WORDS[COMP_CWORD]}
5
5
  case \${COMP_WORDS[COMP_CWORD-1]} in
@@ -39,35 +39,35 @@ complete -c envhound -l home -x -a '(__fish_complete_directories (commandline -c
39
39
  `)?`\r
40
40
  `:`
41
41
  `,t=e.endsWith(`
42
- `),s=e===""?[]:e.split(/\r?\n/);if(t)s.pop();let r=[];for(let i=0;i<s.length;i++){let a=s[i],l={line:i+1,endLine:i+1,raw:a},c=a.trim();if(!c){r.push({...l,kind:"blank"});continue}if(c.startsWith("#")){r.push({...l,kind:"comment"});continue}let f=Yn.exec(a);if(!f){r.push({...l,kind:"invalid",problem:"not a KEY=value line"});continue}let[,o,d,u,,h]=f;if(!Zn.test(u)){r.push({...l,kind:"invalid",key:u,problem:`invalid key '${u}'`});continue}let p=h.trimStart(),m=["'",'"',"`"].find((x)=>p.startsWith(x)),g,y="",w,A,R=i+1,P=a;if(m){let x=p.slice(1),T=Ie(x,0,m),F=i;while(T<0&&F+1<s.length)F++,x+=`
43
- `+s[F],T=Ie(x,0,m);if(T<0){r.push({...l,kind:"invalid",key:u,problem:`unterminated ${m} quote`});continue}let pe=x.slice(T+1);if(pe.trim().startsWith("#"))A=pe;else if(pe.trim())w="text after the closing quote is ignored";g=m==='"'?Gn(x.slice(0,T)):x.slice(0,T),y=m,P=s.slice(i,F+1).join(`
44
- `),R=F+1,i=F}else{let x=/(^|\s+)#.*$/.exec(p);if(x)A=x[0];g=p.slice(0,x?.index??p.length).trim()}r.push({line:l.line,endLine:R,raw:P,kind:"entry",key:u,value:g,exported:!!d,quote:y,trailing:A,indent:o,expands:y!=="'"&&Qn.test(g),problem:w})}return{lines:r,eol:n,trailingNewline:t}}function je(e){return e.lines.map((t)=>t.raw.replace(/\n/g,e.eol)).join(e.eol)+(e.trailingNewline?e.eol:"")}function ie(e){let n=[],t=new Map;for(let s of e.lines){if(s.problem)n.push({line:s.line,key:s.key,message:s.problem});if(s.kind!=="entry")continue;let r=t.get(s.key);if(r!==void 0)n.push({line:s.line,key:s.key,message:`duplicate of line ${r}; most loaders keep the last one`});else t.set(s.key,s.line);if(s.expands)n.push({line:s.line,key:s.key,message:"uses $VAR expansion, which not every loader supports"})}return n.sort((s,r)=>s.line-r.line)}function oe(e,n){let t=new Map;for(let s of e.lines)if(s.kind==="entry")t.set(s.key,s);return[...t.values()].sort((s,r)=>s.line-r.line).map((s)=>{let r=n[s.key],i=r===void 0?"new":r===s.value?"same":"conflict";return{key:s.key,line:s.line,value:s.value,status:i,current:r}})}function Ke(e,n,t){let s=e.lines.findLastIndex((f)=>f.kind==="entry"&&f.key===n),r=e.lines[s],i=r?.quote??"",a=`${r?.indent??""}${r?.exported?"export ":""}${n}=${Ce(t,i)}${r?.trailing??""}`,c={...V(a).lines[0],line:r?.line??0,endLine:r?.endLine??0};if(r)return{...e,lines:e.lines.map((f,o)=>o===s?c:f)};return{...e,lines:[...e.lines,c],trailingNewline:!0}}function We(e,n){return{...e,lines:e.lines.filter((t)=>!(t.kind==="entry"&&t.key===n))}}import{chmodSync as rt,copyFileSync as st,mkdirSync as Je,readFileSync as it,writeFileSync as ot}from"node:fs";import{basename as at,dirname as lt,join as ct}from"node:path";import{createInterface as dt}from"node:readline/promises";function Fe(e,n){let t=Array.from({length:e.length+1},()=>Array(n.length+1).fill(0));for(let a=e.length-1;a>=0;a--)for(let l=n.length-1;l>=0;l--)t[a][l]=e[a]===n[l]?t[a+1][l+1]+1:Math.max(t[a+1][l],t[a][l+1]);let s=[],r=0,i=0;while(r<e.length||i<n.length)if(r<e.length&&i<n.length&&e[r]===n[i])s.push({op:" ",text:e[r]}),r++,i++;else if(r<e.length&&(i===n.length||t[r+1][i]>=t[r][i+1]))s.push({op:"-",text:e[r++]});else s.push({op:"+",text:n[i++]});return s}function Be(e,n=2){let t=e.map((r,i)=>e.slice(Math.max(0,i-n),i+n+1).some((a)=>a.op!==" ")),s=[];if(e.forEach((r,i)=>{if(t[i])s.push(r);else if(s.at(-1)!==null)s.push(null)}),s[0]===null)s.shift();if(s.at(-1)===null)s.pop();return s}var Jn=/TOKEN|SECRET|PASSW(OR)?D|API_?KEY|PRIVATE_?KEY|CREDENTIAL|_KEY$|^KEY$/i,M=(e)=>Jn.test(e);function k(e,n,t){return n===void 0||t.showSecrets||!M(e)?n:"********"}function q(e){let n=(t)=>(s)=>e.color&&s?`\x1B[${t}m${s}\x1B[0m`:s;return{bold:n("1"),dim:n("2"),red:n("31"),green:n("32"),yellow:n("33"),cyan:n("36")}}function v(e,n){if(!n||n==="/")return e;let t=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return e.replace(new RegExp(`(^|[:=\\s])${t}(?=/|:|$|\\s)`,"g"),"$1~")}var be=(e)=>e.replace(/\n/g,"\\n").replace(/[\x00-\x1f\x7f]/g,(n)=>`\\x${n.charCodeAt(0).toString(16).padStart(2,"0")}`);function Xn(e,n){return n!==void 0&&n>0&&e.length>n?e.slice(0,Math.max(0,n-1))+"…":e}function ge(e,n){return e.length>n?"…"+e.slice(e.length-n+1):e}var ze=(e,n)=>`${v(e.file,n)}:${e.line}`;function O(e,n){return ze(G(e),n)+(e.via?.kind==="function"?` ${e.via.name}()`:"")}function C(e,n,t){return e.map((s)=>s.map((r,i)=>{let a=i===s.length-1,l=Xn(r,n[i]);return(t[i]??((c)=>c))(a?l:l.padEnd(n[i]??0))}).join(" ").trimEnd()).join(`
45
- `)}function Ue(e,n){let t=q(n),s=(o)=>{if(o.kind==="shell")return"(login/shell)";if(o.kind==="inherited")return"(inherited)";let d=[o.count>1?`×${o.count}`:"",o.status==="not-effective"?"not effective":""].filter(Boolean);return O(o.last,n.home)+(d.length?` (${d.join(", ")})`:"")},r=e.map((o)=>[o.name,s(o),v(be(k(o.name,o.value,n)),n.home)]),i=Math.min(28,Math.max(8,...r.map((o)=>o[0].length))),a=Math.min(44,Math.max(6,...r.map((o)=>o[1].length)));for(let o of r)o[1]=ge(o[1],a);let l=n.width===void 0?void 0:Math.max(10,n.width-i-a-4),c=C([["VARIABLE","SET BY","VALUE"]],[i,a,0],[t.bold,t.bold,t.bold]),f=e.map((o,d)=>{let u=o.kind!=="startup"?t.dim:o.status==="not-effective"?t.yellow:(p)=>p,h=o.differsFromFresh?t.cyan:(p)=>p;return C([r[d]],[i,a,l??0],[(p)=>p,u,h])});if(n.color&&e.some((o)=>o.differsFromFresh))f.push("",t.dim("values in ")+t.cyan("cyan")+t.dim(" differ from what a fresh login shell gets"));return[c,...f].join(`
46
- `)}function et(e,n){let t=[...e.added.map((s)=>`+${v(s,n)||"(empty)"}`),...e.removed.map((s)=>`-${v(s,n)||"(empty)"}`)];if(t.length)return t.join(" ");return e.reordered?"(reordered)":"(reassigned, nothing new)"}function qe(e,n){let t=q(n),s=(c)=>v(be(k(e.name,c,n)??"<unset>"),n.home);if(!e.assignments.length){if(e.current===void 0)return`${e.name} is not set anywhere.`;let c=e.fresh!==void 0?"set by login or bash itself":"inherited from whatever launched this shell";return`${e.name} is not set by any startup file (${c}).
47
- Current value: ${s(e.current)}`}let r;if(e.pathSteps)r=e.pathSteps.map((c)=>[c.assignment?O(c.assignment,n.home):"(initial)",et(c,n.home)]);else{let c=(f)=>f===""||/^\s|\s$/.test(f)?JSON.stringify(f):f;r=e.assignments.map((f)=>[O(f,n.home),f.op==="unset"?"unset":`${f.op} ${c(s(f.value))}`])}let i=Math.min(48,Math.max(...r.map((c)=>c[0].length))),a=[C(r,[i,n.width===void 0?0:Math.max(10,n.width-i-2)],[t.dim,(c)=>c])],l=e.assignments.flatMap((c)=>c.via?.kind==="function"?[`${c.via.name}() is defined in ${v(c.at.file,n.home)}`]:c.via?.kind==="source"?[`${v(c.at.file,n.home)} is sourced from ${ze(c.via.at,n.home)}`]:[]);if(l.length)a.push("",...nt(l).map((c)=>t.dim(c)));switch(a.push(""),e.status){case"effective":a.push(t.green("✓ a fresh login shell ends with this value"));break;case"unset":a.push(t.green("✓ a fresh login shell ends with this variable unset"));break;case"not-exported":a.push(t.yellow("! set as a shell variable but never exported: programs started from the shell don't see it"));break;case"not-effective":a.push(t.yellow(`! not effective: a fresh shell ends with ${e.name}=${s(e.fresh)}
42
+ `),s=e===""?[]:e.split(/\r?\n/);if(t)s.pop();let r=[];for(let i=0;i<s.length;i++){let a=s[i],l={line:i+1,endLine:i+1,raw:a},c=a.trim();if(!c){r.push({...l,kind:"blank"});continue}if(c.startsWith("#")){r.push({...l,kind:"comment"});continue}let f=Yn.exec(a);if(!f){r.push({...l,kind:"invalid",problem:"not a KEY=value line"});continue}let[,o,u,d,,h]=f;if(!Zn.test(d)){r.push({...l,kind:"invalid",key:d,problem:`invalid key '${d}'`});continue}let p=h.trimStart(),m=["'",'"',"`"].find((S)=>p.startsWith(S)),g,b="",x,w,R=i+1,P=a;if(m){let S=p.slice(1),D=Ie(S,0,m),F=i;while(D<0&&F+1<s.length)F++,S+=`
43
+ `+s[F],D=Ie(S,0,m);if(D<0){r.push({...l,kind:"invalid",key:d,problem:`unterminated ${m} quote`});continue}let pe=S.slice(D+1);if(pe.trim().startsWith("#"))w=pe;else if(pe.trim())x="text after the closing quote is ignored";g=m==='"'?Gn(S.slice(0,D)):S.slice(0,D),b=m,P=s.slice(i,F+1).join(`
44
+ `),R=F+1,i=F}else{let S=/(^|\s+)#.*$/.exec(p);if(S)w=S[0];g=p.slice(0,S?.index??p.length).trim()}r.push({line:l.line,endLine:R,raw:P,kind:"entry",key:d,value:g,exported:!!u,quote:b,trailing:w,indent:o,expands:b!=="'"&&Qn.test(g),problem:x})}return{lines:r,eol:n,trailingNewline:t}}function je(e){return e.lines.map((t)=>t.raw.replace(/\n/g,e.eol)).join(e.eol)+(e.trailingNewline?e.eol:"")}function ie(e){let n=[],t=new Map;for(let s of e.lines){if(s.problem)n.push({line:s.line,key:s.key,message:s.problem});if(s.kind!=="entry")continue;let r=t.get(s.key);if(r!==void 0)n.push({line:s.line,key:s.key,message:`duplicate of line ${r}; most loaders keep the last one`});else t.set(s.key,s.line);if(s.expands)n.push({line:s.line,key:s.key,message:"uses $VAR expansion, which not every loader supports"})}return n.sort((s,r)=>s.line-r.line)}function oe(e,n){let t=new Map;for(let s of e.lines)if(s.kind==="entry")t.set(s.key,s);return[...t.values()].sort((s,r)=>s.line-r.line).map((s)=>{let r=n[s.key],i=r===void 0?"new":r===s.value?"same":"conflict";return{key:s.key,line:s.line,value:s.value,status:i,current:r}})}function Ke(e,n,t){let s=e.lines.findLastIndex((f)=>f.kind==="entry"&&f.key===n),r=e.lines[s],i=r?.quote??"",a=`${r?.indent??""}${r?.exported?"export ":""}${n}=${Ce(t,i)}${r?.trailing??""}`,c={...V(a).lines[0],line:r?.line??0,endLine:r?.endLine??0};if(r)return{...e,lines:e.lines.map((f,o)=>o===s?c:f)};return{...e,lines:[...e.lines,c],trailingNewline:!0}}function We(e,n){return{...e,lines:e.lines.filter((t)=>!(t.kind==="entry"&&t.key===n))}}import{chmodSync as rt,copyFileSync as st,mkdirSync as Je,readFileSync as it,writeFileSync as ot}from"node:fs";import{basename as at,dirname as lt,join as ct}from"node:path";import{createInterface as ut}from"node:readline/promises";function Fe(e,n){let t=Array.from({length:e.length+1},()=>Array(n.length+1).fill(0));for(let a=e.length-1;a>=0;a--)for(let l=n.length-1;l>=0;l--)t[a][l]=e[a]===n[l]?t[a+1][l+1]+1:Math.max(t[a+1][l],t[a][l+1]);let s=[],r=0,i=0;while(r<e.length||i<n.length)if(r<e.length&&i<n.length&&e[r]===n[i])s.push({op:" ",text:e[r]}),r++,i++;else if(r<e.length&&(i===n.length||t[r+1][i]>=t[r][i+1]))s.push({op:"-",text:e[r++]});else s.push({op:"+",text:n[i++]});return s}function Be(e,n=2){let t=e.map((r,i)=>e.slice(Math.max(0,i-n),i+n+1).some((a)=>a.op!==" ")),s=[];if(e.forEach((r,i)=>{if(t[i])s.push(r);else if(s.at(-1)!==null)s.push(null)}),s[0]===null)s.shift();if(s.at(-1)===null)s.pop();return s}var Jn=/TOKEN|SECRET|PASSW(OR)?D|API_?KEY|PRIVATE_?KEY|CREDENTIAL|_KEY$|^KEY$/i,M=(e)=>Jn.test(e);function E(e,n,t){return n===void 0||t.showSecrets||!M(e)?n:"********"}function q(e){let n=(t)=>(s)=>e.color&&s?`\x1B[${t}m${s}\x1B[0m`:s;return{bold:n("1"),dim:n("2"),red:n("31"),green:n("32"),yellow:n("33"),cyan:n("36")}}function v(e,n){if(!n||n==="/")return e;let t=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return e.replace(new RegExp(`(^|[:=\\s])${t}(?=/|:|$|\\s)`,"g"),"$1~")}var be=(e)=>e.replace(/\n/g,"\\n").replace(/[\x00-\x1f\x7f]/g,(n)=>`\\x${n.charCodeAt(0).toString(16).padStart(2,"0")}`);function Xn(e,n){return n!==void 0&&n>0&&e.length>n?e.slice(0,Math.max(0,n-1))+"…":e}function ge(e,n){return e.length>n?"…"+e.slice(e.length-n+1):e}var ze=(e,n)=>`${v(e.file,n)}:${e.line}`;function T(e,n){return ze(G(e),n)+(e.via?.kind==="function"?` ${e.via.name}()`:"")}function C(e,n,t){return e.map((s)=>s.map((r,i)=>{let a=i===s.length-1,l=Xn(r,n[i]);return(t[i]??((c)=>c))(a?l:l.padEnd(n[i]??0))}).join(" ").trimEnd()).join(`
45
+ `)}function Ue(e,n){let t=q(n),s=(o)=>{if(o.kind==="shell")return"(login/shell)";if(o.kind==="inherited")return"(inherited)";let u=[o.count>1?`×${o.count}`:"",o.status==="not-effective"?"not effective":""].filter(Boolean);return T(o.last,n.home)+(u.length?` (${u.join(", ")})`:"")},r=e.map((o)=>[o.name,s(o),v(be(E(o.name,o.value,n)),n.home)]),i=Math.min(28,Math.max(8,...r.map((o)=>o[0].length))),a=Math.min(44,Math.max(6,...r.map((o)=>o[1].length)));for(let o of r)o[1]=ge(o[1],a);let l=n.width===void 0?void 0:Math.max(10,n.width-i-a-4),c=C([["VARIABLE","SET BY","VALUE"]],[i,a,0],[t.bold,t.bold,t.bold]),f=e.map((o,u)=>{let d=o.kind!=="startup"?t.dim:o.status==="not-effective"?t.yellow:(p)=>p,h=o.differsFromFresh?t.cyan:(p)=>p;return C([r[u]],[i,a,l??0],[(p)=>p,d,h])});if(n.color&&e.some((o)=>o.differsFromFresh))f.push("",t.dim("values in ")+t.cyan("cyan")+t.dim(" differ from what a fresh login shell gets"));return[c,...f].join(`
46
+ `)}function et(e,n){let t=[...e.added.map((s)=>`+${v(s,n)||"(empty)"}`),...e.removed.map((s)=>`-${v(s,n)||"(empty)"}`)];if(t.length)return t.join(" ");return e.reordered?"(reordered)":"(reassigned, nothing new)"}function qe(e,n){let t=q(n),s=(c)=>v(be(E(e.name,c,n)??"<unset>"),n.home);if(!e.assignments.length){if(e.current===void 0)return`${e.name} is not set anywhere.`;let c=e.fresh!==void 0?"set by login or bash itself":"inherited from whatever launched this shell";return`${e.name} is not set by any startup file (${c}).
47
+ Current value: ${s(e.current)}`}let r;if(e.pathSteps)r=e.pathSteps.map((c)=>[c.assignment?T(c.assignment,n.home):"(initial)",et(c,n.home)]);else{let c=(f)=>f===""||/^\s|\s$/.test(f)?JSON.stringify(f):f;r=e.assignments.map((f)=>[T(f,n.home),f.op==="unset"?"unset":`${f.op} ${c(s(f.value))}`])}let i=Math.min(48,Math.max(...r.map((c)=>c[0].length))),a=[C(r,[i,n.width===void 0?0:Math.max(10,n.width-i-2)],[t.dim,(c)=>c])],l=e.assignments.flatMap((c)=>c.via?.kind==="function"?[`${c.via.name}() is defined in ${v(c.at.file,n.home)}`]:c.via?.kind==="source"?[`${v(c.at.file,n.home)} is sourced from ${ze(c.via.at,n.home)}`]:[]);if(l.length)a.push("",...nt(l).map((c)=>t.dim(c)));switch(a.push(""),e.status){case"effective":a.push(t.green("✓ a fresh login shell ends with this value"));break;case"unset":a.push(t.green("✓ a fresh login shell ends with this variable unset"));break;case"not-exported":a.push(t.yellow("! set as a shell variable but never exported: programs started from the shell don't see it"));break;case"not-effective":a.push(t.yellow(`! not effective: a fresh shell ends with ${e.name}=${s(e.fresh)}
48
48
  so the last line above is a one-command prefix (${e.name}=x cmd), or something envhound can't trace undid it`));break}if(e.current===void 0&&e.fresh!==void 0)a.push(t.cyan("~ not set in this shell: it started before this was added, or something unset it"));else if(e.current!==e.fresh&&e.current!==void 0)a.push(t.cyan(`~ this shell has a different value: ${e.pathSteps?tt(e.fresh,e.current,n.home):s(e.current)}`));return a.join(`
49
49
  `)}var nt=(e)=>[...new Set(e)];function tt(e,n,t){let s=new Set((e??"").split(":")),r=new Set((n??"").split(":")),i=[...r].filter((f)=>!s.has(f)),a=[...s].filter((f)=>!r.has(f)),l=(f)=>f.length>3?`${f.slice(0,3).map((o)=>v(o,t)).join(", ")}, …`:f.map((o)=>v(o,t)).join(", ");return([i.length?`${i.length} extra (${l(i)})`:"",a.length?`${a.length} missing (${l(a)})`:""].filter(Boolean).join("; ")||"same entries, different order")+`
50
- run 'envhound path' to see each entry`}function Ye(e,n){let t=q(n),s=(o)=>o.source==="startup"?O(o.addedBy,n.home):o.source==="initial"?"(initial)":"(inherited)",r=(o)=>o.dir===""?"empty entry: searches the current directory":o.duplicateOf?`duplicate of #${o.duplicateOf}`:!o.exists?"missing":"",i=e.map((o)=>[String(o.index).padStart(2),v(o.dir,n.home)||"(empty)",s(o),r(o)]),a=Math.min(50,Math.max(9,...i.map((o)=>o[1].length))),l=Math.min(40,Math.max(8,...i.map((o)=>o[2].length)));for(let o of i)o[1]=ge(o[1],a),o[2]=ge(o[2],l);let c=C([[" #","DIRECTORY","ADDED BY",""]],[2,a,l,0],[t.bold,t.bold,t.bold]),f=i.map((o,d)=>C([o],[2,a,l,0],[t.dim,(u)=>u,e[d].source==="startup"?(u)=>u:t.dim,t.red]));return[c,...f].join(`
51
- `)}var ve=(e,n)=>({...e,value:k(e.name,e.value,n),previous:k(e.name,e.previous,n),result:k(e.name,e.result,n)}),Ze=(e,n)=>e.map((t)=>({...t,value:k(t.name,t.value,n),last:t.last&&ve(t.last,n)})),Qe=(e,n)=>({...e,fresh:k(e.name,e.fresh,n),current:k(e.name,e.current,n),assignments:e.assignments.map((t)=>ve(t,n)),pathSteps:e.pathSteps?.map((t)=>({...t,assignment:t.assignment&&ve(t.assignment,n)}))});function Ge(e,n){let t=q(n),s=(i,a)=>v(be(k(i,a,n)??""),n.home),r=[t.bold(`${v(e.file,n.home)}: ${e.keys.length} key${e.keys.length===1?"":"s"}`)];if(e.keys.length){let i=e.keys.map((o)=>{if(o.status==="new")return[o.key,String(o.line),"new",""];if(o.status==="same")return[o.key,String(o.line),"same as shell",""];let d=e.shellOrigin[o.key];return[o.key,String(o.line),"conflict",`shell has ${s(o.key,o.current)}${d?` (set at ${O(d,n.home)})`:""}`]}),a=Math.min(32,Math.max(3,...i.map((o)=>o[0].length))),l=Math.max(4,...i.map((o)=>o[1].length)),c=Math.max(6,...i.map((o)=>o[2].length)),f=(o)=>o.startsWith("conflict")?t.yellow(o):o.startsWith("same")?t.dim(o):t.green(o);r.push(C([["KEY","LINE","STATUS",""]],[a,l,c,0],[t.bold,t.bold,t.bold]));for(let o of i)r.push(C([o],[a,l,c,0],[(d)=>d,t.dim,f,t.dim]));if(i.some((o)=>o[2]==="conflict"))r.push("",t.dim("conflict: loaders such as dotenv keep the shell's value unless told to override; check yours"))}if(e.problems.length){r.push("",t.bold("Problems"));for(let i of e.problems)r.push(`${t.red(`line ${i.line}`)}${i.key?` ${i.key}`:""}: ${i.message}`)}return r.join(`
52
- `)}function L(e){try{return it(e,"utf8")}catch(n){if(n.code==="ENOENT")return;throw n}}var ut=/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_.-]*)(\s*=\s*)(.*)$/;function ft(e,n){if(n.showSecrets)return e;return e.replace(ut,(t,s,r,i)=>M(r)?`${s}${r}${i}********`:t)}function Xe(e,n){let t=(o)=>(d)=>n.color?`\x1B[${o}m${d}\x1B[0m`:d,[s,r,i,a]=[t("1"),t("31"),t("32"),t("2")],l=(o)=>o===void 0||o===""?[]:o.replace(/\n$/,"").split(`
53
- `),c=`${v(e.path,n.home)}${e.before===void 0?" (new file)":""}`,f=Be(Fe(l(e.before),l(e.after))).map((o)=>{if(o===null)return a(" …");let d=`${o.op} ${ft(o.text,n)}`;return o.op==="+"?i(d):o.op==="-"?r(d):a(d)});return[s(c),...f].join(`
54
- `)}async function en(e){if(!process.stdin.isTTY)return!1;let n=dt({input:process.stdin,output:process.stdout});try{return/^y(es)?$/i.test((await n.question(e)).trim())}catch{return!1}finally{n.close()}}function nn(e,n){for(let r of e)if(L(r.path)!==r.before)throw Error(`${r.path} changed while envhound was running; nothing was written`);let t=new Date().toISOString().replace(/[:.]/g,"-"),s=[];for(let r of e){if(r.before!==void 0){Je(n,{recursive:!0,mode:448});let i=ct(n,`${t}-${at(r.path)}`);st(r.path,i),s.push(i)}if(Je(lt(r.path),{recursive:!0}),ot(r.path,r.after,{mode:r.mode}),r.mode!==void 0)rt(r.path,r.mode)}return s}var tn=[" ▗▙ ▟▖"," ▐▖▀▄ ▄▀▗▌"," ▐▌▙▝▜▖ ▗▛▘▟▐▌"," █▌█▙ ████████ ▟█▐█"," █▙█▄██████████▄█▟█"," ▗██████████████████▖"," ▟███▖▜▀████████▀▛▗███▙"," ▗▟█████▄▄▄██████▄▄▄█████▙▖","▗██████████████████████████▖"," ▝▀▜████████████████▛▀▘"," ▝▜████████████▛▘"," ▝▜██▛▀▀▜██▛▘"," ▝▜█▄▄█▛▘"," ▝▜▛▘"],rn=[" 00 00"," 0500 0000"," 021500 000120"," 0311 32222223 1130"," 002242222222242200"," 33333342222224333333"," 3005599534444359955003"," 66712059555444455595021766","6666667112453443542117666666"," 6677777748888477777766"," 7777728888277777"," 777138831777"," 66666666"," 6666"],sn=[["e9801c",172],["e7d3bb",187],["f8a53a",215],["ee8d22",208],["f6a034",215],["e47a16",172],["fbf1e3",255],["eedcc6",253],["d96b12",166],["7fd18b",114]];var ae={row:11,col:22};function on(e,n){let t=(l)=>{let[c,f]=sn[l];if(!n.truecolor)return`38;5;${f}`;return`38;2;${parseInt(c.slice(0,2),16)};${parseInt(c.slice(2,4),16)};${parseInt(c.slice(4),16)}`},s=(l,c)=>n.color?`\x1B[${l}m${c}\x1B[0m`:c,r=t(sn.length-1),i=tn.map((l,c)=>{let f="";for(let o=0;o<l.length;){let d=rn[c][o],u=o+1;while(u<l.length&&rn[c][u]===d)u++;f+=d===" "?l.slice(o,u):s(t(parseInt(d,36)),l.slice(o,u)),o=u}if(c===ae.row)f+=" ".repeat(ae.col-l.length)+s(`1;${r}`,"$")+s(`1;5;${r}`,"_");return{out:f,width:c===ae.row?ae.col+2:l.length}}),a=Math.floor((tn.length-e.length)/2);if((n.columns||80)<34+Math.max(...e.map((l)=>l.length)))return[...i.map((l)=>` ${l.out}`),"",...e.map((l)=>l?` ${l}`:"")].join(`
50
+ run 'envhound path' to see each entry`}function Ye(e,n){let t=q(n),s=(o)=>o.source==="startup"?T(o.addedBy,n.home):o.source==="initial"?"(initial)":"(inherited)",r=(o)=>o.dir===""?"empty entry: searches the current directory":o.duplicateOf?`duplicate of #${o.duplicateOf}`:!o.exists?"missing":"",i=e.map((o)=>[String(o.index).padStart(2),v(o.dir,n.home)||"(empty)",s(o),r(o)]),a=Math.min(50,Math.max(9,...i.map((o)=>o[1].length))),l=Math.min(40,Math.max(8,...i.map((o)=>o[2].length)));for(let o of i)o[1]=ge(o[1],a),o[2]=ge(o[2],l);let c=C([[" #","DIRECTORY","ADDED BY",""]],[2,a,l,0],[t.bold,t.bold,t.bold]),f=i.map((o,u)=>C([o],[2,a,l,0],[t.dim,(d)=>d,e[u].source==="startup"?(d)=>d:t.dim,t.red]));return[c,...f].join(`
51
+ `)}var ve=(e,n)=>({...e,value:E(e.name,e.value,n),previous:E(e.name,e.previous,n),result:E(e.name,e.result,n)}),Ze=(e,n)=>e.map((t)=>({...t,value:E(t.name,t.value,n),last:t.last&&ve(t.last,n)})),Qe=(e,n)=>({...e,fresh:E(e.name,e.fresh,n),current:E(e.name,e.current,n),assignments:e.assignments.map((t)=>ve(t,n)),pathSteps:e.pathSteps?.map((t)=>({...t,assignment:t.assignment&&ve(t.assignment,n)}))});function Ge(e,n){let t=q(n),s=(i,a)=>v(be(E(i,a,n)??""),n.home),r=[t.bold(`${v(e.file,n.home)}: ${e.keys.length} key${e.keys.length===1?"":"s"}`)];if(e.keys.length){let i=e.keys.map((o)=>{if(o.status==="new")return[o.key,String(o.line),"new",""];if(o.status==="same")return[o.key,String(o.line),"same as shell",""];let u=e.shellOrigin[o.key];return[o.key,String(o.line),"conflict",`shell has ${s(o.key,o.current)}${u?` (set at ${T(u,n.home)})`:""}`]}),a=Math.min(32,Math.max(3,...i.map((o)=>o[0].length))),l=Math.max(4,...i.map((o)=>o[1].length)),c=Math.max(6,...i.map((o)=>o[2].length)),f=(o)=>o.startsWith("conflict")?t.yellow(o):o.startsWith("same")?t.dim(o):t.green(o);r.push(C([["KEY","LINE","STATUS",""]],[a,l,c,0],[t.bold,t.bold,t.bold]));for(let o of i)r.push(C([o],[a,l,c,0],[(u)=>u,t.dim,f,t.dim]));if(i.some((o)=>o[2]==="conflict"))r.push("",t.dim("conflict: loaders such as dotenv keep the shell's value unless told to override; check yours"))}if(e.problems.length){r.push("",t.bold("Problems"));for(let i of e.problems)r.push(`${t.red(`line ${i.line}`)}${i.key?` ${i.key}`:""}: ${i.message}`)}return r.join(`
52
+ `)}function L(e){try{return it(e,"utf8")}catch(n){if(n.code==="ENOENT")return;throw n}}var dt=/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_.-]*)(\s*=\s*)(.*)$/;function ft(e,n){if(n.showSecrets)return e;return e.replace(dt,(t,s,r,i)=>M(r)?`${s}${r}${i}********`:t)}function Xe(e,n){let t=(o)=>(u)=>n.color?`\x1B[${o}m${u}\x1B[0m`:u,[s,r,i,a]=[t("1"),t("31"),t("32"),t("2")],l=(o)=>o===void 0||o===""?[]:o.replace(/\n$/,"").split(`
53
+ `),c=`${v(e.path,n.home)}${e.before===void 0?" (new file)":""}`,f=Be(Fe(l(e.before),l(e.after))).map((o)=>{if(o===null)return a(" …");let u=`${o.op} ${ft(o.text,n)}`;return o.op==="+"?i(u):o.op==="-"?r(u):a(u)});return[s(c),...f].join(`
54
+ `)}async function en(e){if(!process.stdin.isTTY)return!1;let n=ut({input:process.stdin,output:process.stdout});try{return/^y(es)?$/i.test((await n.question(e)).trim())}catch{return!1}finally{n.close()}}function nn(e,n){for(let r of e)if(L(r.path)!==r.before)throw Error(`${r.path} changed while envhound was running; nothing was written`);let t=new Date().toISOString().replace(/[:.]/g,"-"),s=[];for(let r of e){if(r.before!==void 0){Je(n,{recursive:!0,mode:448});let i=ct(n,`${t}-${at(r.path)}`);st(r.path,i),s.push(i)}if(Je(lt(r.path),{recursive:!0}),ot(r.path,r.after,{mode:r.mode}),r.mode!==void 0)rt(r.path,r.mode)}return s}var tn=[" ▗▟████▙▖"," ▗▄▄▟██████████▙▄▄▖"," ▟██████████████████▙"," ▗▟████████████████████▙▖"," ████████████████████████"," ▐███████ ▟▖▜██▛▗▙ ███████▌"," █████████▙▟████▙▟█████████","▐██████████████████████████▌","████████████████████████████","████████████████████████████","▐██████████████████████████▌","▐██████████▌ ▐██████████▌","▝███████████▙▄▄▟███████████▘"," ▀█▛▀▀ ▀█▛▀▀▀▀▀▀▜█▀ ▀▀▜█▀"],rn=[" 33333333"," 222233333333332222"," 22223333366333332222"," 022000333336633333000220"," 000000033336633330000000"," 00000001 88666688 10000000"," 00000001144666644110000000","0000000015556666555100000000","0000000115566666655110000000","0000000115766666675110000000","0000000167766666677610000000","000000116676 676611000000","0000011666666666666661100000"," 00111 666666666666 11100"],sn=[["a8521a",130],["8c4214",130],["c0631f",166],["e08a3a",172],["d07a2c",172],["c76f26",166],["fbf1e3",255],["eedcc6",252],["7fd18b",114]];var ae={row:13,col:27};function on(e,n){let t=(l)=>{let[c,f]=sn[l];if(!n.truecolor)return`38;5;${f}`;return`38;2;${parseInt(c.slice(0,2),16)};${parseInt(c.slice(2,4),16)};${parseInt(c.slice(4),16)}`},s=(l,c)=>n.color?`\x1B[${l}m${c}\x1B[0m`:c,r=t(sn.length-1),i=tn.map((l,c)=>{let f="";for(let o=0;o<l.length;){let u=rn[c][o],d=o+1;while(d<l.length&&rn[c][d]===u)d++;f+=u===" "?l.slice(o,d):s(t(parseInt(u,36)),l.slice(o,d)),o=d}if(c===ae.row)f+=" ".repeat(ae.col-l.length)+s(`1;${r}`,"$")+s(`1;5;${r}`,"_");return{out:f,width:c===ae.row?ae.col+2:l.length}}),a=Math.floor((tn.length-e.length)/2);if((n.columns||80)<35+Math.max(...e.map((l)=>l.length)))return[...i.map((l)=>` ${l.out}`),"",...e.map((l)=>l?` ${l}`:"")].join(`
55
55
  `)+`
56
- `;return i.map((l,c)=>{let f=e[c-a];return f?` ${l.out}${" ".repeat(28-l.width)} ${f}`:` ${l.out}`}).join(`
56
+ `;return i.map((l,c)=>{let f=e[c-a];return f?` ${l.out}${" ".repeat(29-l.width)} ${f}`:` ${l.out}`}).join(`
57
57
  `)+`
58
58
  `}import{existsSync as ht}from"node:fs";import{join as I}from"node:path";import{Buffer as an}from"node:buffer";var ln={n:10,t:9,r:13,a:7,b:8,e:27,E:27,f:12,v:11,"\\":92,"'":39,'"':34,"?":63};function pt(e,n){let t=[],s=(r)=>t.push(...an.from(r,"utf8"));while(n<e.length&&e[n]!=="'"){if(e[n]!=="\\"||n+1>=e.length){let l=String.fromCodePoint(e.codePointAt(n));s(l),n+=l.length;continue}let r=e[n+1],i=e.slice(n+2),a;if(r in ln)t.push(ln[r]),n+=2;else if(a=/^[0-7]{1,3}/.exec(e.slice(n+1))?.[0])t.push(parseInt(a,8)&255),n+=1+a.length;else if(r==="x"&&(a=/^[0-9a-fA-F]{1,2}/.exec(i)?.[0]))t.push(parseInt(a,16)),n+=2+a.length;else if((r==="u"||r==="U")&&(a=(r==="u"?/^[0-9a-fA-F]{1,4}/:/^[0-9a-fA-F]{1,8}/).exec(i)?.[0])){let l=parseInt(a,16);s(l<=1114111?String.fromCodePoint(l):"�"),n+=2+a.length}else if(r==="c"&&i.length>0)t.push(i.charCodeAt(0)&31),n+=3;else s("\\"+r),n+=2}return[an.from(t).toString("utf8"),n+1]}function J(e){let n=[],t="",s=!1,r=0;while(r<e.length){let i=e[r];if(i===" "||i==="\t"||i===`
59
- `){if(s)n.push(t);t="",s=!1,r++;continue}if(s=!0,i==="'"){let a=e.indexOf("'",r+1),l=a<0?e.length:a;t+=e.slice(r+1,l),r=l+1}else if(i==="$"&&e[r+1]==="'"){let[a,l]=pt(e,r+2);t+=a,r=l}else if(i==='"'){r++;while(r<e.length&&e[r]!=='"'){if(e[r]==="\\"&&'"\\$`'.includes(e[r+1]??"x"))r++;t+=e[r]??"",r++}r++}else if(i==="\\"&&r+1<e.length)t+=e[r+1],r+=2;else t+=i,r++}if(s)n.push(t);return n}function le(e,n,t){let s=!t&&n.XDG_CONFIG_HOME||I(e,".config"),r=!t&&n.XDG_STATE_HOME||I(e,".local","state");return{home:e,managed:I(s,"envhound","env.sh"),backups:I(r,"envhound","backups"),updateState:I(r,"envhound","update.json")}}var dn="# Managed by envhound: environment variables for your login shell.\n# Change them with `envhound set NAME=value`, `envhound unset NAME` and `envhound path add DIR`.\n# Hand edits are fine: envhound keeps lines it does not recognise.\n",cn='"${XDG_CONFIG_HOME:-$HOME/.config}/envhound/env.sh"',un=`
59
+ `){if(s)n.push(t);t="",s=!1,r++;continue}if(s=!0,i==="'"){let a=e.indexOf("'",r+1),l=a<0?e.length:a;t+=e.slice(r+1,l),r=l+1}else if(i==="$"&&e[r+1]==="'"){let[a,l]=pt(e,r+2);t+=a,r=l}else if(i==='"'){r++;while(r<e.length&&e[r]!=='"'){if(e[r]==="\\"&&'"\\$`'.includes(e[r+1]??"x"))r++;t+=e[r]??"",r++}r++}else if(i==="\\"&&r+1<e.length)t+=e[r+1],r+=2;else t+=i,r++}if(s)n.push(t);return n}function le(e,n,t){let s=!t&&n.XDG_CONFIG_HOME||I(e,".config"),r=!t&&n.XDG_STATE_HOME||I(e,".local","state");return{home:e,managed:I(s,"envhound","env.sh"),backups:I(r,"envhound","backups"),updateState:I(r,"envhound","update.json")}}var un="# Managed by envhound: environment variables for your login shell.\n# Change them with `envhound set NAME=value`, `envhound unset NAME` and `envhound path add DIR`.\n# Hand edits are fine: envhound keeps lines it does not recognise.\n",cn='"${XDG_CONFIG_HOME:-$HOME/.config}/envhound/env.sh"',dn=`
60
60
  # Added by envhound: load variables managed with \`envhound set\`
61
61
  [ -f ${cn} ] && . ${cn}
62
62
  `,fn=(e)=>e.includes("envhound/env.sh");function pn(e){return[".bash_profile",".bash_login",".profile"].map((t)=>I(e,t)).find((t)=>ht(t))??I(e,".profile")}var mt=/^export ([A-Za-z_][A-Za-z0-9_]*)=(.*)$/,gt=/^case ":\$PATH:" in \*":(.*):"\*\) ;; \*\) export PATH="(?:\$PATH:(.*)|(.*):\$PATH)" ;; esac$/;function vt(e,n){let t=(s)=>s.replace(/[\\"$`]/g,"\\$&");return e===n||e.startsWith(n+"/")?"$HOME"+t(e.slice(n.length)):t(e)}function bt(e,n){return e.replace(/^\$HOME(?=\/|$)/,n).replace(/\\([\\"$`])/g,"$1")}function yt(e,n,t){let s=vt(e,t),r=n==="front"?`${s}:$PATH`:`$PATH:${s}`;return`case ":$PATH:" in *":${s}:"*) ;; *) export PATH="${r}" ;; esac`}function ce(e,n){let t=e===""?[]:e.split(`
63
63
  `);if(e.endsWith(`
64
64
  `))t.pop();return t.map((s)=>{let r=mt.exec(s);if(r)return{raw:s,kind:"var",name:r[1],value:J(r[2])[0]??""};let i=gt.exec(s);if(i&&i[1]===(i[2]??i[3]))return{raw:s,kind:"path",dir:bt(i[1],n),position:i[2]!==void 0?"back":"front"};return{raw:s,kind:"other"}})}var hn=(e)=>e.map((n)=>n.raw).join(`
65
65
  `)+`
66
- `;function mn(e,n,t){let s={raw:`export ${n}=${U(t)}`,kind:"var",name:n,value:t},r=e.findIndex((i)=>i.kind==="var"&&i.name===n);if(r<0)return[...e,s];return e.flatMap((i,a)=>a===r?[s]:i.kind==="var"&&i.name===n?[]:[i])}function gn(e,n){return e.filter((t)=>!(t.kind==="var"&&t.name===n))}function vn(e,n,t,s){if(e.some((i)=>i.kind==="path"&&i.dir===n&&i.position===t))return e;let r={raw:yt(n,t,s),kind:"path",dir:n,position:t};return[...ye(e,n),r]}function ye(e,n){return e.filter((t)=>!(t.kind==="path"&&t.dir===n))}import{join as wt}from"node:path";var we=(e,n)=>e.at.file===n.managed;function bn(e,n,t){let s=L(n.managed),r=ce(s??dn,n.home),i=[],a=wt(n.home,".bashrc"),l=(d)=>N(t,d).filter((u)=>!we(u,n)),c=(d)=>[...new Set(d.map((u)=>O(u,n.home)))].join(", ");for(let d of e)switch(d.kind){case"set":{r=mn(r,d.name,d.value);let u=l(d.name);if(u.length)i.push(`${d.name} is also set at ${c(u)}; envhound's line runs last, so it wins in login shells`);if(u.some((h)=>h.at.file===a))i.push(`~/.bashrc runs again in every non-login shell and will set ${d.name} back there; consider removing that line`);break}case"unset":{if(!r.some((h)=>h.kind==="var"&&h.name===d.name))i.push(`${d.name} is not set by envhound`);r=gn(r,d.name);let u=l(d.name);if(u.length)i.push(`${d.name} is still set at ${c(u)}; envhound only edits its own file, remove that line yourself`);break}case"path-add":r=vn(r,d.dir,d.position,n.home);break;case"path-remove":{if(!r.some((h)=>h.kind==="path"&&h.dir===d.dir))i.push(`${d.dir} is not added by envhound`);r=ye(r,d.dir);let u=z(t,t.final.PATH??"").find((h)=>h.dir===d.dir&&h.addedBy&&!we(h.addedBy,n));if(u)i.push(`${d.dir} is also added at ${O(u.addedBy,n.home)}; remove that line yourself`);break}}let f=[],o=hn(r);if(o!==s)f.push({path:n.managed,before:s,after:o,mode:384});if(f.length){let d=pn(n.home),u=L(d);if(!fn(u??"")){let h=u&&!u.endsWith(`
66
+ `;function mn(e,n,t){let s={raw:`export ${n}=${U(t)}`,kind:"var",name:n,value:t},r=e.findIndex((i)=>i.kind==="var"&&i.name===n);if(r<0)return[...e,s];return e.flatMap((i,a)=>a===r?[s]:i.kind==="var"&&i.name===n?[]:[i])}function gn(e,n){return e.filter((t)=>!(t.kind==="var"&&t.name===n))}function vn(e,n,t,s){if(e.some((i)=>i.kind==="path"&&i.dir===n&&i.position===t))return e;let r={raw:yt(n,t,s),kind:"path",dir:n,position:t};return[...ye(e,n),r]}function ye(e,n){return e.filter((t)=>!(t.kind==="path"&&t.dir===n))}import{join as wt}from"node:path";var we=(e,n)=>e.at.file===n.managed;function bn(e,n,t){let s=L(n.managed),r=ce(s??un,n.home),i=[],a=wt(n.home,".bashrc"),l=(u)=>N(t,u).filter((d)=>!we(d,n)),c=(u)=>[...new Set(u.map((d)=>T(d,n.home)))].join(", ");for(let u of e)switch(u.kind){case"set":{r=mn(r,u.name,u.value);let d=l(u.name);if(d.length)i.push(`${u.name} is also set at ${c(d)}; envhound's line runs last, so it wins in login shells`);if(d.some((h)=>h.at.file===a))i.push(`~/.bashrc runs again in every non-login shell and will set ${u.name} back there; consider removing that line`);break}case"unset":{if(!r.some((h)=>h.kind==="var"&&h.name===u.name))i.push(`${u.name} is not set by envhound`);r=gn(r,u.name);let d=l(u.name);if(d.length)i.push(`${u.name} is still set at ${c(d)}; envhound only edits its own file, remove that line yourself`);break}case"path-add":r=vn(r,u.dir,u.position,n.home);break;case"path-remove":{if(!r.some((h)=>h.kind==="path"&&h.dir===u.dir))i.push(`${u.dir} is not added by envhound`);r=ye(r,u.dir);let d=z(t,t.final.PATH??"").find((h)=>h.dir===u.dir&&h.addedBy&&!we(h.addedBy,n));if(d)i.push(`${u.dir} is also added at ${T(d.addedBy,n.home)}; remove that line yourself`);break}}let f=[],o=hn(r);if(o!==s)f.push({path:n.managed,before:s,after:o,mode:384});if(f.length){let u=pn(n.home),d=L(u);if(!fn(d??"")){let h=d&&!d.endsWith(`
67
67
  `)?`
68
- `:"";f.push({path:d,before:u,after:(u??"")+h+un})}}return{changes:f,notes:i}}function yn(e,n,t){let s=L(n),r=V(s??""),i=[];for(let l of e)if(l.kind==="set"){r=Ke(r,l.name,l.value);let c=t[l.name];if(c!==void 0&&c!==l.value)i.push(`your shell already has ${l.name} with another value; loaders such as dotenv keep the shell's value`)}else if(l.kind==="unset"){if(!r.lines.some((c)=>c.kind==="entry"&&c.key===l.name))i.push(`${l.name} is not in this file`);r=We(r,l.name)}else throw Error("PATH changes only apply to the shell, not to a .env file");let a=je(r);return{changes:a===(s??"")?[]:[{path:n,before:s,after:a}],notes:i}}function wn(e,n,t){let s=(i)=>{let a=N(n,i),l=a.findLastIndex((c)=>we(c,t));return a.slice(l+1).map((c)=>O(c,t.home))},r=(n.final.PATH??"").split(":");return e.map((i)=>{switch(i.kind){case"set":{if(n.final[i.name]===i.value)return{ok:!0,message:`a fresh login shell now gets ${i.name}`};let a=s(i.name);return{ok:!1,message:`a fresh login shell does not get ${i.name}${a.length?`: ${a.join(", ")} sets it again after envhound`:""}`}}case"unset":if(n.final[i.name]===void 0)return{ok:!0,message:`${i.name} is no longer set in a fresh login shell`};return{ok:!1,message:`${i.name} is still set in a fresh login shell (envhound blame ${i.name} shows where)`};case"path-add":return r.includes(i.dir)?{ok:!0,message:`${i.dir} is now in PATH for fresh login shells`}:{ok:!1,message:`${i.dir} is not in PATH of a fresh login shell (envhound blame PATH shows why)`};case"path-remove":return r.includes(i.dir)?{ok:!1,message:`${i.dir} is still in PATH (envhound path shows who adds it)`}:{ok:!0,message:`${i.dir} is no longer in PATH for fresh login shells`}}})}function xn(e){return e.flatMap((n)=>{switch(n.kind){case"set":return[`export ${n.name}=${U(n.value)}`];case"unset":return[`unset ${n.name}`];case"path-add":return[n.position==="front"?`export PATH=${U(n.dir)}:"$PATH"`:`export PATH="$PATH":${U(n.dir)}`];case"path-remove":return[]}})}import{spawnSync as xt}from"node:child_process";import{mkdtempSync as $t,readFileSync as St,rmSync as kt}from"node:fs";import{tmpdir as Et,userInfo as $n}from"node:os";import{join as Sn,resolve as At}from"node:path";var Ot="\x1F",de="\x1E",Dt=Ot+["${BASH_SOURCE[0]:-}","${LINENO}","${FUNCNAME[0]:-}","${BASH_SOURCE[1]:-}","${BASH_LINENO[0]:-}"].join(de)+de,Tt="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";function _(e={}){let n=At(e.home??process.env.HOME??$n().homedir),t=process.env.USER??$n().username,s={HOME:n,USER:t,LOGNAME:t,SHELL:process.env.SHELL??"/bin/bash",TERM:process.env.TERM??"xterm",PATH:Tt},r=$t(Sn(Et(),"envhound-")),i=Sn(r,"env");try{let a=xt("bash",["-lixc",`env -0 > '${i}'`],{env:{...s,PS4:Dt},cwd:n,stdio:["ignore","ignore","pipe"],timeout:e.timeoutMs??15000,maxBuffer:268435456,encoding:"utf8"});if(a.error){if(a.error.code==="ETIMEDOUT")throw Error("bash startup files did not finish in time (is something waiting for input?)");throw a.error}let l="";try{l=St(i,"utf8")}catch{}if(!l)throw Error(`bash startup files did not finish (exit status ${a.status}); an 'exec' or 'exit' in a startup file stops the trace`);let c=Pt(l);return delete c.PS4,_t(a.stderr,s,c)}finally{kt(r,{recursive:!0,force:!0})}}function Pt(e){let n={};for(let t of e.split("\x00")){let s=t.indexOf("=");if(s>0)n[t.slice(0,s)]=t.slice(s+1)}return n}var Rt=/^([A-Za-z_][A-Za-z0-9_]*)(\+?=)/,Mt=/^([A-Za-z_][A-Za-z0-9_]*)(?:(\+?=)([\s\S]*))?$/;function Lt(e){let n=Rt.exec(e);if(n){let a=e.slice(n[0].length);if(a.startsWith("("))return[];return[{name:n[1],op:n[2],value:J(a)[0]??"",local:!1,bare:!1}]}let[t,...s]=J(e),r=s.filter((a)=>a.startsWith("-")).join(""),i=s.filter((a)=>!a.startsWith("-"));if(t==="unset"){if(r.includes("f"))return[];return i.map((a)=>({name:a,op:"unset",value:"",local:!1,bare:!1}))}if(t==="local"||t==="declare"||t==="typeset"){if(/[aAfF]/.test(r))return[];let a=t==="local"||!r.includes("g");return i.flatMap((l)=>{let c=Mt.exec(l);if(!c)return[];return[{name:c[1],op:c[2]??"=",value:c[3]??"",local:a,bare:c[2]===void 0}]})}return[]}function _t(e,n,t){let s=[],r=new Map(Object.entries(n)),i=new Map;for(let a of e.split(/(?:^|\n)\x1f+/).slice(1)){let l=a.split(de);if(l.length<6)continue;let[c,f,o,d,u]=l;if(!c)continue;let h=l.slice(5).join(de).replace(/\n$/,""),p=o?{kind:o==="source"||o==="."?"source":"function",name:o,at:{file:d,line:Number(u)}}:void 0,m=p?.kind==="function"?`${p.name}@${p.at.file}:${p.at.line}`:void 0;for(let g of Lt(h)){let y=!1;if(m){let T=i.get(m)??new Set;if(i.set(m,T),g.local)T.add(g.name);y=T.has(g.name)}if(g.bare)continue;let w=y?void 0:r.get(g.name),A=g.op==="unset"?void 0:g.op==="+="?(w??"")+g.value:g.value;if(!y)r.set(g.name,A);let{name:R,op:P,value:x}=g;s.push({name:R,op:P,value:x,previous:w,result:A,at:{file:c,line:Number(f)},via:p,local:y})}}return{shell:"bash",initial:n,final:t,assignments:s}}function Ht(e,n){let t=L(e),s=V(t??""),r=ie(s),i=new Map;for(let l of s.lines)if(l.kind==="entry")i.set(l.key,(i.get(l.key)??0)+1);let a=oe(s,n).map((l)=>({key:l.key,value:l.value,line:l.line,status:l.status,current:l.current,count:i.get(l.key)??1,problem:r.find((c)=>c.key===l.key)?.message}));return{file:e,exists:t!==void 0,rows:a,problems:r.length}}function kn(e,n,t,s=[]){let r=ce(L(t.managed)??"",t.home),i=new Set(r.flatMap((o)=>o.kind==="var"?[o.name]:[])),a=new Set(r.flatMap((o)=>o.kind==="path"?[o.dir]:[])),l=(o)=>o.at.file===t.managed?"envhound":O(o,t.home),c=re(e,n).filter((o)=>o.name!=="PATH").map((o)=>({name:o.name,value:o.value,by:o.last?l(o.last):o.kind==="shell"?"(login/shell)":"(inherited)",source:o.last&&G(o.last),managed:i.has(o.name)}));for(let o of i)if(!c.some((d)=>d.name===o)&&o!=="PATH")c.push({name:o,value:e.final[o],by:"envhound (new shells)",managed:!0});let f=z(e,n.PATH??"").map((o)=>({dir:o.dir,by:o.addedBy?l(o.addedBy):`(${o.source})`,source:o.addedBy&&G(o.addedBy),managed:a.has(o.dir),exists:o.exists,duplicateOf:o.duplicateOf}));for(let o of a)if(!f.some((d)=>d.dir===o))f.push({dir:o,by:"envhound (new shells)",managed:!0,exists:!0});return{home:t.home,vars:c,path:f,dotenv:s.map((o)=>Ht(o,n))}}import{spawnSync as or}from"node:child_process";import{emitKeypressEvents as ar}from"node:readline";import{isAbsolute as Nt,resolve as En}from"node:path";function fe(e){return[{kind:"vars"},{kind:"path"},...e.data.dotenv.map((n)=>({kind:"dotenv",file:n.file}))]}var xe=(e)=>e.kind==="dotenv"?`dotenv:${e.file}`:e.kind,K=(e)=>fe(e)[e.tab]??{kind:"vars"},S=(e)=>e.cursor[xe(K(e))]??0;function An(e,n={}){return{data:e,tab:n.tab??0,ops:[],cursor:{},filter:"",confirmQuit:!1,help:!1,showSecrets:n.showSecrets??!1,pageSize:10}}function On(e,...n){let t=e.filter.toLowerCase();return!t||n.some((s)=>s?.toLowerCase().includes(t))}function ee(e){let n=e.data.vars.map((t)=>({...t}));for(let t of e.ops){if(t.kind!=="set"&&t.kind!=="unset"||t.file)continue;let s=n.find((r)=>r.name===t.name);if(t.kind==="unset"){if(s)s.pending="unset"}else if(s)s.pending="set",s.newValue=t.value;else n.push({name:t.name,value:void 0,by:"envhound",managed:!0,pending:"new",newValue:t.value})}return n.sort((t,s)=>t.name.localeCompare(s.name)),n.filter((t)=>On(e,t.name,t.newValue??t.value))}function ne(e){let n=e.data.path.map((t)=>({...t}));for(let t of e.ops)if(t.kind==="path-remove"){for(let s of n)if(s.dir===t.dir)s.pending="remove"}else if(t.kind==="path-add"){let s={dir:t.dir,by:"envhound",managed:!0,exists:!0,pending:"add"};if(t.position==="front")n.unshift(s);else n.push(s)}return n}function te(e,n){let s=(e.data.dotenv.find((r)=>r.file===n)?.rows??[]).map((r)=>({...r}));for(let r of e.ops){if(r.kind!=="set"&&r.kind!=="unset"||r.file!==n)continue;let i=s.find((a)=>a.key===r.name);if(r.kind==="unset"){if(i)i.pending="unset"}else if(i)i.pending="set",i.newValue=r.value;else s.push({key:r.name,value:"",line:0,status:"new",count:0,pending:"new",newValue:r.value})}return s.filter((r)=>On(e,r.key,r.newValue??r.value))}function Vt(e){let n=K(e);return n.kind==="vars"?ee(e).length:n.kind==="path"?ne(e).length:te(e,n.file).length}function ue(e){if(e.kind==="set"||e.kind==="unset")return`${e.file??"shell"}:${e.name}`;return`path:${e.dir}`}function X(e,n,t){let s=e.ops.filter((r)=>ue(r)!==ue(n));return{...e,ops:[...s,n],message:{text:t}}}function $e(e,n,t){return{...e,ops:e.ops.filter((s)=>ue(s)!==ue(n)),message:{text:t}}}var Ct=/^[A-Za-z_][A-Za-z0-9_]*$/,It=/^[A-Za-z_][A-Za-z0-9_.-]*$/;function jt(e,n){let t=e.trim().replace(/^~(?=\/|$)/,n);return Nt(t)?En(t):En(process.cwd(),t)}var j=(e,n)=>v(n,e.data.home);function Kt(e){let n=e.prompt,t={...e,prompt:void 0};switch(n.for.kind){case"filter":return{...t,filter:n.value};case"edit":case"new-value":{let{name:s,file:r}=n.for;return X(t,{kind:"set",name:s,value:n.value,file:r},`${s} will be set${r?` in ${j(e,r)}`:""}`)}case"new-name":{let s=n.value.trim(),r=n.for.file;if(!(r?It:Ct).test(s))return{...e,message:{text:`'${s}' is not a valid ${r?"key":"variable name"}`,error:!0}};if(s==="PATH"&&!r)return{...t,message:{text:"use the PATH tab to change PATH",error:!0}};let i=r?e.data.dotenv.find((a)=>a.file===r)?.rows.find((a)=>a.key===s)?.value:e.data.vars.find((a)=>a.name===s)?.value;return{...t,prompt:{for:{kind:"new-value",name:s,file:r},label:`${s}=`,value:i??"",secret:M(s)}}}case"path-add":{if(!n.value.trim())return t;let s=jt(n.value,e.data.home);if(e.data.path.some((r)=>r.dir===s))return{...t,message:{text:`${j(e,s)} is already in PATH`,error:!0}};return X(t,{kind:"path-add",dir:s,position:n.for.position},`${j(e,s)} will be added`)}}}function Wt(e,n){let t=e.prompt,s=(r)=>{let i={...e,prompt:{...t,value:r}};return t.for.kind==="filter"?{...i,filter:r,cursor:{...e.cursor,[xe(K(e))]:0}}:i};if(n.name==="escape")return{...e,prompt:void 0,filter:t.for.kind==="filter"?"":e.filter};if(n.name==="return"||n.name==="enter")return Kt(e);if(n.name==="backspace")return s([...t.value].slice(0,-1).join(""));if(n.ctrl&&n.name==="u")return s("");if(n.ch&&!n.ctrl)return s(t.value+n.ch);return e}function E(e,n){let t=Math.max(0,Vt(e)-1);return{...e,cursor:{...e.cursor,[xe(K(e))]:Math.min(t,Math.max(0,n))}}}function Dn(e,n,t,s,r){switch(n.ch){case"/":return[{...e,prompt:{for:{kind:"filter"},label:"filter: ",value:e.filter}}];case"n":return[{...e,prompt:{for:{kind:"new-name",file:s},label:s?"new key: ":"new variable name: ",value:""}}];case"s":return[{...e,showSecrets:!e.showSecrets}]}if(!t)return[e];if(n.ch==="e"||n.name==="return"||n.name==="enter")return[{...e,prompt:{for:{kind:"edit",name:t.name,file:s},label:`${t.name}=`,value:t.value,secret:M(t.name)}}];if(n.ch==="d")return[r()];if(n.ch==="o"){if(!t.source)return[{...e,message:{text:s?"not in the file yet: write first":"not set by a startup file, nothing to open",error:!0}}];return[e,{kind:"open",at:t.source}]}return[e]}function Ft(e,n){let t=ee(e)[S(e)],s=t&&{name:t.name,value:t.newValue??t.value??"",source:t.source};return Dn(e,n,s,void 0,()=>{let r=t,i={kind:"unset",name:r.name};if(r.pending==="new"||r.pending==="unset"||r.pending==="set"&&!r.managed)return $e(e,i,`change to ${r.name} dropped`);if(r.managed)return X(e,i,`${r.name} will be removed from envhound's file`);return{...e,message:{text:Tn(r.name,r),error:!0}}})}function Bt(e,n,t){let s=te(e,t)[S(e)],r=s&&{name:s.key,value:s.newValue??s.value,source:s.line?{file:t,line:s.line}:void 0};return Dn(e,n,r,t,()=>{let i=s,a={kind:"unset",name:i.key,file:t};if(i.pending)return $e(e,a,`change to ${i.key} dropped`);return X(e,a,`${i.key} will be deleted from ${j(e,t)}`)})}function zt(e,n){if(n.ch==="a"||n.ch==="A"){let s=n.ch==="a"?"front":"back";return[{...e,prompt:{for:{kind:"path-add",position:s},label:`add to the ${s} of PATH: `,value:""}}]}let t=ne(e)[S(e)];if(!t)return[e];if(n.ch==="d"){let s={kind:"path-remove",dir:t.dir};if(t.pending)return[$e(e,s,`change to ${j(e,t.dir)} dropped`)];if(t.managed)return[X(e,s,`${j(e,t.dir)} will be removed`)];return[{...e,message:{text:Tn(j(e,t.dir),t),error:!0}}]}if(n.ch==="o"){if(!t.source)return[{...e,message:{text:"not added by a startup file, nothing to open",error:!0}}];return[e,{kind:"open",at:t.source}]}return[e]}function Tn(e,n){if(n.source)return`${e} comes from ${n.by}, not envhound: press o to open that line`;return`${e} ${n.by==="(inherited)"?"comes from the program that started this shell":"is set by login or bash itself"}`}function Pn(e,n){if(n.ctrl&&n.name==="c")return[e,{kind:"quit"}];if(e.prompt)return[Wt(e,n)];let t={...e,message:void 0,confirmQuit:!1};if(e.help)return[{...t,help:!1}];if(n.ch==="q"||n.name==="escape"&&!e.filter){if(e.ops.length&&!e.confirmQuit)return[{...t,confirmQuit:!0,message:{text:`${e.ops.length} staged change(s) will be lost: q again to quit, w to write`,error:!0}}];return[t,{kind:"quit"}]}if(n.name==="escape")return[{...t,filter:""}];let s=fe(t).length;if(n.name==="tab")return[{...t,tab:(t.tab+1)%s}];if(n.ch&&/^[1-9]$/.test(n.ch)&&Number(n.ch)<=s)return[{...t,tab:Number(n.ch)-1}];if(n.ch==="?")return[{...t,help:!0}];if(n.ch==="w")return t.ops.length?[t,{kind:"write"}]:[{...t,message:{text:"nothing staged yet"}}];if(n.ch==="u"){if(!t.ops.length)return[{...t,message:{text:"nothing to undo"}}];return[E({...t,ops:t.ops.slice(0,-1),message:{text:"undone"}},S(t))]}let r=S(t);switch(n.name){case"up":return[E(t,r-1)];case"down":return[E(t,r+1)];case"pageup":return[E(t,r-t.pageSize)];case"pagedown":return[E(t,r+t.pageSize)];case"home":return[E(t,0)];case"end":return[E(t,1/0)]}if(n.ch==="k")return[E(t,r-1)];if(n.ch==="j")return[E(t,r+1)];if(n.ch==="g")return[E(t,0)];if(n.ch==="G")return[E(t,1/0)];let i=K(t),[a,l]=i.kind==="vars"?Ft(t,n):i.kind==="path"?zt(t,n):Bt(t,n,i.file);return[E(a,S(a)),l]}import{basename as Ut,relative as qt}from"node:path";var W=(e,n)=>(t)=>n&&t?`\x1B[${e}m${t}\x1B[0m`:t;function Yt(e){return{bold:W("1",e),dim:W("2",e),inverse:W("7",e),red:W("31",e),green:W("32",e),yellow:W("33",e),cyan:W("36",e)}}function b(e,n){if(n<=0)return"";let t=e.replace(/[\x00-\x1f\x7f]/g," "),s=[...t];return s.length>n?s.slice(0,n-1).join("")+"…":t.padEnd(n+(t.length-s.length))}function Z(e,n){return[...e].length>n?b(e,n):e}function Se(e,n){let t=[...e];return t.length>n?"…"+t.slice(t.length-n+1).join(""):b(e,n)}var Zt={vars:"↑↓ move / filter enter edit n new d unset o open u undo w write tab next ? help q quit",path:"↑↓ move a add front A add end d remove o open u undo w write tab next ? help q quit",dotenv:"↑↓ move / filter enter edit n new d delete o open u undo w write tab next ? help q quit"},Qt=["envhound edit: stage changes, then w to review them as a diff and write.","","Variables tab"," enter / e edit the value (envhound's line comes last, so it wins in login shells)"," n new variable"," d unset a variable envhound set, or drop a staged change"," o open your editor at the line that sets it, then reload"," / filter by name or value; esc clears"," s show or hide secret values","","PATH tab"," a / A add a directory at the front / end of PATH"," d remove a directory envhound added, or drop a staged change"," o open the line that adds it","",".env tabs (envhound edit FILE...; ./.env is opened when it exists)"," enter / e edit a value: only that line changes, comments and quoting are kept"," n / d add a key / delete every line for a key"," o open the file at the key's line","","Anywhere"," u undo · w write · tab or 1-9 switch tabs · q or esc quit · ctrl-c quit now","","Press any key to go back."];function ke(e,n,t){return Math.max(0,Math.min(e-Math.floor(t/2),n-t))}function Gt(e,n){if(e.kind==="vars")return"Variables";if(e.kind==="path")return"PATH";let t=qt(process.cwd(),e.file),s=t&&!t.startsWith("..")?t:v(e.file,n);return s.length>24?Ut(e.file):s}function Jt(e,n,t){let r=fe(e).map((f,o)=>` ${o+1} ${Gt(f,e.data.home)} `),i=r.map((f,o)=>o===e.tab?t.inverse(f):t.dim(f)).join(" "),a=r.join(" ").length,l=e.ops.length?`${e.ops.length} staged · w to write`:"nothing staged",c=e.ops.length?t.yellow(l):t.dim(l);if(15+a+3+l.length<=n)return`${t.bold("envhound edit")} ${i} ${c}`;if(a+1+l.length<=n)return`${i} ${c}`;if(a<=n)return i;return t.inverse(b(r[e.tab].trim(),n))}function Rn(e,n,t,s){let r=Yt(s.color),i=[Jt(e,n,r),r.dim("─".repeat(Math.max(0,n)))],a=K(e),l=[r.dim(b(Zt[a.kind],n))];if(e.prompt){let d=e.prompt.secret&&!e.showSecrets?"•".repeat([...e.prompt.value].length):e.prompt.value,u=Math.max(1,n-e.prompt.label.length-1),h=[...d],p=h.length>u?"…"+h.slice(h.length-u+1).join(""):d;l.push(r.bold(e.prompt.label)+p+(s.color?"\x1B[7m \x1B[0m":"_"))}else if(e.message)l.push(e.message.error?r.red(b(e.message.text,n)):r.green(b(e.message.text,n)));else l.push(r.dim(b(e.filter?`filter: ${e.filter} (esc clears)`:"",n)));let c=Math.max(0,t-i.length-l.length-2),f,o;if(e.help)f=Qt.map((d)=>b(d,n)),o="";else if(a.kind==="vars")f=Xt(e,n,c,r),o=er(e);else if(a.kind==="path")f=nr(e,n,c,r),o=tr(e);else f=sr(e,a.file,n,c,r),o=ir(e,a.file);for(let d=0;d<c;d++)i.push(f[d]??"");return i.push(r.dim("─".repeat(Math.max(0,n)))),i.push(b(o,n)),i.push(...l),i.slice(0,t)}function Y(e,n,t){if(n===void 0)return"";let s=n.replace(/\n/g,"\\n");return M(e)&&!t.showSecrets?"********":v(s,t.data.home)}function Mn(e,n,t,s){if(n.pending==="unset")return`${Y(e,n.value,t)} (${s})`;if(n.pending==="new")return Y(e,n.newValue,t);if(n.pending==="set")return`${Y(e,n.value,t)} → ${Y(e,n.newValue,t)}`;return Y(e,n.value,t)}var Ee=(e)=>e==="new"||e==="add"?"+":e==="unset"||e==="remove"?"-":e?"*":" ";function Xt(e,n,t,s){let r=ee(e);if(!r.length)return[s.dim(e.filter?"no variable matches the filter":"no variables")];let i=Math.min(28,Math.max(8,...r.map((u)=>u.name.length))),a=Math.min(32,Math.max(6,...r.map((u)=>u.by.length))),l=Math.max(10,n-i-a-6),c=t-1,f=S(e),o=ke(f,r.length,c),d=[s.bold(Z(` ${b("NAME",i)} ${b("SET BY",a)} VALUE`,n))];return r.slice(o,o+c).forEach((u,h)=>{let p=Z(`${Ee(u.pending)} ${b(u.name,i)} ${Se(u.by,a)} ${b(Mn(u.name,u,e,"unset"),l)}`,n),m=u.pending==="unset"?s.red:u.pending?s.yellow:u.managed?s.cyan:u.by.startsWith("(")?s.dim:(g)=>g;d.push(o+h===f?s.inverse(p):m(p))}),d}function er(e){let n=ee(e)[S(e)];if(!n)return"";let t=n.managed?"set by envhound":n.source?`set at ${n.by}`:n.by==="(inherited)"?"inherited from the program that started this shell":"set by login or bash";return`${n.name}: ${t}${n.pending?`, staged: ${n.pending}`:""}`}function nr(e,n,t,s){let r=ne(e);if(!r.length)return[s.dim("PATH is empty")];let i=Math.min(60,Math.max(9,...r.map((d)=>v(d.dir,e.data.home).length))),a=Math.min(32,Math.max(8,...r.map((d)=>d.by.length))),l=t-1,c=S(e),f=ke(c,r.length,l),o=[s.bold(Z(` # ${b("DIRECTORY",i)} ${b("ADDED BY",a)}`,n))];return r.slice(f,f+l).forEach((d,u)=>{let h=f+u,p=d.pending?`staged: ${d.pending}`:d.duplicateOf?`duplicate of #${d.duplicateOf}`:!d.exists?"missing":"",m=Z(`${Ee(d.pending)} ${String(h+1).padStart(2)} ${Se(v(d.dir,e.data.home),i)} ${Se(d.by,a)} ${b(p,Math.max(0,n-i-a-10))}`,n),g=d.pending==="remove"?s.red:d.pending?s.yellow:p?s.red:d.managed?s.cyan:d.by.startsWith("(")?s.dim:(y)=>y;o.push(h===c?s.inverse(m):g(m))}),o}function tr(e){let n=ne(e)[S(e)];if(!n)return"";let t=n.managed?"added by envhound":n.source?`added at ${n.by}`:n.by==="(initial)"?"in the initial PATH bash starts with":"inherited from the program that started this shell";return`${v(n.dir,e.data.home)}: ${t}`}function rr(e,n){if(e.pending)return`staged: ${e.pending==="unset"?"delete":e.pending}`;if(e.problem)return e.problem;if(e.count>1)return`defined ${e.count} times`;if(e.status==="conflict")return`shell has ${Y(e.key,e.current,n)}`;return e.status==="same"?"same as shell":""}function sr(e,n,t,s,r){let i=te(e,n),a=e.data.dotenv.find((p)=>p.file===n);if(!i.length){if(e.filter)return[r.dim("no key matches the filter")];return[r.dim(a?.exists?"no keys in this file: n adds one":"this file does not exist yet: n adds a key, w creates it")]}let l=Math.min(28,Math.max(3,...i.map((p)=>p.key.length))),c=Math.max(10,Math.min(48,t-l-32)),f=Math.max(0,t-l-c-12),o=s-1,d=S(e),u=ke(d,i.length,o),h=[r.bold(Z(` ${b("KEY",l)} LINE ${b("VALUE",c)} `,t))];return i.slice(u,u+o).forEach((p,m)=>{let g=rr(p,e),y=p.line?String(p.line).padStart(4):" ",w=Z(`${Ee(p.pending)} ${b(p.key,l)} ${y} ${b(Mn(p.key,p,e,"delete"),c)} ${b(g,f)}`,t),A=p.pending==="unset"?r.red:p.pending?r.yellow:p.problem||p.count>1?r.red:p.status==="conflict"?r.yellow:p.status==="same"?r.dim:(R)=>R;h.push(u+m===d?r.inverse(w):A(w))}),h}function ir(e,n){let t=e.data.dotenv.find((c)=>c.file===n),s=v(n,e.data.home),r=t?.problems?` · ${t.problems} problem(s), see envhound dotenv`:"",i=te(e,n)[S(e)];if(!i)return`${s}${t?.exists?"":" (new file)"}${r}`;let a=i.line?`line ${i.line} of ${s}`:`new in ${s}`,l=i.status==="conflict"?": loaders such as dotenv keep the shell's value":"";return`${i.key}: ${a}${l}${r}`}var lr="\x1B[?1049h\x1B[?25l",cr="\x1B[?25h\x1B[?1049l";function dr(e,n){let[t="vi",...s]=e.trim().split(/\s+/),r=t.split("/").pop()??t;if(/^(code|code-insiders|codium|cursor|windsurf)$/.test(r))return[t,[...s.filter((i)=>i!=="--wait"),"--wait","-g",`${n.file}:${n.line}`]];return[t,[...s,`+${n.line}`,n.file]]}function Ln(e,n){let t=process.stdin,s=process.stdout,r=An(e(),{showSecrets:n.showSecrets,tab:n.tab});return new Promise((i,a)=>{let l=()=>{let h=s.columns||80,p=s.rows||24;r={...r,pageSize:Math.max(1,p-8)};let m=Rn(r,h,p,{color:n.color});s.write("\x1B[H"+m.map((g)=>g+"\x1B[K").join(`\r
69
- `)+"\x1B[J")},c=()=>{s.write(lr),t.setRawMode(!0),t.resume(),l()},f=()=>{t.setRawMode(!1),t.pause(),s.write(cr)},o=(h)=>{t.off("keypress",u),s.off("resize",l),f(),i(h)},d=(h)=>{f();let[p,m]=dr(process.env.VISUAL||process.env.EDITOR||"vi",h),g=or(p,m,{stdio:"inherit"});if(c(),g.error)r={...r,message:{text:`could not run ${p}: ${g.error.message}`,error:!0}};else s.write("\x1B[H\x1B[Jreloading…"),r={...r,data:e(),message:{text:"reloaded after editing"}};l()},u=(h,p)=>{try{let m=h&&!p?.ctrl&&!p?.meta&&[...h].length===1&&h>=" "&&h!==""?h:void 0,g={name:p?.name,ch:m,ctrl:p?.ctrl},[y,w]=Pn(r,g);if(r=y,w?.kind==="quit")return o(void 0);if(w?.kind==="write")return o(r.ops);if(w?.kind==="open")return d(w.at);l()}catch(m){t.off("keypress",u),f(),a(m)}};ar(t),t.on("keypress",u),s.on("resize",l),c()})}import{spawn as ur}from"node:child_process";import{mkdirSync as fr,readFileSync as pr,realpathSync as hr,renameSync as mr,writeFileSync as gr}from"node:fs";import{dirname as vr}from"node:path";var br="michelsalib/envhound",yr="https://registry.npmjs.org/envhound/latest",wr=604800000;function De(e){let n=e.replaceAll("\\","/");if(n.includes("/_npx/"))return"npx";if(n.includes("/bunx-"))return"bunx";if(n.includes("/.bun/install/global/"))return"bun";if(n.includes("/node_modules/"))return"npm";if(/\.[cm]?[jt]s$/.test(n))return"dev";return"standalone"}function Te(e){switch(e){case"npx":return"npx envhound@latest";case"bunx":return"bunx envhound@latest";case"bun":return"bun add -g envhound@latest";case"npm":return"npm install -g envhound@latest";case"standalone":return"envhound upgrade";case"dev":return"git pull && bun run build"}}function xr(e,n){let t=(i)=>(i.match(/^v?(\d+)\.(\d+)\.(\d+)/)??[]).slice(1).map(Number),[s,r]=[t(e),t(n)];if(s.length!==3||r.length!==3)return!1;for(let i=0;i<3;i++)if(s[i]!==r[i])return s[i]>r[i];return!1}function $r(e,n,t){let s=(r)=>r===void 0||t-r>=wr||r>t;return{check:s(e.checkedAt),notify:e.latest!==void 0&&xr(e.latest,n)&&s(e.notifiedAt)}}function Ae(e){try{let n=JSON.parse(pr(e,"utf8"));return typeof n==="object"&&n?n:{}}catch{return{}}}function Oe(e,n){fr(vr(e),{recursive:!0});let t=`${e}.${process.pid}.tmp`;gr(t,JSON.stringify(n)+`
70
- `),mr(t,e)}function _n(e,n,t,s=Date.now()){try{let r=De(hr(n));if(r==="dev")return;let i=Ae(t),{check:a,notify:l}=$r(i,e,s);if(a)Oe(t,{...i,checkedAt:s}),ur(process.execPath,[n,"__update-check",t],{detached:!0,stdio:"ignore"}).unref();if(!l)return;return Oe(t,{...Ae(t),notifiedAt:s}),`envhound ${i.latest} is available (you have ${e}). Update with: ${Te(r)}`}catch{return}}async function Hn(e){let n=await fetch(yr,{signal:AbortSignal.timeout(1e4),headers:{accept:"application/json"}});if(!n.ok)return;let{version:t}=await n.json();if(typeof t!=="string")return;Oe(e,{...Ae(e),checkedAt:Date.now(),latest:t})}function Nn(e){return`${e.ENVHOUND_BASE_URL??`https://github.com/${br}/releases/latest/download`}/install.sh`}var Tr="find which startup file sets each environment variable, and change it",Vn=`Usage:
68
+ `:"";f.push({path:u,before:d,after:(d??"")+h+dn})}}return{changes:f,notes:i}}function yn(e,n,t){let s=L(n),r=V(s??""),i=[];for(let l of e)if(l.kind==="set"){r=Ke(r,l.name,l.value);let c=t[l.name];if(c!==void 0&&c!==l.value)i.push(`your shell already has ${l.name} with another value; loaders such as dotenv keep the shell's value`)}else if(l.kind==="unset"){if(!r.lines.some((c)=>c.kind==="entry"&&c.key===l.name))i.push(`${l.name} is not in this file`);r=We(r,l.name)}else throw Error("PATH changes only apply to the shell, not to a .env file");let a=je(r);return{changes:a===(s??"")?[]:[{path:n,before:s,after:a}],notes:i}}function wn(e,n,t){let s=(i)=>{let a=N(n,i),l=a.findLastIndex((c)=>we(c,t));return a.slice(l+1).map((c)=>T(c,t.home))},r=(n.final.PATH??"").split(":");return e.map((i)=>{switch(i.kind){case"set":{if(n.final[i.name]===i.value)return{ok:!0,message:`a fresh login shell now gets ${i.name}`};let a=s(i.name);return{ok:!1,message:`a fresh login shell does not get ${i.name}${a.length?`: ${a.join(", ")} sets it again after envhound`:""}`}}case"unset":if(n.final[i.name]===void 0)return{ok:!0,message:`${i.name} is no longer set in a fresh login shell`};return{ok:!1,message:`${i.name} is still set in a fresh login shell (envhound blame ${i.name} shows where)`};case"path-add":return r.includes(i.dir)?{ok:!0,message:`${i.dir} is now in PATH for fresh login shells`}:{ok:!1,message:`${i.dir} is not in PATH of a fresh login shell (envhound blame PATH shows why)`};case"path-remove":return r.includes(i.dir)?{ok:!1,message:`${i.dir} is still in PATH (envhound path shows who adds it)`}:{ok:!0,message:`${i.dir} is no longer in PATH for fresh login shells`}}})}function xn(e){return e.flatMap((n)=>{switch(n.kind){case"set":return[`export ${n.name}=${U(n.value)}`];case"unset":return[`unset ${n.name}`];case"path-add":return[n.position==="front"?`export PATH=${U(n.dir)}:"$PATH"`:`export PATH="$PATH":${U(n.dir)}`];case"path-remove":return[]}})}import{spawnSync as xt}from"node:child_process";import{mkdtempSync as $t,readFileSync as St,rmSync as kt}from"node:fs";import{tmpdir as Et,userInfo as $n}from"node:os";import{join as Sn,resolve as At}from"node:path";var Tt="\x1F",ue="\x1E",Ot=Tt+["${BASH_SOURCE[0]:-}","${LINENO}","${FUNCNAME[0]:-}","${BASH_SOURCE[1]:-}","${BASH_LINENO[0]:-}"].join(ue)+ue,Dt="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";function _(e={}){let n=At(e.home??process.env.HOME??$n().homedir),t=process.env.USER??$n().username,s={HOME:n,USER:t,LOGNAME:t,SHELL:process.env.SHELL??"/bin/bash",TERM:process.env.TERM??"xterm",PATH:Dt},r=$t(Sn(Et(),"envhound-")),i=Sn(r,"env");try{let a=xt("bash",["-lixc",`env -0 > '${i}'`],{env:{...s,PS4:Ot},cwd:n,stdio:["ignore","ignore","pipe"],timeout:e.timeoutMs??15000,maxBuffer:268435456,encoding:"utf8"});if(a.error){if(a.error.code==="ETIMEDOUT")throw Error("bash startup files did not finish in time (is something waiting for input?)");throw a.error}let l="";try{l=St(i,"utf8")}catch{}if(!l)throw Error(`bash startup files did not finish (exit status ${a.status}); an 'exec' or 'exit' in a startup file stops the trace`);let c=Pt(l);return delete c.PS4,_t(a.stderr,s,c)}finally{kt(r,{recursive:!0,force:!0})}}function Pt(e){let n={};for(let t of e.split("\x00")){let s=t.indexOf("=");if(s>0)n[t.slice(0,s)]=t.slice(s+1)}return n}var Rt=/^([A-Za-z_][A-Za-z0-9_]*)(\+?=)/,Mt=/^([A-Za-z_][A-Za-z0-9_]*)(?:(\+?=)([\s\S]*))?$/;function Lt(e){let n=Rt.exec(e);if(n){let a=e.slice(n[0].length);if(a.startsWith("("))return[];return[{name:n[1],op:n[2],value:J(a)[0]??"",local:!1,bare:!1}]}let[t,...s]=J(e),r=s.filter((a)=>a.startsWith("-")).join(""),i=s.filter((a)=>!a.startsWith("-"));if(t==="unset"){if(r.includes("f"))return[];return i.map((a)=>({name:a,op:"unset",value:"",local:!1,bare:!1}))}if(t==="local"||t==="declare"||t==="typeset"){if(/[aAfF]/.test(r))return[];let a=t==="local"||!r.includes("g");return i.flatMap((l)=>{let c=Mt.exec(l);if(!c)return[];return[{name:c[1],op:c[2]??"=",value:c[3]??"",local:a,bare:c[2]===void 0}]})}return[]}function _t(e,n,t){let s=[],r=new Map(Object.entries(n)),i=new Map;for(let a of e.split(/(?:^|\n)\x1f+/).slice(1)){let l=a.split(ue);if(l.length<6)continue;let[c,f,o,u,d]=l;if(!c)continue;let h=l.slice(5).join(ue).replace(/\n$/,""),p=o?{kind:o==="source"||o==="."?"source":"function",name:o,at:{file:u,line:Number(d)}}:void 0,m=p?.kind==="function"?`${p.name}@${p.at.file}:${p.at.line}`:void 0;for(let g of Lt(h)){let b=!1;if(m){let D=i.get(m)??new Set;if(i.set(m,D),g.local)D.add(g.name);b=D.has(g.name)}if(g.bare)continue;let x=b?void 0:r.get(g.name),w=g.op==="unset"?void 0:g.op==="+="?(x??"")+g.value:g.value;if(!b)r.set(g.name,w);let{name:R,op:P,value:S}=g;s.push({name:R,op:P,value:S,previous:x,result:w,at:{file:c,line:Number(f)},via:p,local:b})}}return{shell:"bash",initial:n,final:t,assignments:s}}function Ht(e,n){let t=L(e),s=V(t??""),r=ie(s),i=new Map;for(let l of s.lines)if(l.kind==="entry")i.set(l.key,(i.get(l.key)??0)+1);let a=oe(s,n).map((l)=>({key:l.key,value:l.value,line:l.line,status:l.status,current:l.current,count:i.get(l.key)??1,problem:r.find((c)=>c.key===l.key)?.message}));return{file:e,exists:t!==void 0,rows:a,problems:r.length}}function kn(e,n,t,s=[]){let r=ce(L(t.managed)??"",t.home),i=new Set(r.flatMap((o)=>o.kind==="var"?[o.name]:[])),a=new Set(r.flatMap((o)=>o.kind==="path"?[o.dir]:[])),l=(o)=>o.at.file===t.managed?"envhound":T(o,t.home),c=re(e,n).filter((o)=>o.name!=="PATH").map((o)=>({name:o.name,value:o.value,by:o.last?l(o.last):o.kind==="shell"?"(login/shell)":"(inherited)",source:o.last&&G(o.last),managed:i.has(o.name)}));for(let o of i)if(!c.some((u)=>u.name===o)&&o!=="PATH")c.push({name:o,value:e.final[o],by:"envhound (new shells)",managed:!0});let f=z(e,n.PATH??"").map((o)=>({dir:o.dir,by:o.addedBy?l(o.addedBy):`(${o.source})`,source:o.addedBy&&G(o.addedBy),managed:a.has(o.dir),exists:o.exists,duplicateOf:o.duplicateOf}));for(let o of a)if(!f.some((u)=>u.dir===o))f.push({dir:o,by:"envhound (new shells)",managed:!0,exists:!0});return{home:t.home,vars:c,path:f,dotenv:s.map((o)=>Ht(o,n))}}import{spawnSync as or}from"node:child_process";import{emitKeypressEvents as ar}from"node:readline";import{isAbsolute as Nt,resolve as En}from"node:path";function fe(e){return[{kind:"vars"},{kind:"path"},...e.data.dotenv.map((n)=>({kind:"dotenv",file:n.file}))]}var xe=(e)=>e.kind==="dotenv"?`dotenv:${e.file}`:e.kind,K=(e)=>fe(e)[e.tab]??{kind:"vars"},k=(e)=>e.cursor[xe(K(e))]??0;function An(e,n={}){return{data:e,tab:n.tab??0,ops:[],cursor:{},filter:"",confirmQuit:!1,help:!1,showSecrets:n.showSecrets??!1,pageSize:10}}function Tn(e,...n){let t=e.filter.toLowerCase();return!t||n.some((s)=>s?.toLowerCase().includes(t))}function ee(e){let n=e.data.vars.map((t)=>({...t}));for(let t of e.ops){if(t.kind!=="set"&&t.kind!=="unset"||t.file)continue;let s=n.find((r)=>r.name===t.name);if(t.kind==="unset"){if(s)s.pending="unset"}else if(s)s.pending="set",s.newValue=t.value;else n.push({name:t.name,value:void 0,by:"envhound",managed:!0,pending:"new",newValue:t.value})}return n.sort((t,s)=>t.name.localeCompare(s.name)),n.filter((t)=>Tn(e,t.name,t.newValue??t.value))}function ne(e){let n=e.data.path.map((t)=>({...t}));for(let t of e.ops)if(t.kind==="path-remove"){for(let s of n)if(s.dir===t.dir)s.pending="remove"}else if(t.kind==="path-add"){let s={dir:t.dir,by:"envhound",managed:!0,exists:!0,pending:"add"};if(t.position==="front")n.unshift(s);else n.push(s)}return n}function te(e,n){let s=(e.data.dotenv.find((r)=>r.file===n)?.rows??[]).map((r)=>({...r}));for(let r of e.ops){if(r.kind!=="set"&&r.kind!=="unset"||r.file!==n)continue;let i=s.find((a)=>a.key===r.name);if(r.kind==="unset"){if(i)i.pending="unset"}else if(i)i.pending="set",i.newValue=r.value;else s.push({key:r.name,value:"",line:0,status:"new",count:0,pending:"new",newValue:r.value})}return s.filter((r)=>Tn(e,r.key,r.newValue??r.value))}function Vt(e){let n=K(e);return n.kind==="vars"?ee(e).length:n.kind==="path"?ne(e).length:te(e,n.file).length}function de(e){if(e.kind==="set"||e.kind==="unset")return`${e.file??"shell"}:${e.name}`;return`path:${e.dir}`}function X(e,n,t){let s=e.ops.filter((r)=>de(r)!==de(n));return{...e,ops:[...s,n],message:{text:t}}}function $e(e,n,t){return{...e,ops:e.ops.filter((s)=>de(s)!==de(n)),message:{text:t}}}var Ct=/^[A-Za-z_][A-Za-z0-9_]*$/,It=/^[A-Za-z_][A-Za-z0-9_.-]*$/;function jt(e,n){let t=e.trim().replace(/^~(?=\/|$)/,n);return Nt(t)?En(t):En(process.cwd(),t)}var j=(e,n)=>v(n,e.data.home);function Kt(e){let n=e.prompt,t={...e,prompt:void 0};switch(n.for.kind){case"filter":return{...t,filter:n.value};case"edit":case"new-value":{let{name:s,file:r}=n.for;return X(t,{kind:"set",name:s,value:n.value,file:r},`${s} will be set${r?` in ${j(e,r)}`:""}`)}case"new-name":{let s=n.value.trim(),r=n.for.file;if(!(r?It:Ct).test(s))return{...e,message:{text:`'${s}' is not a valid ${r?"key":"variable name"}`,error:!0}};if(s==="PATH"&&!r)return{...t,message:{text:"use the PATH tab to change PATH",error:!0}};let i=r?e.data.dotenv.find((a)=>a.file===r)?.rows.find((a)=>a.key===s)?.value:e.data.vars.find((a)=>a.name===s)?.value;return{...t,prompt:{for:{kind:"new-value",name:s,file:r},label:`${s}=`,value:i??"",secret:M(s)}}}case"path-add":{if(!n.value.trim())return t;let s=jt(n.value,e.data.home);if(e.data.path.some((r)=>r.dir===s))return{...t,message:{text:`${j(e,s)} is already in PATH`,error:!0}};return X(t,{kind:"path-add",dir:s,position:n.for.position},`${j(e,s)} will be added`)}}}function Wt(e,n){let t=e.prompt,s=(r,i=!1)=>{let a={...e,prompt:{...t,value:r,reveal:i}};return t.for.kind==="filter"?{...a,filter:r,cursor:{...e.cursor,[xe(K(e))]:0}}:a};if(n.name==="escape")return{...e,prompt:void 0,filter:t.for.kind==="filter"?"":e.filter};if(n.name==="return"||n.name==="enter")return Kt(e);if(n.name==="backspace")return s([...t.value].slice(0,-1).join(""));if(n.ctrl&&n.name==="u")return s("");if(n.ch&&!n.ctrl)return s(t.value+n.ch,!0);return e}function A(e,n){let t=Math.max(0,Vt(e)-1);return{...e,cursor:{...e.cursor,[xe(K(e))]:Math.min(t,Math.max(0,n))}}}function On(e,n,t,s,r){switch(n.ch){case"/":return[{...e,prompt:{for:{kind:"filter"},label:"filter: ",value:e.filter}}];case"n":return[{...e,prompt:{for:{kind:"new-name",file:s},label:s?"new key: ":"new variable name: ",value:""}}];case"s":return[{...e,showSecrets:!e.showSecrets}]}if(!t)return[e];if(n.ch==="e"||n.name==="return"||n.name==="enter")return[{...e,prompt:{for:{kind:"edit",name:t.name,file:s},label:`${t.name}=`,value:t.value,secret:M(t.name)}}];if(n.ch==="d")return[r()];if(n.ch==="o"){if(!t.source)return[{...e,message:{text:s?"not in the file yet: write first":"not set by a startup file, nothing to open",error:!0}}];return[e,{kind:"open",at:t.source}]}return[e]}function Ft(e,n){let t=ee(e)[k(e)],s=t&&{name:t.name,value:t.newValue??t.value??"",source:t.source};return On(e,n,s,void 0,()=>{let r=t,i={kind:"unset",name:r.name};if(r.pending==="new"||r.pending==="unset"||r.pending==="set"&&!r.managed)return $e(e,i,`change to ${r.name} dropped`);if(r.managed)return X(e,i,`${r.name} will be removed from envhound's file`);return{...e,message:{text:Dn(r.name,r),error:!0}}})}function Bt(e,n,t){let s=te(e,t)[k(e)],r=s&&{name:s.key,value:s.newValue??s.value,source:s.line?{file:t,line:s.line}:void 0};return On(e,n,r,t,()=>{let i=s,a={kind:"unset",name:i.key,file:t};if(i.pending)return $e(e,a,`change to ${i.key} dropped`);return X(e,a,`${i.key} will be deleted from ${j(e,t)}`)})}function zt(e,n){if(n.ch==="a"||n.ch==="A"){let s=n.ch==="a"?"front":"back";return[{...e,prompt:{for:{kind:"path-add",position:s},label:`add to the ${s} of PATH: `,value:""}}]}let t=ne(e)[k(e)];if(!t)return[e];if(n.ch==="d"){let s={kind:"path-remove",dir:t.dir};if(t.pending)return[$e(e,s,`change to ${j(e,t.dir)} dropped`)];if(t.managed)return[X(e,s,`${j(e,t.dir)} will be removed`)];return[{...e,message:{text:Dn(j(e,t.dir),t),error:!0}}]}if(n.ch==="o"){if(!t.source)return[{...e,message:{text:"not added by a startup file, nothing to open",error:!0}}];return[e,{kind:"open",at:t.source}]}return[e]}function Dn(e,n){if(n.source)return`${e} comes from ${n.by}, not envhound: press o to open that line`;return`${e} ${n.by==="(inherited)"?"comes from the program that started this shell":"is set by login or bash itself"}`}function Pn(e,n){if(n.ctrl&&n.name==="c")return[e,{kind:"quit"}];if(e.prompt)return[Wt(e,n)];let t={...e,message:void 0,confirmQuit:!1};if(e.help)return[{...t,help:!1}];if(n.ch==="q"||n.name==="escape"&&!e.filter){if(e.ops.length&&!e.confirmQuit)return[{...t,confirmQuit:!0,message:{text:`${e.ops.length} staged change(s) will be lost: q again to quit, w to write`,error:!0}}];return[t,{kind:"quit"}]}if(n.name==="escape")return[{...t,filter:""}];let s=fe(t).length;if(n.name==="tab")return[{...t,tab:(t.tab+1)%s}];if(n.ch&&/^[1-9]$/.test(n.ch)&&Number(n.ch)<=s)return[{...t,tab:Number(n.ch)-1}];if(n.ch==="?")return[{...t,help:!0}];if(n.ch==="w")return t.ops.length?[t,{kind:"write"}]:[{...t,message:{text:"nothing staged yet"}}];if(n.ch==="u"){if(!t.ops.length)return[{...t,message:{text:"nothing to undo"}}];return[A({...t,ops:t.ops.slice(0,-1),message:{text:"undone"}},k(t))]}let r=k(t);switch(n.name){case"up":return[A(t,r-1)];case"down":return[A(t,r+1)];case"pageup":return[A(t,r-t.pageSize)];case"pagedown":return[A(t,r+t.pageSize)];case"home":return[A(t,0)];case"end":return[A(t,1/0)]}if(n.ch==="k")return[A(t,r-1)];if(n.ch==="j")return[A(t,r+1)];if(n.ch==="g")return[A(t,0)];if(n.ch==="G")return[A(t,1/0)];let i=K(t),[a,l]=i.kind==="vars"?Ft(t,n):i.kind==="path"?zt(t,n):Bt(t,n,i.file);return[A(a,k(a)),l]}import{basename as Ut,relative as qt}from"node:path";var W=(e,n)=>(t)=>n&&t?`\x1B[${e}m${t}\x1B[0m`:t;function Yt(e){return{bold:W("1",e),dim:W("2",e),inverse:W("7",e),red:W("31",e),green:W("32",e),yellow:W("33",e),cyan:W("36",e)}}function y(e,n){if(n<=0)return"";let t=e.replace(/[\x00-\x1f\x7f]/g," "),s=[...t];return s.length>n?s.slice(0,n-1).join("")+"…":t.padEnd(n+(t.length-s.length))}function Z(e,n){return[...e].length>n?y(e,n):e}function Se(e,n){let t=[...e];return t.length>n?"…"+t.slice(t.length-n+1).join(""):y(e,n)}var Zt={vars:"↑↓ move / filter enter edit n new d unset o open u undo w write tab next ? help q quit",path:"↑↓ move a add front A add end d remove o open u undo w write tab next ? help q quit",dotenv:"↑↓ move / filter enter edit n new d delete o open u undo w write tab next ? help q quit"},Qt=["envhound edit: stage changes, then w to review them as a diff and write.","","Variables tab"," enter / e edit the value (envhound's line comes last, so it wins in login shells)"," n new variable"," d unset a variable envhound set, or drop a staged change"," o open your editor at the line that sets it, then reload"," / filter by name or value; esc clears"," s show or hide secret values","","PATH tab"," a / A add a directory at the front / end of PATH"," d remove a directory envhound added, or drop a staged change"," o open the line that adds it","",".env tabs (envhound edit FILE...; ./.env is opened when it exists)"," enter / e edit a value: only that line changes, comments and quoting are kept"," n / d add a key / delete every line for a key"," o open the file at the key's line","","Anywhere"," u undo · w write · tab or 1-9 switch tabs · q or esc quit · ctrl-c quit now","","Press any key to go back."];function ke(e,n,t){return Math.max(0,Math.min(e-Math.floor(t/2),n-t))}function Gt(e,n){if(e.kind==="vars")return"Variables";if(e.kind==="path")return"PATH";let t=qt(process.cwd(),e.file),s=t&&!t.startsWith("..")?t:v(e.file,n);return s.length>24?Ut(e.file):s}function Jt(e,n,t){let r=fe(e).map((f,o)=>` ${o+1} ${Gt(f,e.data.home)} `),i=r.map((f,o)=>o===e.tab?t.inverse(f):t.dim(f)).join(" "),a=r.join(" ").length,l=e.ops.length?`${e.ops.length} staged · w to write`:"nothing staged",c=e.ops.length?t.yellow(l):t.dim(l);if(15+a+3+l.length<=n)return`${t.bold("envhound edit")} ${i} ${c}`;if(a+1+l.length<=n)return`${i} ${c}`;if(a<=n)return i;return t.inverse(y(r[e.tab].trim(),n))}function Rn(e,n,t,s){let r=Yt(s.color),i=[Jt(e,n,r),r.dim("─".repeat(Math.max(0,n)))],a=K(e),l=[r.dim(y(Zt[a.kind],n))];if(e.prompt){let u=e.prompt.secret&&!e.showSecrets,d=[...e.prompt.value].length,h=u?r.dim(` (${d} character${d===1?"":"s"})`):"",p=u?` (${d} characters)`.length:0,m=[...e.prompt.value].at(-1)??"",g=!u?e.prompt.value:e.prompt.reveal?"•".repeat(d-1)+m:"•".repeat(d),b=Math.max(1,n-e.prompt.label.length-1-p),x=[...g],w=x.length>b?"…"+x.slice(x.length-b+1).join(""):g;l.push(r.bold(e.prompt.label)+w+(s.color?"\x1B[7m \x1B[0m":"_")+h)}else if(e.message)l.push(e.message.error?r.red(y(e.message.text,n)):r.green(y(e.message.text,n)));else l.push(r.dim(y(e.filter?`filter: ${e.filter} (esc clears)`:"",n)));let c=Math.max(0,t-i.length-l.length-2),f,o;if(e.help)f=Qt.map((u)=>y(u,n)),o="";else if(a.kind==="vars")f=Xt(e,n,c,r),o=er(e);else if(a.kind==="path")f=nr(e,n,c,r),o=tr(e);else f=sr(e,a.file,n,c,r),o=ir(e,a.file);for(let u=0;u<c;u++)i.push(f[u]??"");return i.push(r.dim("─".repeat(Math.max(0,n)))),i.push(y(o,n)),i.push(...l),i.slice(0,t)}function Y(e,n,t){if(n===void 0)return"";let s=n.replace(/\n/g,"\\n");return M(e)&&!t.showSecrets?"********":v(s,t.data.home)}function Mn(e,n,t,s){if(n.pending==="unset")return`${Y(e,n.value,t)} (${s})`;if(n.pending==="new")return Y(e,n.newValue,t);if(n.pending==="set")return`${Y(e,n.value,t)} → ${Y(e,n.newValue,t)}`;return Y(e,n.value,t)}var Ee=(e)=>e==="new"||e==="add"?"+":e==="unset"||e==="remove"?"-":e?"*":" ";function Xt(e,n,t,s){let r=ee(e);if(!r.length)return[s.dim(e.filter?"no variable matches the filter":"no variables")];let i=Math.min(28,Math.max(8,...r.map((d)=>d.name.length))),a=Math.min(32,Math.max(6,...r.map((d)=>d.by.length))),l=Math.max(10,n-i-a-6),c=t-1,f=k(e),o=ke(f,r.length,c),u=[s.bold(Z(` ${y("NAME",i)} ${y("SET BY",a)} VALUE`,n))];return r.slice(o,o+c).forEach((d,h)=>{let p=Z(`${Ee(d.pending)} ${y(d.name,i)} ${Se(d.by,a)} ${y(Mn(d.name,d,e,"unset"),l)}`,n),m=d.pending==="unset"?s.red:d.pending?s.yellow:d.managed?s.cyan:d.by.startsWith("(")?s.dim:(g)=>g;u.push(o+h===f?s.inverse(p):m(p))}),u}function er(e){let n=ee(e)[k(e)];if(!n)return"";let t=n.managed?"set by envhound":n.source?`set at ${n.by}`:n.by==="(inherited)"?"inherited from the program that started this shell":"set by login or bash";return`${n.name}: ${t}${n.pending?`, staged: ${n.pending}`:""}`}function nr(e,n,t,s){let r=ne(e);if(!r.length)return[s.dim("PATH is empty")];let i=Math.min(60,Math.max(9,...r.map((u)=>v(u.dir,e.data.home).length))),a=Math.min(32,Math.max(8,...r.map((u)=>u.by.length))),l=t-1,c=k(e),f=ke(c,r.length,l),o=[s.bold(Z(` # ${y("DIRECTORY",i)} ${y("ADDED BY",a)}`,n))];return r.slice(f,f+l).forEach((u,d)=>{let h=f+d,p=u.pending?`staged: ${u.pending}`:u.duplicateOf?`duplicate of #${u.duplicateOf}`:!u.exists?"missing":"",m=Z(`${Ee(u.pending)} ${String(h+1).padStart(2)} ${Se(v(u.dir,e.data.home),i)} ${Se(u.by,a)} ${y(p,Math.max(0,n-i-a-10))}`,n),g=u.pending==="remove"?s.red:u.pending?s.yellow:p?s.red:u.managed?s.cyan:u.by.startsWith("(")?s.dim:(b)=>b;o.push(h===c?s.inverse(m):g(m))}),o}function tr(e){let n=ne(e)[k(e)];if(!n)return"";let t=n.managed?"added by envhound":n.source?`added at ${n.by}`:n.by==="(initial)"?"in the initial PATH bash starts with":"inherited from the program that started this shell";return`${v(n.dir,e.data.home)}: ${t}`}function rr(e,n){if(e.pending)return`staged: ${e.pending==="unset"?"delete":e.pending}`;if(e.problem)return e.problem;if(e.count>1)return`defined ${e.count} times`;if(e.status==="conflict")return`shell has ${Y(e.key,e.current,n)}`;return e.status==="same"?"same as shell":""}function sr(e,n,t,s,r){let i=te(e,n),a=e.data.dotenv.find((p)=>p.file===n);if(!i.length){if(e.filter)return[r.dim("no key matches the filter")];return[r.dim(a?.exists?"no keys in this file: n adds one":"this file does not exist yet: n adds a key, w creates it")]}let l=Math.min(28,Math.max(3,...i.map((p)=>p.key.length))),c=Math.max(10,Math.min(48,t-l-32)),f=Math.max(0,t-l-c-12),o=s-1,u=k(e),d=ke(u,i.length,o),h=[r.bold(Z(` ${y("KEY",l)} LINE ${y("VALUE",c)} `,t))];return i.slice(d,d+o).forEach((p,m)=>{let g=rr(p,e),b=p.line?String(p.line).padStart(4):" ",x=Z(`${Ee(p.pending)} ${y(p.key,l)} ${b} ${y(Mn(p.key,p,e,"delete"),c)} ${y(g,f)}`,t),w=p.pending==="unset"?r.red:p.pending?r.yellow:p.problem||p.count>1?r.red:p.status==="conflict"?r.yellow:p.status==="same"?r.dim:(R)=>R;h.push(d+m===u?r.inverse(x):w(x))}),h}function ir(e,n){let t=e.data.dotenv.find((c)=>c.file===n),s=v(n,e.data.home),r=t?.problems?` · ${t.problems} problem(s), see envhound dotenv`:"",i=te(e,n)[k(e)];if(!i)return`${s}${t?.exists?"":" (new file)"}${r}`;let a=i.line?`line ${i.line} of ${s}`:`new in ${s}`,l=i.status==="conflict"?": loaders such as dotenv keep the shell's value":"";return`${i.key}: ${a}${l}${r}`}var lr="\x1B[?1049h\x1B[?25l",cr="\x1B[?25h\x1B[?1049l";function ur(e,n){let[t="vi",...s]=e.trim().split(/\s+/),r=t.split("/").pop()??t;if(/^(code|code-insiders|codium|cursor|windsurf)$/.test(r))return[t,[...s.filter((i)=>i!=="--wait"),"--wait","-g",`${n.file}:${n.line}`]];return[t,[...s,`+${n.line}`,n.file]]}function Ln(e,n){let t=process.stdin,s=process.stdout,r=An(e(),{showSecrets:n.showSecrets,tab:n.tab});return new Promise((i,a)=>{let l,c=()=>{let p=Math.max(1,(s.columns||80)-1),m=s.rows||24;r={...r,pageSize:Math.max(1,m-8)};let g=Rn(r,p,m,{color:n.color});if(s.write("\x1B[H"+g.map((b)=>b+"\x1B[K").join(`\r
69
+ `)+"\x1B[J"),clearTimeout(l),r.prompt?.reveal)l=setTimeout(()=>{if(r.prompt)r={...r,prompt:{...r.prompt,reveal:!1}};c()},1000)},f=()=>{s.write(lr),t.setRawMode(!0),t.resume(),c()},o=()=>{clearTimeout(l),t.setRawMode(!1),t.pause(),s.write(cr)},u=(p)=>{t.off("keypress",h),s.off("resize",c),o(),i(p)},d=(p)=>{o();let[m,g]=ur(process.env.VISUAL||process.env.EDITOR||"vi",p),b=or(m,g,{stdio:"inherit"});if(f(),b.error)r={...r,message:{text:`could not run ${m}: ${b.error.message}`,error:!0}};else s.write("\x1B[H\x1B[Jreloading…"),r={...r,data:e(),message:{text:"reloaded after editing"}};c()},h=(p,m)=>{try{let g=p&&!m?.ctrl&&!m?.meta&&[...p].length===1&&p>=" "&&p!==""?p:void 0,b={name:m?.name,ch:g,ctrl:m?.ctrl},[x,w]=Pn(r,b);if(r=x,w?.kind==="quit")return u(void 0);if(w?.kind==="write")return u(r.ops);if(w?.kind==="open")return d(w.at);c()}catch(g){t.off("keypress",h),o(),a(g)}};ar(t),t.on("keypress",h),s.on("resize",c),f()})}import{spawn as dr}from"node:child_process";import{mkdirSync as fr,readFileSync as pr,realpathSync as hr,renameSync as mr,writeFileSync as gr}from"node:fs";import{dirname as vr}from"node:path";var br="michelsalib/envhound",yr="https://registry.npmjs.org/envhound/latest",wr=604800000;function Oe(e){let n=e.replaceAll("\\","/");if(n.includes("/_npx/"))return"npx";if(n.includes("/bunx-"))return"bunx";if(n.includes("/.bun/install/global/"))return"bun";if(n.includes("/node_modules/"))return"npm";if(/\.[cm]?[jt]s$/.test(n))return"dev";return"standalone"}function De(e){switch(e){case"npx":return"npx envhound@latest";case"bunx":return"bunx envhound@latest";case"bun":return"bun add -g envhound@latest";case"npm":return"npm install -g envhound@latest";case"standalone":return"envhound upgrade";case"dev":return"git pull && bun run build"}}function xr(e,n){let t=(i)=>(i.match(/^v?(\d+)\.(\d+)\.(\d+)/)??[]).slice(1).map(Number),[s,r]=[t(e),t(n)];if(s.length!==3||r.length!==3)return!1;for(let i=0;i<3;i++)if(s[i]!==r[i])return s[i]>r[i];return!1}function $r(e,n,t){let s=(r)=>r===void 0||t-r>=wr||r>t;return{check:s(e.checkedAt),notify:e.latest!==void 0&&xr(e.latest,n)&&s(e.notifiedAt)}}function Ae(e){try{let n=JSON.parse(pr(e,"utf8"));return typeof n==="object"&&n?n:{}}catch{return{}}}function Te(e,n){fr(vr(e),{recursive:!0});let t=`${e}.${process.pid}.tmp`;gr(t,JSON.stringify(n)+`
70
+ `),mr(t,e)}function _n(e,n,t,s=Date.now()){try{let r=Oe(hr(n));if(r==="dev")return;let i=Ae(t),{check:a,notify:l}=$r(i,e,s);if(a)Te(t,{...i,checkedAt:s}),dr(process.execPath,[n,"__update-check",t],{detached:!0,stdio:"ignore"}).unref();if(!l)return;return Te(t,{...Ae(t),notifiedAt:s}),`envhound ${i.latest} is available (you have ${e}). Update with: ${De(r)}`}catch{return}}async function Hn(e){let n=await fetch(yr,{signal:AbortSignal.timeout(1e4),headers:{accept:"application/json"}});if(!n.ok)return;let{version:t}=await n.json();if(typeof t!=="string")return;Te(e,{...Ae(e),checkedAt:Date.now(),latest:t})}function Nn(e){return`${e.ENVHOUND_BASE_URL??`https://github.com/${br}/releases/latest/download`}/install.sh`}var Dr="find which startup file sets each environment variable, and change it",Vn=`Usage:
71
71
  envhound [list] every exported variable, with the startup file:line that sets it
72
72
  envhound blame VAR every startup file:line that assigns VAR, in order
73
73
  (for PATH: what each step added or removed)
@@ -114,20 +114,20 @@ so it only sees what startup files do. That runs your startup files once.
114
114
 
115
115
  Once a week envhound asks npm in the background whether a newer version exists,
116
116
  and says so after a command. ENVHOUND_NO_UPDATE_CHECK=1 turns this off.
117
- `,Re,Pr=/^[A-Za-z_][A-Za-z0-9_]*$/,Rr=/^[A-Za-z_][A-Za-z0-9_.-]*$/;async function Mr(e){if(e[0]==="__complete"){for(let d of He(e.slice(1),process.env))console.log(d);return 0}if(e[0]==="__update-check")return await Hn(e[1]),0;let{values:n,positionals:t}=Dr({args:e,allowPositionals:!0,options:{json:{type:"boolean"},"show-secrets":{type:"boolean"},home:{type:"string"},file:{type:"string",short:"f"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},append:{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),s=process.stdout.isTTY,r=()=>on([`envhound ${Q.version}`,"","find which startup file","sets each environment","variable, and change it"],{color:!process.env.NO_COLOR,truecolor:/^(truecolor|24bit)$/i.test(process.env.COLORTERM??""),columns:process.stdout.columns});if(n.help)return process.stdout.write(s?`${r()}
118
- ${Vn}`:`envhound ${Q.version}: ${Tr}
117
+ `,Re,Pr=/^[A-Za-z_][A-Za-z0-9_]*$/,Rr=/^[A-Za-z_][A-Za-z0-9_.-]*$/;async function Mr(e){if(e[0]==="__complete"){for(let u of He(e.slice(1),process.env))console.log(u);return 0}if(e[0]==="__update-check")return await Hn(e[1]),0;let{values:n,positionals:t}=Or({args:e,allowPositionals:!0,options:{json:{type:"boolean"},"show-secrets":{type:"boolean"},home:{type:"string"},file:{type:"string",short:"f"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},append:{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),s=process.stdout.isTTY,r=()=>on([`envhound ${Q.version}`,"","find which startup file","sets each environment","variable, and change it"],{color:!process.env.NO_COLOR,truecolor:/^(truecolor|24bit)$/i.test(process.env.COLORTERM??""),columns:process.stdout.columns});if(n.help)return process.stdout.write(s?`${r()}
118
+ ${Vn}`:`envhound ${Q.version}: ${Dr}
119
119
 
120
120
  ${Vn}`),0;if(n.version)return process.stdout.write(s?r():`${Q.version}
121
- `),0;let[i="list",...a]=t,l=process.env;if(process.stderr.isTTY&&n.home===void 0&&!n.json&&!l.CI&&!l.ENVHOUND_NO_UPDATE_CHECK&&!l.NO_UPDATE_NOTIFIER&&!["completion","upgrade"].includes(i))Re=_n(Q.version,process.argv[1],le(H(l.HOME??""),l,!1).updateState);let c=process.stdout.isTTY,f={home:H(n.home??process.env.HOME??""),color:c&&!process.env.NO_COLOR,width:c?process.stdout.columns:void 0,showSecrets:n["show-secrets"]??!1},o=(d,u)=>console.log(n.json?JSON.stringify(u(),null,2):d());switch(i){case"list":{let d=re(_({home:n.home}),process.env);return o(()=>Ue(d,f),()=>Ze(d,f)),0}case"blame":{let d=a[0];if(!d)return D("blame needs a variable name, e.g. envhound blame PATH");let u=me(_({home:n.home}),d,process.env);return o(()=>qe(u,f),()=>Qe(u,f)),0}case"path":{let[d,...u]=a;if(d==="add"||d==="remove"){if(!u.length)return D(`path ${d} needs a directory`);let p=u.map((m)=>d==="add"?{kind:"path-add",dir:H(m),position:n.append?"back":"front"}:{kind:"path-remove",dir:H(m)});if(n.file)return D("--file only applies to set and unset");return Pe(p,n,f)}if(d!==void 0)return D(`unknown path command '${d}' (add, remove)`);let h=z(_({home:n.home}),process.env.PATH??"");return o(()=>Ye(h,f),()=>h),0}case"set":case"unset":{if(!a.length)return D(i==="set"?"set needs NAME=value":"unset needs a NAME");let d=n.file?Rr:Pr,u=[];for(let h of a){let p=h.indexOf("="),m=i==="set"?h.slice(0,p):h;if(i==="set"&&p<0)return D(`expected NAME=value, got '${h}'`);if(!d.test(m))return D(`'${m}' is not a valid variable name`);if(m==="PATH"&&!n.file)return D("use 'envhound path add DIR' to change PATH");let g=n.file&&H(n.file);u.push(i==="set"?{kind:"set",name:m,value:h.slice(p+1),file:g}:{kind:"unset",name:m,file:g})}return Pe(u,n,f)}case"edit":{if(!process.stdin.isTTY||!process.stdout.isTTY)return D("edit needs a terminal");let d=le(f.home,process.env,n.home!==void 0),u=[...a,...n.file?[n.file]:[]].map((g)=>H(g)),h=u.length?[...new Set(u)]:kr(".env")?[H(".env")]:[];process.stderr.write("tracing startup files…\r");let m=await Ln(()=>kn(_({home:n.home}),process.env,d,h),{...f,tab:u.length?2:0});if(process.stderr.write("\x1B[K"),!m?.length)return 0;return Pe(m,n,f)}case"dotenv":{let d=a.length?a:[".env"],u,h=d.map((m)=>{let g=H(m),y=V(Cn(g,"utf8")),w=oe(y,process.env),A=w.filter((P)=>P.status==="conflict");if(A.length)u??=_({home:n.home});let R=Object.fromEntries(A.map((P)=>[P.key,me(u,P.key,process.env).assignments.at(-1)]));return{file:g,keys:w,problems:ie(y),shellOrigin:R}}),p=h.map((m)=>({...m,keys:m.keys.map((g)=>({...g,value:k(g.key,g.value,f),current:k(g.key,g.current,f)}))}));return o(()=>h.map((m)=>Ge(m,f)).join(`
121
+ `),0;let[i="list",...a]=t,l=process.env;if(process.stderr.isTTY&&n.home===void 0&&!n.json&&!l.CI&&!l.ENVHOUND_NO_UPDATE_CHECK&&!l.NO_UPDATE_NOTIFIER&&!["completion","upgrade"].includes(i))Re=_n(Q.version,process.argv[1],le(H(l.HOME??""),l,!1).updateState);let c=process.stdout.isTTY,f={home:H(n.home??process.env.HOME??""),color:c&&!process.env.NO_COLOR,width:c?process.stdout.columns:void 0,showSecrets:n["show-secrets"]??!1},o=(u,d)=>console.log(n.json?JSON.stringify(d(),null,2):u());switch(i){case"list":{let u=re(_({home:n.home}),process.env);return o(()=>Ue(u,f),()=>Ze(u,f)),0}case"blame":{let u=a[0];if(!u)return O("blame needs a variable name, e.g. envhound blame PATH");let d=me(_({home:n.home}),u,process.env);return o(()=>qe(d,f),()=>Qe(d,f)),0}case"path":{let[u,...d]=a;if(u==="add"||u==="remove"){if(!d.length)return O(`path ${u} needs a directory`);let p=d.map((m)=>u==="add"?{kind:"path-add",dir:H(m),position:n.append?"back":"front"}:{kind:"path-remove",dir:H(m)});if(n.file)return O("--file only applies to set and unset");return Pe(p,n,f)}if(u!==void 0)return O(`unknown path command '${u}' (add, remove)`);let h=z(_({home:n.home}),process.env.PATH??"");return o(()=>Ye(h,f),()=>h),0}case"set":case"unset":{if(!a.length)return O(i==="set"?"set needs NAME=value":"unset needs a NAME");let u=n.file?Rr:Pr,d=[];for(let h of a){let p=h.indexOf("="),m=i==="set"?h.slice(0,p):h;if(i==="set"&&p<0)return O(`expected NAME=value, got '${h}'`);if(!u.test(m))return O(`'${m}' is not a valid variable name`);if(m==="PATH"&&!n.file)return O("use 'envhound path add DIR' to change PATH");let g=n.file&&H(n.file);d.push(i==="set"?{kind:"set",name:m,value:h.slice(p+1),file:g}:{kind:"unset",name:m,file:g})}return Pe(d,n,f)}case"edit":{if(!process.stdin.isTTY||!process.stdout.isTTY)return O("edit needs a terminal");let u=le(f.home,process.env,n.home!==void 0),d=[...a,...n.file?[n.file]:[]].map((g)=>H(g)),h=d.length?[...new Set(d)]:kr(".env")?[H(".env")]:[];process.stderr.write("tracing startup files…\r");let m=await Ln(()=>kn(_({home:n.home}),process.env,u,h),{...f,tab:d.length?2:0});if(process.stderr.write("\x1B[K"),!m?.length)return 0;return Pe(m,n,f)}case"dotenv":{let u=a.length?a:[".env"],d,h=u.map((m)=>{let g=H(m),b=V(Cn(g,"utf8")),x=oe(b,process.env),w=x.filter((P)=>P.status==="conflict");if(w.length)d??=_({home:n.home});let R=Object.fromEntries(w.map((P)=>[P.key,me(d,P.key,process.env).assignments.at(-1)]));return{file:g,keys:x,problems:ie(b),shellOrigin:R}}),p=h.map((m)=>({...m,keys:m.keys.map((g)=>({...g,value:E(g.key,g.value,f),current:E(g.key,g.current,f)}))}));return o(()=>h.map((m)=>Ge(m,f)).join(`
122
122
 
123
- `),()=>p),h.some((m)=>m.problems.length)?1:0}case"completion":{let d=a[0];if(!se.includes(d))return D(`completion needs a shell: ${se.join(", ")}`);return process.stdout.write(Ne(d)),0}case"upgrade":return Lr();default:return D(`unknown command '${i}'`)}}async function Pe(e,n,t){let s=q(t),r=le(t.home,process.env,n.home!==void 0),i=e.filter((u)=>!(("file"in u)&&u.file)),a=[...new Set(e.flatMap((u)=>("file"in u)&&u.file?[u.file]:[]))],l=[...i.length?[bn(i,r,_({home:n.home}))]:[],...a.map((u)=>yn(e.filter((h)=>("file"in h)&&h.file===u),u,process.env))],c={changes:l.flatMap((u)=>u.changes),notes:l.flatMap((u)=>u.notes)};for(let u of c.notes)console.log(s.yellow(`note: ${u}`));if(!c.changes.length)return console.log("Nothing to change."),0;if(c.notes.length)console.log();if(console.log(c.changes.map((u)=>Xe(u,t)).join(`
123
+ `),()=>p),h.some((m)=>m.problems.length)?1:0}case"completion":{let u=a[0];if(!se.includes(u))return O(`completion needs a shell: ${se.join(", ")}`);return process.stdout.write(Ne(u)),0}case"upgrade":return Lr();default:return O(`unknown command '${i}'`)}}async function Pe(e,n,t){let s=q(t),r=le(t.home,process.env,n.home!==void 0),i=e.filter((d)=>!(("file"in d)&&d.file)),a=[...new Set(e.flatMap((d)=>("file"in d)&&d.file?[d.file]:[]))],l=[...i.length?[bn(i,r,_({home:n.home}))]:[],...a.map((d)=>yn(e.filter((h)=>("file"in h)&&h.file===d),d,process.env))],c={changes:l.flatMap((d)=>d.changes),notes:l.flatMap((d)=>d.notes)};for(let d of c.notes)console.log(s.yellow(`note: ${d}`));if(!c.changes.length)return console.log("Nothing to change."),0;if(c.notes.length)console.log();if(console.log(c.changes.map((d)=>Xe(d,t)).join(`
124
124
 
125
125
  `)+`
126
- `),n["dry-run"])return 0;if(!n.yes){if(!process.stdin.isTTY)return console.error("envhound: not a terminal, so envhound can't ask; pass --yes to write without asking"),1;if(!await en("Write these changes? [y/N] "))return console.log("Nothing written."),1}let f=nn(c.changes,r.backups);for(let u of c.changes)console.log(`wrote ${v(u.path,t.home)}`);if(f.length)console.log(s.dim(`backups in ${v(r.backups,t.home)}`));if(!i.length)return 0;let o=wn(i,_({home:n.home}),r);for(let u of o)console.log(u.ok?s.green(`✓ ${u.message}`):s.yellow(`! ${u.message}`));let d=xn(i);if(d.length)console.log(s.dim(`
126
+ `),n["dry-run"])return 0;if(!n.yes){if(!process.stdin.isTTY)return console.error("envhound: not a terminal, so envhound can't ask; pass --yes to write without asking"),1;if(!await en("Write these changes? [y/N] "))return console.log("Nothing written."),1}let f=nn(c.changes,r.backups);for(let d of c.changes)console.log(`wrote ${v(d.path,t.home)}`);if(f.length)console.log(s.dim(`backups in ${v(r.backups,t.home)}`));if(!i.length)return 0;let o=wn(i,_({home:n.home}),r);for(let d of o)console.log(d.ok?s.green(`✓ ${d.message}`):s.yellow(`! ${d.message}`));let u=xn(i);if(u.length)console.log(s.dim(`
127
127
  This shell is unchanged. To apply it here too, run:
128
- `)+d.map((u)=>` ${u}`).join(`
129
- `));return o.every((u)=>u.ok)?0:1}async function Lr(){let e=Er(process.argv[1]),n=De(e);if(n!=="standalone")return console.log(`This envhound was not installed with install.sh. Update it with:
130
- ${Te(n)}`),0;let t=Nn(process.env),s;try{if(t.startsWith("file:"))s=Cn(Or(t),"utf8");else{let i=await fetch(t,{signal:AbortSignal.timeout(30000)});if(!i.ok)throw Error(`HTTP ${i.status}`);s=await i.text()}}catch(i){return console.error(`envhound: could not download ${t}: ${i instanceof Error?i.message:i}`),1}return Sr("sh",["-s"],{input:s,stdio:["pipe","inherit","inherit"],env:{...process.env,ENVHOUND_INSTALL_DIR:Ar(e)}}).status??1}function D(e){return console.error(`envhound: ${e}
128
+ `)+u.map((d)=>` ${d}`).join(`
129
+ `));return o.every((d)=>d.ok)?0:1}async function Lr(){let e=Er(process.argv[1]),n=Oe(e);if(n!=="standalone")return console.log(`This envhound was not installed with install.sh. Update it with:
130
+ ${De(n)}`),0;let t=Nn(process.env),s;try{if(t.startsWith("file:"))s=Cn(Tr(t),"utf8");else{let i=await fetch(t,{signal:AbortSignal.timeout(30000)});if(!i.ok)throw Error(`HTTP ${i.status}`);s=await i.text()}}catch(i){return console.error(`envhound: could not download ${t}: ${i instanceof Error?i.message:i}`),1}return Sr("sh",["-s"],{input:s,stdio:["pipe","inherit","inherit"],env:{...process.env,ENVHOUND_INSTALL_DIR:Ar(e)}}).status??1}function O(e){return console.error(`envhound: ${e}
131
131
  Run 'envhound --help' for usage.`),2}Mr(process.argv.slice(2)).then((e)=>{if(process.exitCode=e,Re)process.stderr.write(`
132
132
  ${Re}
133
133
  `)},(e)=>{console.error(`envhound: ${e instanceof Error?e.message:e}`),process.exitCode=1});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envhound",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Find which startup file sets each environment variable, and audit your PATH",
5
5
  "keywords": [
6
6
  "environment",