@loopgrid/gitm
Version:
Seamlessly manage multiple git accounts on the same device
56 lines (53 loc) • 33 kB
JavaScript
import ge from'path';import'url';import ke from'os';import bt from'conf';import {spawn}from'child_process';import V from'fs/promises';import Rt from'crypto';import {Command}from'commander';import y from'chalk';import $e from'inquirer';import ee from'simple-git';var Pt=Object.defineProperty;var T=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ne=(e,t)=>{for(var o in t)Pt(e,o,{get:t[o],enumerable:true});};var u=T(()=>{});var Ge=T(()=>{u();});var _,B,_e,Te,oe=T(()=>{u();_=ge.join(ke.homedir(),".ssh"),B=ge.join(_,"config"),_e="ed25519",Te={github:{name:"GitHub",host:"github.com",sshHost:"github.com",urlPatterns:[/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/,/^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/]},gitlab:{name:"GitLab",host:"gitlab.com",sshHost:"gitlab.com",urlPatterns:[/gitlab\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/,/^git@gitlab\.com:([^/]+)\/(.+?)(?:\.git)?$/]},bitbucket:{name:"Bitbucket",host:"bitbucket.org",sshHost:"bitbucket.org",urlPatterns:[/bitbucket\.org[:/]([^/]+)\/(.+?)(?:\.git)?$/,/^git@bitbucket\.org:([^/]+)\/(.+?)(?:\.git)?$/]}};});var Y,ae=T(()=>{u();Y="1.1.0";});function k(){return L.get("accounts",{})}function E(e){return k()[e]}function Le(e,t){let o=k();o[e]=t,L.set("accounts",o);}function De(e){let t=k();delete t[e],L.set("accounts",t);}function Ke(e){let t=k();return e in t}function Fe(e,t="github"){return ge.join(_,`gitm_${e}_${t}`)}function F(){let e=L.get("customProviders",{}),t={};for(let[o,r]of Object.entries(e))t[o]={...r,urlPatterns:r.urlPatterns.map(i=>new RegExp(i))};return t}function Ve(e,t){let o=L.get("customProviders",{}),r={...t,urlPatterns:t.urlPatterns.map(i=>i.source),isCustom:true};o[e]=r,L.set("customProviders",o);}function We(e){let t=L.get("customProviders",{});delete t[e],L.set("customProviders",t);}function ce(e){let t=F();return e in t}var L,O=T(()=>{u();Ge();oe();ae();L=new bt({projectName:"gitm",projectVersion:Y});});function A(e,t=[],o={}){return new Promise((r,i)=>{var p,f;let n=spawn(e,t,{shell:false,stdio:o.input?["pipe","pipe","pipe"]:["ignore","pipe","pipe"],cwd:o.cwd,env:o.env}),s="",a="";o.input&&n.stdin&&(o.input,n.stdin.write(o.input),n.stdin.end()),(p=n.stdout)==null||p.on("data",d=>{s+=d.toString();}),(f=n.stderr)==null||f.on("data",d=>{a+=d.toString();}),n.on("error",d=>{i(d);}),n.on("exit",d=>{if(d===0||d===1)r({stdout:s,stderr:a});else {let P=new Error(`Command failed with exit code ${d}`);P.code=d,P.stdout=s,P.stderr=a,i(P);}});})}function Z(e,t){let o=ge.resolve(e),r=ge.resolve(t);return o.startsWith(r+ge.sep)||o===r}function G(e){return /^[a-zA-Z0-9_-]+$/.test(e)&&e.length>0&&e.length<50}function je(e){return e.replace(/[^a-zA-Z0-9_-]/g,"_")}var U=T(()=>{u();});var ze={};Ne(ze,{detectProvider:()=>D,getAllProviders:()=>q,getProviderConfig:()=>he,getProviderDisplayName:()=>fe,getProviderList:()=>pe,isValidProvider:()=>$t});function q(){let e=F();return {...Te,...e}}function D(e){let t=/^git@([^:]+)-([^:]+):([^/]+)\/(.+?)(?:\.git)?$/,o=e.match(t);if(o){let[,i,,n,s]=o,a=null;if(i.includes("github"))a="github";else if(i.includes("gitlab"))a="gitlab";else if(i.includes("bitbucket"))a="bitbucket";else {let p=F();for(let[f,d]of Object.entries(p))if(i.includes(d.host.replace(/\./g,"-"))){a=f;break}}if(a)return {provider:a,organization:n,repository:s.replace(/\.git$/,"")}}let r=q();for(let[i,n]of Object.entries(r))for(let s of n.urlPatterns)if(s.test(e)){let a=e.match(s);if(a)return {provider:i,organization:a[1],repository:a[2].replace(/\.git$/,"")}}return null}function pe(){let e=q();return Object.keys(e)}function $t(e){let t=q();return e in t}function fe(e){var o;return ((o=q()[e])==null?void 0:o.name)||e}function he(e){return q()[e]||null}var J=T(()=>{u();oe();O();});var Be={};Ne(Be,{addSSHKeyToAgent:()=>re,generateSSHKey:()=>Se,getSSHKeyFingerprint:()=>Me,getSSHKeyInfo:()=>we,getSSHRemoteUrl:()=>ne,readPublicKey:()=>Pe,setSecureFilePermissions:()=>ye,sshKeyExists:()=>ve,updateSSHConfig:()=>W});async function ye(e){try{if(process.platform==="win32"){let t=process.env.USERNAME||process.env.USER;if(!t)throw new Error("Could not determine current user");let o=[["icacls",[e,"/inheritance:r"]],["icacls",[e,"/grant:r",`${t}:F`]],["icacls",[e,"/remove","Users"]],["icacls",[e,"/remove","Everyone"]],["icacls",[e,"/remove","Authenticated Users"]]];for(let[r,i]of o)try{await A(r,i);}catch(n){if(!i.includes("/remove"))throw n}}else await V.chmod(e,384);}catch(t){console.warn(`Warning: Could not set secure permissions on ${e}: ${t.message}`);}}async function Se(e,t,o={}){if(!Z(e,_))throw new Error("Invalid key path: must be within SSH directory");let r=o.type||_e,i=o.passphrase||"",n=o.comment||t,s=["-t",r,"-f",e,"-N",i,"-C",n];r==="rsa"&&o.bits&&s.splice(2,0,"-b",o.bits.toString());try{await A("ssh-keygen",s),await ye(e),process.platform!=="win32"&&await V.chmod(`${e}.pub`,420);}catch(a){throw new Error(`Failed to generate SSH key: ${a.message}`)}}async function W(e,t,o,r){if(!Z(o,_))throw new Error("Invalid SSH key path");let{getProviderConfig:i}=await Promise.resolve().then(()=>(J(),ze)),n=i(t),s,a;r?(s=r.domain,a=r.sshPort):n?(s=n.sshHost||n.host,a=n.sshPort):s=t==="bitbucket"?"bitbucket.org":`${t}.com`;let p=`${s.replace(/\./g,"-")}-${e}`,f=`
# gitm: ${e}
Host ${p}
HostName ${s}
User git
IdentityFile ${o}
IdentitiesOnly yes`;a&&a!==22&&(f+=`
Port ${a}`),f+=`
`;try{let d="";try{d=await V.readFile(B,"utf-8");}catch{}let P=`# gitm: ${e}`;if(d.includes(P)){let $=d.split(`
`),v=$.findIndex(R=>R.includes(P)),N=v+1;for(;N<$.length&&$[N].trim()!==""&&!$[N].startsWith("Host ");)N++;$.splice(v,N-v),d=$.join(`
`);}let x=d.trim()+`
`+f;await V.writeFile(B,x),await ye(B);}catch(d){throw new Error(`Failed to update SSH config: ${d.message}`)}}async function re(e){if(!Z(e,_))throw new Error("Invalid SSH key path");try{await A("ssh-add",[e]);}catch{process.platform==="win32"?console.warn("Could not add key to ssh-agent. On Windows, you may need to start the ssh-agent service."):console.warn("Could not add key to ssh-agent (this is ok)");}}function ne(e,t,o,r,i){let n=o.replace(/[^a-zA-Z0-9_-]/g,""),s=r.replace(/[^a-zA-Z0-9_.-]/g,""),a;return i?a=i.domain:a=e==="bitbucket"?"bitbucket.org":`${e}.com`,`git@${`${a.replace(/\./g,"-")}-${t}`}:${n}/${s}.git`}async function ve(e){if(!Z(e,_))return false;try{return await V.access(e),await V.access(`${e}.pub`),!0}catch{return false}}async function Pe(e){if(!Z(e,_))throw new Error("Invalid SSH key path");return await V.readFile(`${e}.pub`,"utf-8")}function Me(e){let t=e.trim().split(/\s+/);if(t.length<2)throw new Error("Invalid SSH public key format");let o=t[1],r=Buffer.from(o,"base64");return `SHA256:${Rt.createHash("sha256").update(r).digest("base64").replace(/=+$/,"")}`}function we(e){let t=e.trim().split(/\s+/);if(t.length<2)throw new Error("Invalid SSH public key format");let o=t[0],r=Me(e);return {type:o,fingerprint:r}}var j=T(()=>{u();oe();U();});u();u();O();j();J();U();u();function h(e,t="info"){switch(t){case "error":console.log(y.red(e));break;case "warning":console.log(y.yellow(e));break;case "success":console.log(y.green(e));break;case "debug":console.log(y.gray(e));break;default:console.log(e);}}function c(e){h(`Error: ${e}`,"error");}function m(e){h(`\u2713 ${e}`,"success");}function S(e){h(e,"warning");}function C(e){h(e,"debug");}function l(e){h(e,"info");}function H(e,t,o=2){let r=" ".repeat(o);return y.gray(`${r}${e}: ${t}`)}function Q(e){return y.bold(`
${e}:
`)}function K(e){let t=e.split("@");if(t.length!==2)return e;let[o,r]=t;if(!o)return e;let i=r.split(".");if(i.length<2)return e;let n;o.length===1?n=o:o.length===2?n=o[0]+"*":o.length<=5?n=o[0]+"*".repeat(o.length-2)+o[o.length-1]:n=o[0]+"***"+o[o.length-1];let s=i[0],a;s.length===1?a=s:s.length===2?a=s[0]+"*":s.length<=6?a=s[0]+"*".repeat(s.length-2)+s[s.length-1]:a=s[0]+"****"+s[s.length-1];let p=i.slice(1).join(".");return `${n}@${a}.${p}`}u();U();async function be(e){try{return process.platform==="win32"?await A("where",[e]):await A("which",[e]),!0}catch{return false}}async function Ye(){let[e,t,o]=await Promise.all([be("ssh-keygen"),be("ssh-add"),be("ssh")]);return {sshKeygen:e,sshAdd:t,ssh:o}}function Ze(){switch(process.platform){case "win32":return `To use gitm on Windows, you need OpenSSH installed:
Option 1: Windows 10/11 Built-in OpenSSH (Recommended)
1. Open Settings > Apps > Optional Features
2. Click "Add a feature"
3. Search for "OpenSSH Client" and install it
4. Restart your terminal
Option 2: Git for Windows
1. Install Git for Windows from https://git-scm.com/download/win
2. During installation, select "Use Git and optional Unix tools from the Command Prompt"
3. This will add ssh, ssh-keygen, and ssh-add to your PATH
Option 3: Use Git Bash
If you have Git installed, you can run gitm from Git Bash which includes all SSH tools`;case "darwin":return `SSH tools should be pre-installed on macOS. If missing, install Xcode Command Line Tools:
xcode-select --install`;default:return `Install OpenSSH using your package manager:
Ubuntu/Debian: sudo apt-get install openssh-client
Fedora: sudo dnf install openssh-clients
Arch: sudo pacman -S openssh`}}async function Je(e,t){if(!(await Ye()).sshKeygen){c("SSH tools are not available on your system."),console.log(),console.log(Ze());return}if(!G(e)){c("Invalid profile name. Use only letters, numbers, dash, and underscore.");return}if(Ke(e)){S(`Account '${e}' already exists`);let{overwrite:d}=await $e.prompt([{type:"confirm",name:"overwrite",message:"Do you want to overwrite it?",default:false}]);if(d)return}let r=pe().map(d=>({name:fe(d),value:d})),i=(t==null?void 0:t.provider)||"github",n=await $e.prompt([{type:"input",name:"name",message:"Your name:",validate:d=>d.trim()!==""||"Name is required"},{type:"input",name:"email",message:"Your email:",validate:d=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(d)||"Please enter a valid email"},{type:"list",name:"provider",message:"Git provider:",choices:r,default:i,when:!(t!=null&&t.provider)},{type:"input",name:"username",message:"Username/Organization (for auto-detection):",validate:d=>d.trim()!==""||"Username is required"},{type:"confirm",name:"generateSSH",message:"Generate new SSH key?",default:true}]);t!=null&&t.provider&&(n.provider=t.provider);let s=Fe(e,n.provider);if(n.generateSSH)try{await Se(s,n.email,{comment:`${n.email} (gitm: ${e})`}),m("SSH key generated successfully");}catch(d){c(`Failed to generate SSH key: ${d.message}`);return}let a=he(n.provider),p=(a==null?void 0:a.isCustom)||false,f={name:n.name,email:n.email,provider:n.provider,username:n.username,sshKeyPath:s,createdAt:new Date().toISOString()};p&&a&&(f.customProvider={type:n.provider,domain:a.host,sshPort:a.sshPort}),Le(e,f),m(`Account '${e}' added successfully`),l("Next steps:"),l(`\u2022 Setup authentication: gitm auth ${e}`),l(`\u2022 Verify configuration: gitm verify ${e}`);}u();O();function Xe(){let e=k(),t=Object.keys(e);if(t.length===0){S("No accounts configured yet."),l("Add an account with: gitm add <profile>");return}h(Q("Configured accounts")),t.forEach(o=>{let r=e[o];h(y.cyan(` ${o}`)),h(H("Name",r.name,4)),h(H("Email",K(r.email),4)),h(H("Provider",r.provider,4)),h(H("Username",r.username,4)),console.log();});}u();O();u();J();O();U();async function z(e){let t=e||ee(process.cwd());if(!await t.checkIsRepo())throw new Error("Not in a git repository");let r=await t.getRemotes(true);if(r.length===0)throw new Error("No remote configured");let i=r.find(n=>n.name==="origin")||r[0];return {url:i.refs.fetch||i.refs.push||"",name:i.name}}async function le(e){let t=D(e);if(!t)return null;let o=k();for(let[n,s]of Object.entries(o))if(s.provider===t.provider&&s.username.toLowerCase()===t.organization.toLowerCase())return {profile:n,account:s,repoInfo:t};let r=Object.entries(o).filter(([n,s])=>s.provider===t.provider);if(r.length===1)return {profile:r[0][0],account:r[0][1],repoInfo:t};if(r.length>1)return {profile:null,account:null,repoInfo:t,candidates:r};let i=Object.entries(o);return i.length>0?{profile:null,account:null,repoInfo:t,candidates:i}:null}async function Ae(e,t){let o=t||ee(process.cwd()),r=e.name.replace(/['"\\]/g,""),i=e.email.replace(/['"\\]/g,"");await o.addConfig("user.name",r),await o.addConfig("user.email",i);}async function He(e,t,o){let r=o||ee(process.cwd()),i=je(e);if(!t.match(/^(https?:\/\/|git@|ssh:\/\/)/))throw new Error("Invalid remote URL format");await r.remote(["set-url",i,t]);}async function Qe(e){let t=e||ee(process.cwd());try{return await t.checkIsRepo()}catch{return false}}async function et(e){let t=e||ee(process.cwd());try{let[o,r]=await Promise.all([t.getConfig("user.name"),t.getConfig("user.email")]);return {name:o.value,email:r.value}}catch{return {name:null,email:null}}}async function tt(e,t=false,o){let r=E(e);if(!r)throw new Error(`Account '${e}' not found`);let i=ee(process.cwd());await Ae(r,i);let{updateSSHConfig:n,addSSHKeyToAgent:s,getSSHRemoteUrl:a}=await Promise.resolve().then(()=>(j(),Be));if(await n(e,r.provider,r.sshKeyPath,r.customProvider),await s(r.sshKeyPath),t){let p=await z(i),f=D(p.url);if(!f)throw new Error("Could not detect git provider from remote URL");let d=a(r.provider,e,f.organization,f.repository,r.customProvider);await He(p.name,d,i);}}j();J();U();async function ot(e){if(!G(e)){c("Invalid profile name.");return}let t=E(e);if(!t){c(`Account '${e}' not found`),C("Available accounts: gitm list");return}try{let o=await z(),r=D(o.url);if(!r){S("Could not detect git provider from remote URL"),C("Remote URL: "+o.url);return}await Ae(t),m("Git config updated"),await W(e,t.provider,t.sshKeyPath),m("SSH config updated"),await re(t.sshKeyPath);let i=ne(t.provider,e,r.organization,r.repository);await He(o.name,i),m("Remote URL updated"),m(`Account '${e}' is now active for this repository`),C(`
Repository configuration:`),C(H("Name",t.name)),C(H("Email",K(t.email))),C(H("Remote",i));}catch(o){let r=o.message;r.includes("Not in a git repository")?(c("Not in a git repository"),l("Navigate to a git repository first.")):c(r);}}u();O();async function rt(e={ssh:true}){try{let t=await z(),o=await le(t.url);if(!o){let i=k();Object.keys(i).length===0?(c("No accounts configured"),l("Add an account first: gitm add <profile>")):(c("Could not match repository to any configured account"),l('Run "gitm list" to see configured accounts'));return}let r;if(o.profile){m(`Auto-detected account: ${o.profile}`);let{confirmDetection:i}=await $e.prompt([{type:"confirm",name:"confirmDetection",message:`Use auto-detected account '${o.profile}'?`,default:!0}]);if(i)r=o.profile;else {let n=k(),s=Object.entries(n),{profile:a}=await $e.prompt([{type:"list",name:"profile",message:"Select account to use for this repository:",choices:s.map(([p,f])=>({name:`${p} (${f.name})`,value:p}))}]);r=a;}}else if(o.candidates&&o.candidates.length>0){let{profile:i}=await $e.prompt([{type:"list",name:"profile",message:"Select account to use for this repository:",choices:o.candidates.map(([n,s])=>({name:`${n} (${s.name})`,value:n}))}]);r=i;}else {c("No accounts available");return}await tt(r,e.ssh||!1),m(`Account '${r}' configured for this repository`),e.ssh!==!1&&t.url.startsWith("https://")?m("Remote URL converted to SSH for secure authentication"):e.ssh===!1&&t.url.startsWith("https://")&&(S("Important: HTTPS URLs may not work correctly with multiple accounts"),l("SSH is recommended for multi-account management"),l(`To convert to SSH, run: gitm use ${r}`));}catch(t){let o=t.message;o.includes("Not in a git repository")?c("Not in a git repository"):c(o);}}u();j();O();async function nt(e,t={ssh:true}){var d;let o=await le(e),r=k();if(!o||Object.keys(r).length===0){S("No accounts configured"),l("Cloning with default git settings..."),await ee().clone(e,t.directory??"repository"),m("Repository cloned"),l('Run "gitm add <profile>" to add an account');return}let i,n;if(o.profile){m(`Auto-detected account: ${o.profile}`);let{confirmDetection:P}=await $e.prompt([{type:"confirm",name:"confirmDetection",message:`Use auto-detected account '${o.profile}'?`,default:true}]);if(P)i=o.profile,n=o.account;else {let x=Object.entries(r),{profile:$}=await $e.prompt([{type:"list",name:"profile",message:"Select account to use for this repository:",choices:x.map(([v,N])=>({name:`${v} (${N.name})`,value:v}))}]);i=$,n=E($);}}else if(o.candidates&&o.candidates.length>0){let{profile:P}=await $e.prompt([{type:"list",name:"profile",message:"Select account to use for this repository:",choices:o.candidates.map(([x,$])=>({name:`${x} (${$.name})`,value:x}))}]);i=P,n=E(P);}else {c("No accounts available");return}if(!n){c("Account not found");return}let s=t.ssh!==false;(e.startsWith("git@")||e.includes("ssh://"))&&(s=true),await W(i,n.provider,n.sshKeyPath,n.customProvider),await re(n.sshKeyPath);let a=e;s&&o.repoInfo&&(a=ne(n.provider,i,o.repoInfo.organization,o.repoInfo.repository,n.customProvider));let p=ee(),f=t.directory||((d=o.repoInfo)==null?void 0:d.repository)||"repository";try{await p.clone(a,f),m("Repository cloned successfully");let P=ee(f);await P.addConfig("user.name",n.name),await P.addConfig("user.email",n.email),m(`Git config set for account '${i}'`),l(`Repository: ${ge.resolve(f)}`),l(`Account: ${n.name} <${K(n.email)}>`),!s&&e.startsWith("https://")&&l("Note: Cloned with HTTPS. Use 'gitm use <profile>' to switch to SSH");}catch(P){c(`Clone failed: ${P.message}`),s&&(l("Make sure you have added your SSH key to your git provider:"),l(`Run: gitm auth ${i}`));}}u();O();u();ae();var ie=new bt({projectName:"gitm-auth-state",projectVersion:Y});function ue(e){return ie.get("authStates",{})[e]||{copyCompleted:false,browserCompleted:false,addCompleted:false,testCompleted:false,lastUpdated:new Date().toISOString()}}function M(e,t){let o=ie.get("authStates",{});o[e]={...ue(e),...t,lastUpdated:new Date().toISOString()},ie.set("authStates",o);}function de(e){let t=ie.get("authStates",{});delete t[e],ie.set("authStates",t);}function it(e){let t=ue(e);return t.copyCompleted&&t.browserCompleted&&t.addCompleted&&t.testCompleted}U();async function ct(e){if(!G(e)){c("Invalid profile name.");return}let t=E(e);if(!t){c(`Account '${e}' not found`);return}let{confirm:o}=await $e.prompt([{type:"confirm",name:"confirm",message:`Are you sure you want to remove account '${e}'?`,default:false}]);if(!o)return;let{removeSSH:r}=await $e.prompt([{type:"confirm",name:"removeSSH",message:"Also remove associated SSH keys?",default:true}]);if(r&&t.sshKeyPath)try{await V.unlink(t.sshKeyPath),await V.unlink(`${t.sshKeyPath}.pub`),m("SSH keys removed");}catch{S("Could not remove SSH keys (may already be deleted)");}De(e),de(e),m(`Account '${e}' removed successfully`);}u();J();async function ut(){try{let e=ee();if(!await Qe(e)){c("Not in a git repository");return}let o=await et(e);try{let r=await z(e),i=D(r.url);if(console.log(Q("Repository Status")),o.name&&o.email?(console.log(y.green("Git User Configuration:")),console.log(H("Name",o.name)),console.log(H("Email",K(o.email)))):S("No git user configured for this repository"),console.log(y.cyan(`
Remote Configuration:`)),console.log(H("Remote",r.name)),console.log(H("URL",r.url)),i){console.log(H("Provider",i.provider)),console.log(H("Organization",i.organization)),console.log(H("Repository",i.repository));let n=/^git@(.+)-([^:]+):/,s=r.url.match(n);s?(console.log(`
`),m(`Using gitm account: ${s[2]}`)):(S("Not using gitm managed account"),l('Run "gitm init" to set up an account'));}}catch{S("No remote configured");}}catch(e){c(e.message);}}u();O();j();U();async function mt(e){var o,r,i,n,s,a,p;let t=E(e);if(!t){c(`Account '${e}' not found`);return}if(it(e)){let{rerun:f}=await $e.prompt([{type:"confirm",name:"rerun",message:"Authentication already completed. Run setup again?",default:false}]);if(!f){m("Authentication is already set up for this profile.");return}de(e);}try{let f=await Pe(t.sshKeyPath),d=we(f);console.log(y.bold(`
SSH Key Details for `+e+`:
`)),console.log(y.cyan("Type: ")+d.type),console.log(y.cyan("Fingerprint: ")+d.fingerprint);let x={github:"https://github.com/settings/keys",gitlab:"https://gitlab.com/-/profile/keys",bitbucket:"https://bitbucket.org/account/settings/ssh-keys/"}[t.provider];if(!x&&t.customProvider){let R=`https://${t.customProvider.domain}`;t.customProvider.type==="gitlab"?x=`${R}/-/profile/keys`:t.customProvider.type==="github"?x=`${R}/settings/keys`:t.customProvider.type==="bitbucket"?x=`${R}/account/settings/ssh-keys/`:x=R;}console.log(y.bold(`
Setup Instructions:
`)),console.log("1. Copy the SSH key above"),console.log("2. Open your browser to:"),console.log(y.cyan(` ${x}`)),console.log("3. Add a new SSH key with a descriptive title"),console.log(` Suggested title: ${y.green(`gitm (${e})`)}`);let $=ue(e),v=[{id:"copy",label:"Copy SSH key to clipboard",completed:$.copyCompleted},{id:"browser",label:"Open browser to add key",completed:$.browserCompleted},{id:"add",label:"Add SSH key to provider",completed:$.addCompleted},{id:"test",label:"Test SSH connection",completed:$.testCompleted}];v.some(R=>R.completed)&&m("Resuming authentication setup...");let N=!1;for(;!N;){if(console.log(y.bold(`
\u{1F4CB} Authentication Checklist:
`)),v.forEach(g=>{let te=g.completed?y.green("\u2713"):y.gray("\u2610"),vt=g.completed?y.strikethrough.gray(g.label):g.label;console.log(` ${te} ${vt}`);}),N=v.every(g=>g.completed),N){m("Authentication setup completed successfully.");break}let R=[];(o=v.find(g=>g.id==="copy"))!=null&&o.completed||R.push({name:"\u{1F4CB} Copy SSH key to clipboard",value:"copy"}),(r=v.find(g=>g.id==="browser"))!=null&&r.completed||R.push({name:"\u{1F310} Open browser to add key",value:"browser"}),(i=v.find(g=>g.id==="add"))!=null&&i.completed||R.push({name:"\u2705 I've added the key to my provider",value:"add"}),!((n=v.find(g=>g.id==="test"))!=null&&n.completed)&&((s=v.find(g=>g.id==="add"))!=null&&s.completed)&&R.push({name:"\u{1F527} Test SSH connection",value:"test"}),!((a=v.find(g=>g.id==="test"))!=null&&a.completed)&&((p=v.find(g=>g.id==="add"))!=null&&p.completed)&&R.push({name:"\u23ED\uFE0F Skip test (network issues)",value:"skip-test"}),R.push({name:"\u274C Exit (complete later)",value:"exit"});let{action:St}=await $e.prompt([{type:"list",name:"action",message:`
What would you like to do next?`,choices:R}]);switch(St){case "copy":try{await It(f),v.find(g=>g.id==="copy").completed=!0,M(e,{copyCompleted:!0}),m("SSH key copied to clipboard");}catch(g){c(g.message);}break;case "browser":try{await Ot(x),v.find(g=>g.id==="browser").completed=!0,M(e,{browserCompleted:!0}),m("Opening browser...");}catch{c("Could not open browser. Please open manually:"),h(y.cyan(x),"plain"),v.find(g=>g.id==="browser").completed=!0,M(e,{browserCompleted:!0});}break;case "add":v.find(g=>g.id==="add").completed=!0,M(e,{addCompleted:!0}),m("SSH key successfully added to provider.");break;case "test":{console.log();try{await W(e,t.provider,t.sshKeyPath,t.customProvider);}catch(te){c(`Failed to update SSH config: ${te.message}`);}await Nt(e,t.provider,t.customProvider)?(v.find(te=>te.id==="test").completed=!0,M(e,{testCompleted:!0})):(c("Connection test failed. Please check:"),l("- Is the SSH key correctly added to your provider?"),l('- Did you use the entire public key including "ssh-ed25519"?'));break}case "skip-test":console.log(y.yellow(`
Skipping SSH test due to network issues.`)),l("You can verify the connection later by:"),l("1. Running: gitm verify "+e),l("2. Or trying to clone/push to a repository"),v.find(g=>g.id==="test").completed=!0,M(e,{testCompleted:!0});break;case "exit":console.log(y.yellow(`
Exiting... Complete the remaining steps later.`)),console.log(y.gray('Run "gitm auth '+e+'" to continue setup.'));return}}}catch(f){c(`Failed to read SSH key: ${f.message}`);}}async function It(e){let t=process.platform;try{if(t==="darwin"){let o=ge.join(ke.tmpdir(),`gitm-clipboard-${Date.now()}.tmp`);await V.writeFile(o,e,"utf-8");try{await A("pbcopy",[],{input:await V.readFile(o)});}finally{await V.unlink(o).catch(()=>{});}}else if(t==="linux")try{await A("xclip",["-selection","clipboard"],{input:Buffer.from(e)});}catch{await A("xsel",["--clipboard","--input"],{input:Buffer.from(e)});}else t==="win32"&&await A("clip",[],{input:Buffer.from(e)});}catch{throw new Error("Could not copy to clipboard. Please copy manually.")}}async function Ot(e){let t=process.platform;try{new URL(e);}catch{throw new Error("Invalid URL")}try{t==="darwin"?await A("open",[e]):t==="linux"?await A("xdg-open",[e]):t==="win32"&&await A("cmd",["/c","start","",e]);}catch{throw new Error("Could not open browser")}}async function Nt(e,t,o){let r;o?r=o.domain:r=t==="bitbucket"?"bitbucket.org":`${t}.com`;let n=["-o","ConnectTimeout=10","-T",`git@${`${r.replace(/\./g,"-")}-${e}`}`];try{let{stdout:s,stderr:a}=await A("ssh",n),p=s||a;return p.includes("successfully authenticated")||p.includes("Welcome to GitLab")||p.includes("logged in as")?(m("SSH authentication successful"),h(y.gray(p.trim()),"plain"),!0):(c("SSH authentication failed"),h(y.gray(p.trim()),"plain"),!1)}catch(s){let a=s.stdout||s.stderr||s.message;if(a.includes("successfully authenticated")||a.includes("Welcome to GitLab")||a.includes("logged in as"))return m("SSH authentication successful"),h(y.gray(a.trim()),"plain"),true;if(a.includes("Operation timed out")||a.includes("Connection timed out")||a.includes("port 22")){c("SSH connection timed out - Port 22 may be blocked"),console.log(),l("This often happens on corporate networks or certain ISPs."),l("Alternative solutions:"),l("1. Try using HTTPS instead of SSH for this repository"),l("2. Configure SSH to use port 443:");let p=ge.join(ke.homedir(),".ssh","config"),f=ge.join(ke.homedir(),".ssh",`gitm_${e}_${t}`);return h(y.cyan(` Add this to ${p}:`),"plain"),h(y.gray(` Host ${t}.com-${e}`),"plain"),h(y.gray(` HostName ssh.${t}.com`),"plain"),h(y.gray(" Port 443"),"plain"),h(y.gray(" User git"),"plain"),h(y.gray(` IdentityFile ${f}`),"plain"),l("3. Check if you're behind a firewall or proxy"),false}else return a.includes("Permission denied")?(c("SSH authentication failed - Permission denied"),h(y.gray(a.trim()),"plain"),false):(c("SSH authentication failed"),h(y.gray(a.trim()),"plain"),false)}}u();O();j();U();oe();async function gt(e){if(!G(e)){c("Invalid profile name.");return}let t=E(e);if(!t){c(`Account '${e}' not found`);return}console.log(Q(`Verifying account: ${e}`));let o=true;console.log(y.cyan(`
1. SSH Key Status:`)),await ve(t.sshKeyPath)?(m("SSH key pair exists"),C(` Private key: ${t.sshKeyPath}`),C(` Public key: ${t.sshKeyPath}.pub`)):(c("SSH key pair missing"),l(" Run: gitm add "+e+" to regenerate"),o=false),console.log(y.cyan(`
2. SSH Config:`));try{let n=await V.readFile(B,"utf-8"),s=`${t.provider}.com-${e}`;n.includes(s)?m(`SSH config contains host: ${s}`):(S("SSH config entry missing"),l(" Run: gitm use "+e+" in a repository to fix"),o=!1);}catch{S("SSH config file not found"),o=false;}console.log(y.cyan(`
3. SSH Connection Test:`));let i=t.provider==="bitbucket"?`${t.provider}.org-${e}`:`${t.provider}.com-${e}`;try{let{stderr:n}=await A("ssh",["-o","ConnectTimeout=10","-T",`git@${i}`]);n.includes("successfully authenticated")||n.includes("Welcome to GitLab")||n.includes("logged in as")?(m("SSH authentication working"),C(` ${n.trim()}`)):(S("SSH authentication not verified"),l(" Have you added your SSH key to "+t.provider+"?"),l(" Run: gitm auth "+e),o=!1);}catch(n){let s=n.stderr||n.message;s.includes("successfully authenticated")||s.includes("Welcome to GitLab")||s.includes("logged in as")?(m("SSH authentication working"),C(` ${s.trim()}`)):s.includes("Operation timed out")||s.includes("Connection timed out")||s.includes("port 22")?(c("SSH connection timed out - Port 22 may be blocked"),C(" This is often due to corporate firewalls or ISP restrictions"),C(' See "gitm auth '+e+'" for alternative solutions'),o=false):s.includes("Permission denied")?(c("SSH authentication failed"),C(" Your SSH key is not added to "+t.provider),l(" Run: gitm auth "+e),o=false):(S("Could not verify SSH connection"),C(` ${s.trim()}`),o=false);}console.log(y.cyan(`
4. SSH Agent:`));try{let{stdout:n}=await A("ssh-add",["-l"]);n.includes(t.sshKeyPath)?m("SSH key loaded in agent"):(S("SSH key not in agent"),l(" Run: ssh-add "+t.sshKeyPath));}catch{S("SSH agent not running or no keys loaded");}console.log(y.bold(`
`+(o?"\u2705 Summary:":"\u26A0\uFE0F Summary:"))),o?m(`Account '${e}' is fully configured and working`):S(`Account '${e}' needs attention. See above for details.`);}u();O();U();async function pt(){let e=F(),t=Object.entries(e);if(t.length===0){l("No custom providers configured"),l("Add a custom provider with: gitm provider add");return}console.log(y.bold(`
Custom Providers:
`)),t.forEach(([o,r])=>{console.log(y.cyan(`${o}:`)),console.log(` Name: ${r.name}`),console.log(` Host: ${r.host}`),console.log(` SSH Host: ${r.sshHost}`),r.sshPort&&r.sshPort!==22&&console.log(` SSH Port: ${r.sshPort}`),console.log();});}async function ft(e){if(e){if(!G(e)){c("Invalid provider key. Use only letters, numbers, dash, and underscore.");return}if(ce(e)){c(`Provider '${e}' already exists`);return}}else {let{providerKey:i}=await $e.prompt([{type:"input",name:"providerKey",message:"Provider key (e.g., gitlab-self):",validate:n=>G(n)?ce(n)?`Provider '${n}' already exists`:true:"Invalid key. Use only letters, numbers, dash, and underscore."}]);e=i;}let t=await $e.prompt([{type:"input",name:"name",message:"Provider display name:",default:"Self-hosted Git",validate:i=>i.trim()!==""||"Name is required"},{type:"list",name:"type",message:"Provider type:",choices:[{name:"GitLab (self-hosted)",value:"gitlab"},{name:"GitHub Enterprise",value:"github"},{name:"Bitbucket Server",value:"bitbucket"},{name:"Gitea",value:"gitea"},{name:"Other",value:"other"}]},{type:"input",name:"domain",message:"Domain (e.g., git.company.com):",validate:i=>/^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])*$/.test(i)||"Please enter a valid domain"},{type:"number",name:"sshPort",message:"SSH port:",default:22,validate:i=>i>0&&i<65536||"Please enter a valid port number"}]),o=[new RegExp(`${t.domain.replace(/\./g,"\\.")}[:/]([^/]+)\\/(.+?)(?:\\.git)?$`),new RegExp(`^git@${t.domain.replace(/\./g,"\\.")}:([^/]+)\\/(.+?)(?:\\.git)?$`)],r={name:t.name,host:t.domain,sshHost:t.domain,sshPort:t.sshPort!==22?t.sshPort:void 0,urlPatterns:o,isCustom:true};Ve(e,r),m(`Custom provider '${e}' added successfully`),l(`
You can now use this provider when adding accounts:`),l(`gitm add <profile> --provider ${e}`);}async function ht(e){if(!ce(e)){c(`Provider '${e}' not found`);return}let{confirm:t}=await $e.prompt([{type:"confirm",name:"confirm",message:`Are you sure you want to remove provider '${e}'?`,default:false}]);t&&(We(e),m(`Provider '${e}' removed successfully`));}async function yt(e){let o=F()[e];if(!o){c(`Provider '${e}' not found`);return}console.log(y.bold(`
Provider: ${e}
`)),console.log(`Name: ${o.name}`),console.log(`Host: ${o.host}`),console.log(`SSH Host: ${o.sshHost}`),o.sshPort&&o.sshPort!==22&&console.log(`SSH Port: ${o.sshPort}`),console.log(`
URL Patterns:`),o.urlPatterns.forEach((r,i)=>{console.log(` ${i+1}. ${r.source}`);});}ae();var I=new Command;I.name("gitm").description("Seamlessly manage multiple git accounts").version(Y);I.command("add <profile>").description("Add a new git account").option("--provider <provider>","Use a specific provider (including custom ones)").action(Je);I.command("list").alias("ls").description("List all configured accounts").action(Xe);I.command("use <profile>").description("Set git account for current repository").action(ot);I.command("init").description("Auto-detect and set account for current repository").option("--no-ssh","Keep HTTPS URLs instead of converting to SSH").action(rt);I.command("clone <url>").description("Clone repository with correct account").option("-d, --directory <dir>","Directory to clone into").option("--no-ssh","Keep HTTPS URLs instead of converting to SSH").action(nt);I.command("remove <profile>").alias("rm").description("Remove a git account").action(ct);I.command("status").description("Show current account for repository").action(ut);I.command("auth <profile>").description("Setup authentication for an account").action(mt);I.command("verify <profile>").description("Verify account configuration and authentication").action(gt);var me=I.command("provider").description("Manage custom git providers");me.command("list").alias("ls").description("List all custom providers").action(pt);me.command("add [key]").description("Add a custom provider").action(ft);me.command("remove <key>").alias("rm").description("Remove a custom provider").action(ht);me.command("show <key>").description("Show details of a custom provider").action(yt);I.exitOverride();try{I.parse();}catch(e){e.code==="commander.version"||e.code==="commander.help"||e.code==="commander.helpDisplayed"?process.exit(0):(console.error(y.red("Error:"),e.message||e),process.exit(1));}