envhound 0.1.1 → 0.1.2
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 +6 -2
- package/dist/envhound.js +40 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
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">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">envhound</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">Find which startup file sets each environment variable, audit your <code>PATH</code>, and change them safely.</p>
|
|
4
8
|
|
|
5
9
|
```
|
|
6
10
|
$ envhound blame PATH
|
package/dist/envhound.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawnSync as
|
|
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)"
|
|
3
3
|
_envhound() {
|
|
4
4
|
local cur=\${COMP_WORDS[COMP_CWORD]}
|
|
5
5
|
case \${COMP_WORDS[COMP_CWORD-1]} in
|
|
@@ -34,38 +34,40 @@ complete -c envhound -f -a '(envhound __complete (commandline -opc)[2..-1] (comm
|
|
|
34
34
|
complete -c envhound -n '__fish_seen_subcommand_from dotenv' -F
|
|
35
35
|
complete -c envhound -l file -s f -r -F
|
|
36
36
|
complete -c envhound -l home -x -a '(__fish_complete_directories (commandline -ct))'
|
|
37
|
-
`}}var
|
|
38
|
-
`,r:"\r",t:"\t"})[t]??t);function
|
|
37
|
+
`}}var Ve=/^[A-Za-z0-9_./:@%+,=-]+$/,Un=/[\x00-\x1f\x7f]/g,qn=(e)=>({"\n":"\\n","\t":"\\t","\r":"\\r"})[e]??"\\x"+e.charCodeAt(0).toString(16).padStart(2,"0");function U(e){if(Ve.test(e))return e;if(/[\x00-\x1f\x7f]/.test(e))return"$'"+e.replace(/[\\']/g,"\\$&").replace(Un,qn)+"'";return"'"+e.replace(/'/g,"'\\''")+"'"}function Ce(e,n=""){let t=!e.includes("'")&&!/[\r\n]/.test(e);if(n==="'"&&t)return`'${e}'`;if(n!=='"'){if(Ve.test(e))return e;if(t)return`'${e}'`}return'"'+e.replace(/[\\"$]/g,"\\$&").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")+'"'}var Yn=/^(\s*)(export\s+)?([^=\s#]+)(\s*)=(.*)$/,Zn=/^[A-Za-z_][A-Za-z0-9_.-]*$/,Qn=/\$\{?[A-Za-z_]/;function Ie(e,n,t){for(let s=n;s<e.length;s++)if(t==='"'&&e[s]==="\\")s++;else if(e[s]===t)return s;return-1}var Gn=(e)=>e.replace(/\\([nrt"\\$])/g,(n,t)=>({n:`
|
|
38
|
+
`,r:"\r",t:"\t"})[t]??t);function V(e){let n=e.includes(`\r
|
|
39
39
|
`)?`\r
|
|
40
40
|
`:`
|
|
41
41
|
`,t=e.endsWith(`
|
|
42
|
-
`),
|
|
43
|
-
`+
|
|
44
|
-
`),
|
|
45
|
-
`)}function
|
|
46
|
-
`)}function
|
|
47
|
-
Current value: ${
|
|
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?
|
|
49
|
-
`)}var
|
|
50
|
-
run 'envhound path' to see each entry`}function
|
|
51
|
-
`)}var
|
|
52
|
-
`)}function
|
|
53
|
-
`),c=`${v(e.path,n.home)}${e.before===void 0?" (new file)":""}`,
|
|
54
|
-
`)}async function
|
|
55
|
-
`)
|
|
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)}
|
|
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
|
+
`)}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(`
|
|
55
|
+
`)+`
|
|
56
|
+
`;return i.map((l,c)=>{let f=e[c-a];return f?` ${l.out}${" ".repeat(28-l.width)} ${f}`:` ${l.out}`}).join(`
|
|
57
|
+
`)+`
|
|
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=`
|
|
56
60
|
# Added by envhound: load variables managed with \`envhound set\`
|
|
57
|
-
[ -f ${
|
|
58
|
-
`,
|
|
61
|
+
[ -f ${cn} ] && . ${cn}
|
|
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(`
|
|
59
63
|
`);if(e.endsWith(`
|
|
60
|
-
`))t.pop();return t.map((
|
|
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(`
|
|
61
65
|
`)+`
|
|
62
|
-
`;function
|
|
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(`
|
|
63
67
|
`)?`
|
|
64
|
-
`:"";u.push({path:d,before:f,after:(f??"")+h+on})}}return{changes:u,notes:s}}function hn(e,n,t){let i=R(n),r=H(i??""),s=[];for(let l of e)if(l.kind==="set"){r=Ie(r,l.name,l.value);let c=t[l.name];if(c!==void 0&&c!==l.value)s.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))s.push(`${l.name} is not in this file`);r=Ke(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===(i??"")?[]:[{path:n,before:i,after:a}],notes:s}}function mn(e,n,t){let i=(s)=>{let a=_(n,s),l=a.findLastIndex((c)=>ye(c,t));return a.slice(l+1).map((c)=>D(c,t.home))},r=(n.final.PATH??"").split(":");return e.map((s)=>{switch(s.kind){case"set":{if(n.final[s.name]===s.value)return{ok:!0,message:`a fresh login shell now gets ${s.name}`};let a=i(s.name);return{ok:!1,message:`a fresh login shell does not get ${s.name}${a.length?`: ${a.join(", ")} sets it again after envhound`:""}`}}case"unset":if(n.final[s.name]===void 0)return{ok:!0,message:`${s.name} is no longer set in a fresh login shell`};return{ok:!1,message:`${s.name} is still set in a fresh login shell (envhound blame ${s.name} shows where)`};case"path-add":return r.includes(s.dir)?{ok:!0,message:`${s.dir} is now in PATH for fresh login shells`}:{ok:!1,message:`${s.dir} is not in PATH of a fresh login shell (envhound blame PATH shows why)`};case"path-remove":return r.includes(s.dir)?{ok:!1,message:`${s.dir} is still in PATH (envhound path shows who adds it)`}:{ok:!0,message:`${s.dir} is no longer in PATH for fresh login shells`}}})}function gn(e){return e.flatMap((n)=>{switch(n.kind){case"set":return[`export ${n.name}=${B(n.value)}`];case"unset":return[`unset ${n.name}`];case"path-add":return[n.position==="front"?`export PATH=${B(n.dir)}:"$PATH"`:`export PATH="$PATH":${B(n.dir)}`];case"path-remove":return[]}})}import{spawnSync as mt}from"node:child_process";import{mkdtempSync as gt,readFileSync as vt,rmSync as bt}from"node:fs";import{tmpdir as yt,userInfo as vn}from"node:os";import{join as bn,resolve as wt}from"node:path";var xt="\x1F",ce="\x1E",$t=xt+["${BASH_SOURCE[0]:-}","${LINENO}","${FUNCNAME[0]:-}","${BASH_SOURCE[1]:-}","${BASH_LINENO[0]:-}"].join(ce)+ce,St="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";function M(e={}){let n=wt(e.home??process.env.HOME??vn().homedir),t=process.env.USER??vn().username,i={HOME:n,USER:t,LOGNAME:t,SHELL:process.env.SHELL??"/bin/bash",TERM:process.env.TERM??"xterm",PATH:St},r=gt(bn(yt(),"envhound-")),s=bn(r,"env");try{let a=mt("bash",["-lixc",`env -0 > '${s}'`],{env:{...i,PS4:$t},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=vt(s,"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=kt(l);return delete c.PS4,Ot(a.stderr,i,c)}finally{bt(r,{recursive:!0,force:!0})}}function kt(e){let n={};for(let t of e.split("\x00")){let i=t.indexOf("=");if(i>0)n[t.slice(0,i)]=t.slice(i+1)}return n}var Et=/^([A-Za-z_][A-Za-z0-9_]*)(\+?=)/,At=/^([A-Za-z_][A-Za-z0-9_]*)(?:(\+?=)([\s\S]*))?$/;function Dt(e){let n=Et.exec(e);if(n){let a=e.slice(n[0].length);if(a.startsWith("("))return[];return[{name:n[1],op:n[2],value:Q(a)[0]??"",local:!1,bare:!1}]}let[t,...i]=Q(e),r=i.filter((a)=>a.startsWith("-")).join(""),s=i.filter((a)=>!a.startsWith("-"));if(t==="unset"){if(r.includes("f"))return[];return s.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 s.flatMap((l)=>{let c=At.exec(l);if(!c)return[];return[{name:c[1],op:c[2]??"=",value:c[3]??"",local:a,bare:c[2]===void 0}]})}return[]}function Ot(e,n,t){let i=[],r=new Map(Object.entries(n)),s=new Map;for(let a of e.split(/(?:^|\n)\x1f+/).slice(1)){let l=a.split(ce);if(l.length<6)continue;let[c,u,o,d,f]=l;if(!c)continue;let h=l.slice(5).join(ce).replace(/\n$/,""),p=o?{kind:o==="source"||o==="."?"source":"function",name:o,at:{file:d,line:Number(f)}}:void 0,g=p?.kind==="function"?`${p.name}@${p.at.file}:${p.at.line}`:void 0;for(let m of Dt(h)){let y=!1;if(g){let P=s.get(g)??new Set;if(s.set(g,P),m.local)P.add(m.name);y=P.has(m.name)}if(m.bare)continue;let w=y?void 0:r.get(m.name),S=m.op==="unset"?void 0:m.op==="+="?(w??"")+m.value:m.value;if(!y)r.set(m.name,S);let{name:K,op:ne,value:x}=m;i.push({name:K,op:ne,value:x,previous:w,result:S,at:{file:c,line:Number(u)},via:p,local:y})}}return{shell:"bash",initial:n,final:t,assignments:i}}function Pt(e,n){let t=R(e),i=H(t??""),r=se(i),s=new Map;for(let l of i.lines)if(l.kind==="entry")s.set(l.key,(s.get(l.key)??0)+1);let a=oe(i,n).map((l)=>({key:l.key,value:l.value,line:l.line,status:l.status,current:l.current,count:s.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 yn(e,n,t,i=[]){let r=le(R(t.managed)??"",t.home),s=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":D(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&&Z(o.last),managed:s.has(o.name)}));for(let o of s)if(!c.some((d)=>d.name===o)&&o!=="PATH")c.push({name:o,value:e.final[o],by:"envhound (new shells)",managed:!0});let u=z(e,n.PATH??"").map((o)=>({dir:o.dir,by:o.addedBy?l(o.addedBy):`(${o.source})`,source:o.addedBy&&Z(o.addedBy),managed:a.has(o.dir),exists:o.exists,duplicateOf:o.duplicateOf}));for(let o of a)if(!u.some((d)=>d.dir===o))u.push({dir:o,by:"envhound (new shells)",managed:!0,exists:!0});return{home:t.home,vars:c,path:u,dotenv:i.map((o)=>Pt(o,n))}}import{spawnSync as er}from"node:child_process";import{emitKeypressEvents as nr}from"node:readline";import{isAbsolute as Tt,resolve as wn}from"node:path";function ue(e){return[{kind:"vars"},{kind:"path"},...e.data.dotenv.map((n)=>({kind:"dotenv",file:n.file}))]}var we=(e)=>e.kind==="dotenv"?`dotenv:${e.file}`:e.kind,j=(e)=>ue(e)[e.tab]??{kind:"vars"},k=(e)=>e.cursor[we(j(e))]??0;function xn(e,n={}){return{data:e,tab:n.tab??0,ops:[],cursor:{},filter:"",confirmQuit:!1,help:!1,showSecrets:n.showSecrets??!1,pageSize:10}}function $n(e,...n){let t=e.filter.toLowerCase();return!t||n.some((i)=>i?.toLowerCase().includes(t))}function G(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 i=n.find((r)=>r.name===t.name);if(t.kind==="unset"){if(i)i.pending="unset"}else if(i)i.pending="set",i.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,i)=>t.name.localeCompare(i.name)),n.filter((t)=>$n(e,t.name,t.newValue??t.value))}function X(e){let n=e.data.path.map((t)=>({...t}));for(let t of e.ops)if(t.kind==="path-remove"){for(let i of n)if(i.dir===t.dir)i.pending="remove"}else if(t.kind==="path-add"){let i={dir:t.dir,by:"envhound",managed:!0,exists:!0,pending:"add"};if(t.position==="front")n.unshift(i);else n.push(i)}return n}function ee(e,n){let i=(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 s=i.find((a)=>a.key===r.name);if(r.kind==="unset"){if(s)s.pending="unset"}else if(s)s.pending="set",s.newValue=r.value;else i.push({key:r.name,value:"",line:0,status:"new",count:0,pending:"new",newValue:r.value})}return i.filter((r)=>$n(e,r.key,r.newValue??r.value))}function Rt(e){let n=j(e);return n.kind==="vars"?G(e).length:n.kind==="path"?X(e).length:ee(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 J(e,n,t){let i=e.ops.filter((r)=>de(r)!==de(n));return{...e,ops:[...i,n],message:{text:t}}}function xe(e,n,t){return{...e,ops:e.ops.filter((i)=>de(i)!==de(n)),message:{text:t}}}var Mt=/^[A-Za-z_][A-Za-z0-9_]*$/,Lt=/^[A-Za-z_][A-Za-z0-9_.-]*$/;function _t(e,n){let t=e.trim().replace(/^~(?=\/|$)/,n);return Tt(t)?wn(t):wn(process.cwd(),t)}var C=(e,n)=>v(n,e.data.home);function Ht(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:i,file:r}=n.for;return J(t,{kind:"set",name:i,value:n.value,file:r},`${i} will be set${r?` in ${C(e,r)}`:""}`)}case"new-name":{let i=n.value.trim(),r=n.for.file;if(!(r?Lt:Mt).test(i))return{...e,message:{text:`'${i}' is not a valid ${r?"key":"variable name"}`,error:!0}};if(i==="PATH"&&!r)return{...t,message:{text:"use the PATH tab to change PATH",error:!0}};let s=r?e.data.dotenv.find((a)=>a.file===r)?.rows.find((a)=>a.key===i)?.value:e.data.vars.find((a)=>a.name===i)?.value;return{...t,prompt:{for:{kind:"new-value",name:i,file:r},label:`${i}=`,value:s??"",secret:T(i)}}}case"path-add":{if(!n.value.trim())return t;let i=_t(n.value,e.data.home);if(e.data.path.some((r)=>r.dir===i))return{...t,message:{text:`${C(e,i)} is already in PATH`,error:!0}};return J(t,{kind:"path-add",dir:i,position:n.for.position},`${C(e,i)} will be added`)}}}function Nt(e,n){let t=e.prompt,i=(r)=>{let s={...e,prompt:{...t,value:r}};return t.for.kind==="filter"?{...s,filter:r,cursor:{...e.cursor,[we(j(e))]:0}}:s};if(n.name==="escape")return{...e,prompt:void 0,filter:t.for.kind==="filter"?"":e.filter};if(n.name==="return"||n.name==="enter")return Ht(e);if(n.name==="backspace")return i([...t.value].slice(0,-1).join(""));if(n.ctrl&&n.name==="u")return i("");if(n.ch&&!n.ctrl)return i(t.value+n.ch);return e}function A(e,n){let t=Math.max(0,Rt(e)-1);return{...e,cursor:{...e.cursor,[we(j(e))]:Math.min(t,Math.max(0,n))}}}function Sn(e,n,t,i,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:i},label:i?"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:i},label:`${t.name}=`,value:t.value,secret:T(t.name)}}];if(n.ch==="d")return[r()];if(n.ch==="o"){if(!t.source)return[{...e,message:{text:i?"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 Vt(e,n){let t=G(e)[k(e)],i=t&&{name:t.name,value:t.newValue??t.value??"",source:t.source};return Sn(e,n,i,void 0,()=>{let r=t,s={kind:"unset",name:r.name};if(r.pending==="new"||r.pending==="unset"||r.pending==="set"&&!r.managed)return xe(e,s,`change to ${r.name} dropped`);if(r.managed)return J(e,s,`${r.name} will be removed from envhound's file`);return{...e,message:{text:kn(r.name,r),error:!0}}})}function Ct(e,n,t){let i=ee(e,t)[k(e)],r=i&&{name:i.key,value:i.newValue??i.value,source:i.line?{file:t,line:i.line}:void 0};return Sn(e,n,r,t,()=>{let s=i,a={kind:"unset",name:s.key,file:t};if(s.pending)return xe(e,a,`change to ${s.key} dropped`);return J(e,a,`${s.key} will be deleted from ${C(e,t)}`)})}function jt(e,n){if(n.ch==="a"||n.ch==="A"){let i=n.ch==="a"?"front":"back";return[{...e,prompt:{for:{kind:"path-add",position:i},label:`add to the ${i} of PATH: `,value:""}}]}let t=X(e)[k(e)];if(!t)return[e];if(n.ch==="d"){let i={kind:"path-remove",dir:t.dir};if(t.pending)return[xe(e,i,`change to ${C(e,t.dir)} dropped`)];if(t.managed)return[J(e,i,`${C(e,t.dir)} will be removed`)];return[{...e,message:{text:kn(C(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 kn(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 En(e,n){if(n.ctrl&&n.name==="c")return[e,{kind:"quit"}];if(e.prompt)return[Nt(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 i=ue(t).length;if(n.name==="tab")return[{...t,tab:(t.tab+1)%i}];if(n.ch&&/^[1-9]$/.test(n.ch)&&Number(n.ch)<=i)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 s=j(t),[a,l]=s.kind==="vars"?Vt(t,n):s.kind==="path"?jt(t,n):Ct(t,n,s.file);return[A(a,k(a)),l]}import{basename as It,relative as Kt}from"node:path";var I=(e,n)=>(t)=>n&&t?`\x1B[${e}m${t}\x1B[0m`:t;function Wt(e){return{bold:I("1",e),dim:I("2",e),inverse:I("7",e),red:I("31",e),green:I("32",e),yellow:I("33",e),cyan:I("36",e)}}function b(e,n){if(n<=0)return"";let t=e.replace(/[\x00-\x1f\x7f]/g," "),i=[...t];return i.length>n?i.slice(0,n-1).join("")+"…":t.padEnd(n+(t.length-i.length))}function Y(e,n){return[...e].length>n?b(e,n):e}function $e(e,n){let t=[...e];return t.length>n?"…"+t.slice(t.length-n+1).join(""):b(e,n)}var Ft={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"},zt=["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 Se(e,n,t){return Math.max(0,Math.min(e-Math.floor(t/2),n-t))}function Bt(e,n){if(e.kind==="vars")return"Variables";if(e.kind==="path")return"PATH";let t=Kt(process.cwd(),e.file),i=t&&!t.startsWith("..")?t:v(e.file,n);return i.length>24?It(e.file):i}function Ut(e,n,t){let r=ue(e).map((u,o)=>` ${o+1} ${Bt(u,e.data.home)} `),s=r.map((u,o)=>o===e.tab?t.inverse(u):t.dim(u)).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")} ${s} ${c}`;if(a+1+l.length<=n)return`${s} ${c}`;if(a<=n)return s;return t.inverse(b(r[e.tab].trim(),n))}function An(e,n,t,i){let r=Wt(i.color),s=[Ut(e,n,r),r.dim("─".repeat(Math.max(0,n)))],a=j(e),l=[r.dim(b(Ft[a.kind],n))];if(e.prompt){let d=e.prompt.secret&&!e.showSecrets?"•".repeat([...e.prompt.value].length):e.prompt.value,f=Math.max(1,n-e.prompt.label.length-1),h=[...d],p=h.length>f?"…"+h.slice(h.length-f+1).join(""):d;l.push(r.bold(e.prompt.label)+p+(i.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-s.length-l.length-2),u,o;if(e.help)u=zt.map((d)=>b(d,n)),o="";else if(a.kind==="vars")u=qt(e,n,c,r),o=Yt(e);else if(a.kind==="path")u=Zt(e,n,c,r),o=Qt(e);else u=Gt(e,a.file,n,c,r),o=Xt(e,a.file);for(let d=0;d<c;d++)s.push(u[d]??"");return s.push(r.dim("─".repeat(Math.max(0,n)))),s.push(b(o,n)),s.push(...l),s.slice(0,t)}function q(e,n,t){if(n===void 0)return"";let i=n.replace(/\n/g,"\\n");return T(e)&&!t.showSecrets?"********":v(i,t.data.home)}function Dn(e,n,t,i){if(n.pending==="unset")return`${q(e,n.value,t)} (${i})`;if(n.pending==="new")return q(e,n.newValue,t);if(n.pending==="set")return`${q(e,n.value,t)} → ${q(e,n.newValue,t)}`;return q(e,n.value,t)}var ke=(e)=>e==="new"||e==="add"?"+":e==="unset"||e==="remove"?"-":e?"*":" ";function qt(e,n,t,i){let r=G(e);if(!r.length)return[i.dim(e.filter?"no variable matches the filter":"no variables")];let s=Math.min(28,Math.max(8,...r.map((f)=>f.name.length))),a=Math.min(32,Math.max(6,...r.map((f)=>f.by.length))),l=Math.max(10,n-s-a-6),c=t-1,u=k(e),o=Se(u,r.length,c),d=[i.bold(Y(` ${b("NAME",s)} ${b("SET BY",a)} VALUE`,n))];return r.slice(o,o+c).forEach((f,h)=>{let p=Y(`${ke(f.pending)} ${b(f.name,s)} ${$e(f.by,a)} ${b(Dn(f.name,f,e,"unset"),l)}`,n),g=f.pending==="unset"?i.red:f.pending?i.yellow:f.managed?i.cyan:f.by.startsWith("(")?i.dim:(m)=>m;d.push(o+h===u?i.inverse(p):g(p))}),d}function Yt(e){let n=G(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 Zt(e,n,t,i){let r=X(e);if(!r.length)return[i.dim("PATH is empty")];let s=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=k(e),u=Se(c,r.length,l),o=[i.bold(Y(` # ${b("DIRECTORY",s)} ${b("ADDED BY",a)}`,n))];return r.slice(u,u+l).forEach((d,f)=>{let h=u+f,p=d.pending?`staged: ${d.pending}`:d.duplicateOf?`duplicate of #${d.duplicateOf}`:!d.exists?"missing":"",g=Y(`${ke(d.pending)} ${String(h+1).padStart(2)} ${$e(v(d.dir,e.data.home),s)} ${$e(d.by,a)} ${b(p,Math.max(0,n-s-a-10))}`,n),m=d.pending==="remove"?i.red:d.pending?i.yellow:p?i.red:d.managed?i.cyan:d.by.startsWith("(")?i.dim:(y)=>y;o.push(h===c?i.inverse(g):m(g))}),o}function Qt(e){let n=X(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 Jt(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 ${q(e.key,e.current,n)}`;return e.status==="same"?"same as shell":""}function Gt(e,n,t,i,r){let s=ee(e,n),a=e.data.dotenv.find((p)=>p.file===n);if(!s.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,...s.map((p)=>p.key.length))),c=Math.max(10,Math.min(48,t-l-32)),u=Math.max(0,t-l-c-12),o=i-1,d=k(e),f=Se(d,s.length,o),h=[r.bold(Y(` ${b("KEY",l)} LINE ${b("VALUE",c)} `,t))];return s.slice(f,f+o).forEach((p,g)=>{let m=Jt(p,e),y=p.line?String(p.line).padStart(4):" ",w=Y(`${ke(p.pending)} ${b(p.key,l)} ${y} ${b(Dn(p.key,p,e,"delete"),c)} ${b(m,u)}`,t),S=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:(K)=>K;h.push(f+g===d?r.inverse(w):S(w))}),h}function Xt(e,n){let t=e.data.dotenv.find((c)=>c.file===n),i=v(n,e.data.home),r=t?.problems?` · ${t.problems} problem(s), see envhound dotenv`:"",s=ee(e,n)[k(e)];if(!s)return`${i}${t?.exists?"":" (new file)"}${r}`;let a=s.line?`line ${s.line} of ${i}`:`new in ${i}`,l=s.status==="conflict"?": loaders such as dotenv keep the shell's value":"";return`${s.key}: ${a}${l}${r}`}var tr="\x1B[?1049h\x1B[?25l",rr="\x1B[?25h\x1B[?1049l";function ir(e,n){let[t="vi",...i]=e.trim().split(/\s+/),r=t.split("/").pop()??t;if(/^(code|code-insiders|codium|cursor|windsurf)$/.test(r))return[t,[...i.filter((s)=>s!=="--wait"),"--wait","-g",`${n.file}:${n.line}`]];return[t,[...i,`+${n.line}`,n.file]]}function On(e,n){let t=process.stdin,i=process.stdout,r=xn(e(),{showSecrets:n.showSecrets,tab:n.tab});return new Promise((s,a)=>{let l=()=>{let h=i.columns||80,p=i.rows||24;r={...r,pageSize:Math.max(1,p-8)};let g=An(r,h,p,{color:n.color});i.write("\x1B[H"+g.map((m)=>m+"\x1B[K").join(`\r
|
|
65
|
-
`)+"\x1B[J")},c=()=>{
|
|
66
|
-
`),
|
|
67
|
-
|
|
68
|
-
Usage:
|
|
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:
|
|
69
71
|
envhound [list] every exported variable, with the startup file:line that sets it
|
|
70
72
|
envhound blame VAR every startup file:line that assigns VAR, in order
|
|
71
73
|
(for PATH: what each step added or removed)
|
|
@@ -112,16 +114,20 @@ so it only sees what startup files do. That runs your startup files once.
|
|
|
112
114
|
|
|
113
115
|
Once a week envhound asks npm in the background whether a newer version exists,
|
|
114
116
|
and says so after a command. ENVHOUND_NO_UPDATE_CHECK=1 turns this off.
|
|
115
|
-
`,
|
|
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}
|
|
119
|
+
|
|
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(`
|
|
116
122
|
|
|
117
|
-
`),()=>
|
|
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(`
|
|
118
124
|
|
|
119
125
|
`)+`
|
|
120
|
-
`),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
|
|
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(`
|
|
121
127
|
This shell is unchanged. To apply it here too, run:
|
|
122
|
-
`)+d.map((
|
|
123
|
-
`));return o.every((
|
|
124
|
-
${
|
|
125
|
-
Run 'envhound --help' for usage.`),2}
|
|
126
|
-
${
|
|
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}
|
|
131
|
+
Run 'envhound --help' for usage.`),2}Mr(process.argv.slice(2)).then((e)=>{if(process.exitCode=e,Re)process.stderr.write(`
|
|
132
|
+
${Re}
|
|
127
133
|
`)},(e)=>{console.error(`envhound: ${e instanceof Error?e.message:e}`),process.exitCode=1});
|