@nalvietnam/avatar-cli 1.14.1 → 1.14.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/dist/index.js CHANGED
@@ -8,7 +8,8 @@ import{Command as Tl}from"commander";import{promises as Ho}from"fs";import{join
8
8
  `),dim:t=>process.stdout.write(`${u.dim(t)}
9
9
  `),plain:t=>process.stdout.write(`${t}
10
10
  `)};function O(t){return Xr({text:t,spinner:"dots",isEnabled:process.stdout.isTTY??!1}).start()}function yt(t){let e=Date.now(),n=O(`${t} (0:00)`),i=()=>{let s=Math.floor((Date.now()-e)/1e3),a=Math.floor(s/60),c=s%60;return`${a}:${String(c).padStart(2,"0")}`},r=setInterval(()=>{n.text=`${t} (${i()})`},1e3);return{succeed:s=>{clearInterval(r),n.succeed(`${s} (${i()})`)},fail:s=>{clearInterval(r),n.fail(`${s} (${i()})`)},stop:()=>{clearInterval(r),n.stop()}}}var vn=6e4,Qr="ok";function ve(){let t=be("claude",["auth","status"],{encoding:"utf8"});if(t.error||t.status!==0)return{state:"not-authenticated"};let e=(t.stdout||"").trim();if(!e.startsWith("{"))return{state:"authenticated"};try{let n=JSON.parse(e);return n.loggedIn!==!0?{state:"not-authenticated"}:{state:"authenticated",email:n.email,subscriptionType:n.subscriptionType,apiProvider:n.apiProvider}}catch{return{state:"authenticated"}}}function xe(){o.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp Claude Code (browser s\u1EBD m\u1EDF)...");let t=be("claude",["auth","login"],{stdio:"inherit"});if(t.status!==0)throw new Error(`claude auth login th\u1EA5t b\u1EA1i (exit ${t.status}). Th\u1EED 'claude auth login' tay r\u1ED3i ch\u1EA1y l\u1EA1i.`);o.success("\u0110\xE3 \u0111\u0103ng nh\u1EADp Claude Code")}function Zr(t){let e=t.toLowerCase();return e.includes("credit_balance_too_low")||e.includes("credit balance too low")?"credit_balance_too_low":e.includes("insufficient_quota")||e.includes("insufficient quota")||e.includes("quota_exceeded")||e.includes("quota exceeded")||e.includes("usage limit")||e.includes("you've used all")?"insufficient_quota":e.includes("401")||e.includes("invalid authentication")||e.includes("authentication credentials")||e.includes("failed to authenticate")||e.includes("authentication failed")||e.includes("unauthorized")?"auth-expired":e.includes("invalid_api_key")||e.includes("invalid api key")?"invalid_api_key":e.includes("rate_limit")||e.includes("rate limit")||e.includes("429")?"rate_limit":"unknown"}function xn(t){switch(t){case"auth-expired":return"Token Claude Code \u0111\xE3 h\u1EBFt h\u1EA1n/b\u1ECB revoke. Ch\u1EA1y: `claude auth logout && claude auth login`.";case"credit_balance_too_low":case"insufficient_quota":return"H\u1EBFt quota subscription. Upgrade plan ho\u1EB7c d\xF9ng LLMLite (avatar ai setup \u2192 ch\u1ECDn LLMLite).";case"invalid_api_key":return"API key invalid. Re-login: `claude auth login`.";case"rate_limit":return"B\u1ECB rate limit t\u1EA1m th\u1EDDi. Ch\u1EDD v\xE0i ph\xFAt r\u1ED3i ch\u1EA1y `avatar ai setup`.";case"timeout":return"Timeout 60s: (1) m\u1EA1ng VN ch\u1EADm \u2014 th\u1EED VPN, (2) Anthropic API spike \u2014 retry v\xE0i ph\xFAt, (3) token v\u1EABn auth nh\u01B0ng quota h\u1EBFt \xE2m th\u1EA7m \u2014 check t\u1EA1i claude.ai/settings/usage.";default:return"L\u1ED7i ch\u01B0a bi\u1EBFt. Xem stderr \u1EDF tr\xEAn + ch\u1EA1y `claude --print ok` tay \u0111\u1EC3 debug."}}function Se(){let t=be("claude",["--print",Qr],{encoding:"utf8",timeout:vn,stdio:["ignore","pipe","pipe"]});if(t.signal==="SIGTERM"||t.status===143||t.error?.code==="ETIMEDOUT")return{ok:!1,reason:"timeout",detail:`claude --print > ${vn/1e3}s (m\u1EA1ng ch\u1EADm / API rate limit / token revoked kh\xF4ng return error)`};let n=t.stderr||"",i=t.stdout||"";if(t.status===0)return{ok:!0};let r=i.trim(),s=n.toLowerCase();if(r.length>20&&!s.includes("error")&&!s.includes("limit")&&!s.includes("quota")&&!s.includes("401"))return o.warn(`claude --print exit=${t.status} nh\u01B0ng c\xF3 response (${r.length} chars). Accept v\u1EDBi caution.`),{ok:!0};let a=Zr(`${n}
11
- ${i}`);return a==="unknown"&&(o.warn(`[debug] claude --print exit=${t.status} signal=${t.signal??"none"}`),n.trim()&&o.warn(`[debug] stderr: ${n.slice(0,500)}`),i.trim()&&o.warn(`[debug] stdout: ${i.slice(0,300)}`)),{ok:!1,reason:a,detail:n.slice(0,500)||i.slice(0,500)}}import{spawnSync as Sn}from"child_process";import{platform as to}from"os";function Y(){let t=to();return t==="darwin"||t==="linux"||t==="win32"?t:"unsupported"}var eo=5e3,no=/(\d+\.\d+\.\d+)/;function io(){let e=Y()==="win32"?"where":"which",n=Sn(e,["claude"],{encoding:"utf8"});if(n.error||n.status!==0)return null;let i=(n.stdout||"").trim();return i?i.split(/\r?\n/)[0].trim():null}function ro(){let t=Sn("claude",["--version"],{encoding:"utf8",timeout:eo});if(t.error||t.status!==0)return null;let e=(t.stdout||"").trim();return no.exec(e)?.[1]??null}var J=null;function bt(){if(J!==null)return J;let t=io();return t?(J={installed:!0,version:ro(),path:t},J):(J={installed:!1,version:null,path:null},J)}function Mt(){J=null}import{spawnSync as oo}from"child_process";var Cn=300*1e3,An="@anthropic-ai/claude-code",X=class extends Error{reason;exitCode;constructor(e,n,i=null){super(n),this.name="InstallClaudeCodeError",this.reason=e,this.exitCode=i}};function so(t,e){let n=e.toLowerCase();return n.includes("eacces")||n.includes("permission denied")?new X("permission-denied",`npm install -g c\u1EA7n quy\u1EC1n. Th\u1EED: sudo npm install -g ${An} ho\u1EB7c fix npm prefix (npm config set prefix ~/.npm-global).`,t):n.includes("enospc")||n.includes("no space")?new X("disk-full","\u0110\u0129a \u0111\u1EA7y. Free disk space r\u1ED3i th\u1EED l\u1EA1i.",t):new X("generic",`npm install th\u1EA5t b\u1EA1i (exit ${t??"null"}). Xem log npm ph\xEDa tr\xEAn.`,t)}function Pn(){o.info("\u0110ang c\xE0i Claude Code qua npm (c\xF3 th\u1EC3 m\u1EA5t 1-2 ph\xFAt)...");let t=oo("npm",["install","-g",An],{stdio:["inherit","inherit","pipe"],timeout:Cn,encoding:"utf8"});if(t.signal==="SIGTERM")throw new X("timeout",`npm install timeout sau ${Cn/1e3}s. Check m\u1EA1ng r\u1ED3i th\u1EED l\u1EA1i.`,null);if(t.status!==0)throw t.stderr&&process.stderr.write(t.stderr),so(t.status,t.stderr||"");Mt();let e=bt();if(!e.installed||!e.path)throw new X("binary-not-in-path","npm c\xE0i xong nh\u01B0ng `claude` kh\xF4ng trong PATH. Reload shell (source ~/.zshrc) ho\u1EB7c th\xEAm npm global bin v\xE0o PATH.",null);return o.success(`\u0110\xE3 c\xE0i Claude Code${e.version?` v${e.version}`:""} t\u1EA1i ${e.path}`),{version:e.version,path:e.path}}import{readFileSync as ao}from"fs";import{homedir as co}from"os";import{join as lo}from"path";import{select as $n}from"@inquirer/prompts";function uo(){return lo(co(),".claude","settings.json")}function Ce(){let t=uo(),e;try{e=ao(t,"utf8")}catch{return{exists:!1,hasBaseUrl:!1,hasToken:!1}}let n;try{n=JSON.parse(e)}catch{return{exists:!0,hasBaseUrl:!1,hasToken:!1}}let i=n.env||{},r=typeof i.ANTHROPIC_BASE_URL=="string"?i.ANTHROPIC_BASE_URL:void 0,s=typeof i.ANTHROPIC_AUTH_TOKEN=="string"&&i.ANTHROPIC_AUTH_TOKEN.length>0,a=typeof n.model=="string"?n.model:void 0;return{exists:!0,hasBaseUrl:!!r,baseUrl:r,hasToken:s,model:a,rawSettings:n}}async function Tn(t=Ce()){return t.exists&&t.hasBaseUrl&&t.hasToken&&await $n({message:`Ph\xE1t hi\u1EC7n AI config global (base URL: ${t.baseUrl}). D\xF9ng cho project n\xE0y?`,choices:[{name:"a. Yes \u2014 copy config global v\xE0o .claude/settings.json (per-project)",value:"use-global"},{name:"b. No \u2014 setup ri\xEAng (ch\u1ECDn provider kh\xE1c)",value:"setup-fresh"}]})==="use-global"?"use-global":await $n({message:"Ch\u1ECDn provider cho AI features:",choices:[{name:"1. Claude Code Subscription (d\xF9ng quota c\xE1 nh\xE2n Anthropic, OAuth login)",value:"subscription"},{name:"2. LLMLite API key (llm.nal.vn \u2014 NAL gateway, key sk-...)",value:"llmlite"},{name:"3. Anthropic API key tr\u1EF1c ti\u1EBFp (console.anthropic.com, key sk-ant-...)",value:"anthropic"}]})}import{password as po,select as mo}from"@inquirer/prompts";var Ut="https://api.anthropic.com",go="2023-06-01",Rn=1e4;function ho(t){return t.length<=12?"sk-ant-***":`${t.slice(0,7)}...${t.slice(-4)}`}function fo(t){let e=t.trim();return e.length===0?"API key b\u1EAFt bu\u1ED9c":e.startsWith("sk-ant-")?!0:"Anthropic API key th\u01B0\u1EDDng b\u1EAFt \u0111\u1EA7u b\u1EB1ng 'sk-ant-' (l\u1EA5y t\u1EEB console.anthropic.com)."}async function ko(){return await po({message:"Anthropic API key (sk-ant-..., \u1EA9n input):",mask:"*",validate:fo})}async function wo(t){let e=new AbortController,n=setTimeout(()=>e.abort(),Rn);try{let i=await fetch(`${Ut}/v1/models`,{method:"GET",headers:{"x-api-key":t,"anthropic-version":go,Accept:"application/json"},signal:e.signal});if(i.status===401)throw new Error("API key invalid (HTTP 401). Check key tr\xEAn console.anthropic.com.");if(i.status===403)throw new Error("API key b\u1ECB reject (HTTP 403). Key c\xF3 th\u1EC3 \u0111\xE3 b\u1ECB revoke ho\u1EB7c thi\u1EBFu permission.");if(i.status===429)throw new Error("Rate limit (HTTP 429). Ch\u1EDD v\xE0i gi\xE2y r\u1ED3i th\u1EED l\u1EA1i.");if(!i.ok)throw new Error(`Fetch models th\u1EA5t b\u1EA1i (HTTP ${i.status}).`);let s=((await i.json()).data||[]).map(a=>typeof a.id=="string"?a.id:null).filter(a=>a!==null);if(s.length===0)throw new Error("Anthropic tr\u1EA3 v\u1EC1 list r\u1ED7ng. Li\xEAn h\u1EC7 support ho\u1EB7c check account.");return s}catch(i){throw i.name==="AbortError"?new Error(`Connect ${Ut} timeout sau ${Rn/1e3}s.`):i}finally{clearTimeout(n)}}async function yo(t){if(t.length===1){let n=t[0];return o.info(`Auto-pick model: ${n} (ch\u1EC9 1 model available)`),n}let e=[...t].sort((n,i)=>{let r=s=>{let a=s.toLowerCase();return a.includes("sonnet")?0:a.includes("opus")?1:a.includes("haiku")?2:3};return r(n)-r(i)});return await mo({message:"Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",choices:e.map(n=>({name:n,value:n}))})}async function En(){let t=await ko();o.info(`Verify key (${ho(t)}) qua ${Ut}/v1/models...`);let e=await wo(t);o.success(`Endpoint OK \u2014 ${e.length} models available`);let n=await yo(e);return{apiKey:t,baseUrl:Ut,model:n}}import{input as bo,password as vo,select as xo}from"@inquirer/prompts";var So="https://llm.nal.vn",_n=1e4;function N(t){return t.length<=8?"sk-***":`${t.slice(0,3)}...${t.slice(-4)}`}async function Co(){return await vo({message:"LLMLite API key (\u1EA9n input):",mask:"*",validate:t=>t.trim().length>0?!0:"API key b\u1EAFt bu\u1ED9c"})}async function Ao(t=So){return(await bo({message:"LLMLite base URL:",default:t,validate:n=>/^https?:\/\//.test(n)?!0:"Ph\u1EA3i l\xE0 URL h\u1EE3p l\u1EC7 (http/https)"})).replace(/\/+$/,"")}async function Po(t,e){let n=new AbortController,i=setTimeout(()=>n.abort(),_n);try{let r=await fetch(`${t}/v1/models`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:n.signal});if(r.status===401||r.status===403)throw new Error(`API key invalid (HTTP ${r.status}).`);if(r.status===404)throw new Error(`Endpoint /v1/models kh\xF4ng t\u1ED3n t\u1EA1i tr\xEAn ${t}.`);if(!r.ok)throw new Error(`Fetch models th\u1EA5t b\u1EA1i (HTTP ${r.status}).`);let a=((await r.json()).data||[]).map(c=>typeof c.id=="string"?c.id:null).filter(c=>c!==null);if(a.length===0)throw new Error("LLMLite tr\u1EA3 v\u1EC1 list r\u1ED7ng. Li\xEAn h\u1EC7 admin NAL.");return a}catch(r){throw r.name==="AbortError"?new Error(`Connect ${t} timeout sau ${_n/1e3}s.`):r}finally{clearTimeout(i)}}async function $o(t){let e=t.filter(i=>i.toLowerCase().includes("claude"));if(e.length===1){let i=e[0];return o.info(`Auto-pick model: ${i} (ch\u1EC9 1 claude alias tr\xEAn endpoint)`),i}let n=e.length>0?e:t;return await xo({message:"Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",choices:n.map(i=>({name:i,value:i}))})}async function In(){let t=await Co(),e=await Ao();o.info(`Verify key (${N(t)}) qua ${e}/v1/models...`);let n=await Po(e,t);o.success(`Endpoint OK \u2014 ${n.length} models available`);let i=await $o(n);return{apiKey:t,baseUrl:e,model:i}}import{promises as To}from"fs";import{join as Ro}from"path";var Ae=384;function Eo(t){return Ro(t,".claude","settings.json")}async function _o(t){if(!await d(t))return{};try{return await C(t)}catch(e){throw new Error(`Kh\xF4ng parse \u0111\u01B0\u1EE3c ${t} (JSON l\u1ED7i): ${e.message}. Backup file r\u1ED3i x\xF3a \u0111\u1EC3 Avatar t\u1EA1o l\u1EA1i.`)}}function Io(t,e){let{env:n,...i}=t,r={...i,model:e};if(n){let{ANTHROPIC_BASE_URL:s,ANTHROPIC_AUTH_TOKEN:a,ANTHROPIC_API_KEY:c,...l}=n;Object.keys(l).length>0&&(r.env=l)}return r}function Oo(t,e,n,i){let{ANTHROPIC_API_KEY:r,...s}=t.env||{};return{...t,env:{...s,ANTHROPIC_BASE_URL:n,ANTHROPIC_AUTH_TOKEN:e},model:i}}function No(t,e,n,i){let{ANTHROPIC_AUTH_TOKEN:r,...s}=t.env||{};return{...t,env:{...s,ANTHROPIC_BASE_URL:n,ANTHROPIC_API_KEY:e},model:i}}function Go(t,e){let n=e.env||{},i=typeof e.model=="string"?e.model:void 0;return{...t,env:{...t.env||{},...n},...i?{model:i}:{}}}async function Q(t,e){let n=Eo(t),i=await _o(n),r;switch(e.provider){case"subscription":r=Io(i,e.model);break;case"llmlite":r=Oo(i,e.apiKey,e.baseUrl,e.model);break;case"anthropic":r=No(i,e.apiKey,e.baseUrl,e.model);break;case"use-global":r=Go(i,e.sourceSettings);break}await $(n,r,Ae);try{await To.chmod(n,Ae)}catch{}return{path:n,mode:Ae}}var T="sonnet";function On(t){o.warn(`\u{1F512} ${t} key \u0111\xE3 l\u01B0u PLAINTEXT v\xE0o .claude/settings.json.
11
+ ${i}`);return a==="unknown"&&(o.warn(`[debug] claude --print exit=${t.status} signal=${t.signal??"none"}`),n.trim()&&o.warn(`[debug] stderr: ${n.slice(0,500)}`),i.trim()&&o.warn(`[debug] stdout: ${i.slice(0,300)}`)),{ok:!1,reason:a,detail:n.slice(0,500)||i.slice(0,500)}}import{spawnSync as Sn}from"child_process";import{platform as to}from"os";function Y(){let t=to();return t==="darwin"||t==="linux"||t==="win32"?t:"unsupported"}var eo=5e3,no=/(\d+\.\d+\.\d+)/;function io(){let e=Y()==="win32"?"where":"which",n=Sn(e,["claude"],{encoding:"utf8"});if(n.error||n.status!==0)return null;let i=(n.stdout||"").trim();return i?i.split(/\r?\n/)[0].trim():null}function ro(){let t=Sn("claude",["--version"],{encoding:"utf8",timeout:eo});if(t.error||t.status!==0)return null;let e=(t.stdout||"").trim();return no.exec(e)?.[1]??null}var J=null;function bt(){if(J!==null)return J;let t=io();return t?(J={installed:!0,version:ro(),path:t},J):(J={installed:!1,version:null,path:null},J)}function Mt(){J=null}import{spawnSync as oo}from"child_process";var Cn=300*1e3,An="@anthropic-ai/claude-code",X=class extends Error{reason;exitCode;constructor(e,n,i=null){super(n),this.name="InstallClaudeCodeError",this.reason=e,this.exitCode=i}};function so(t,e){let n=e.toLowerCase();return n.includes("eacces")||n.includes("permission denied")?new X("permission-denied",`npm install -g c\u1EA7n quy\u1EC1n. Th\u1EED: sudo npm install -g ${An} ho\u1EB7c fix npm prefix (npm config set prefix ~/.npm-global).`,t):n.includes("enospc")||n.includes("no space")?new X("disk-full","\u0110\u0129a \u0111\u1EA7y. Free disk space r\u1ED3i th\u1EED l\u1EA1i.",t):new X("generic",`npm install th\u1EA5t b\u1EA1i (exit ${t??"null"}). Xem log npm ph\xEDa tr\xEAn.`,t)}function Pn(){o.info("\u0110ang c\xE0i Claude Code qua npm (c\xF3 th\u1EC3 m\u1EA5t 1-2 ph\xFAt)...");let t=oo("npm",["install","-g",An],{stdio:["inherit","inherit","pipe"],timeout:Cn,encoding:"utf8"});if(t.signal==="SIGTERM")throw new X("timeout",`npm install timeout sau ${Cn/1e3}s. Check m\u1EA1ng r\u1ED3i th\u1EED l\u1EA1i.`,null);if(t.status!==0)throw t.stderr&&process.stderr.write(t.stderr),so(t.status,t.stderr||"");Mt();let e=bt();if(!e.installed||!e.path)throw new X("binary-not-in-path","npm c\xE0i xong nh\u01B0ng `claude` kh\xF4ng trong PATH. Reload shell (source ~/.zshrc) ho\u1EB7c th\xEAm npm global bin v\xE0o PATH.",null);return o.success(`\u0110\xE3 c\xE0i Claude Code${e.version?` v${e.version}`:""} t\u1EA1i ${e.path}`),{version:e.version,path:e.path}}import{readFileSync as ao}from"fs";import{homedir as co}from"os";import{join as lo}from"path";import{select as $n}from"@inquirer/prompts";function uo(){return lo(co(),".claude","settings.json")}function Ce(){let t=uo(),e;try{e=ao(t,"utf8")}catch{return{exists:!1,hasBaseUrl:!1,hasToken:!1}}let n;try{n=JSON.parse(e)}catch{return{exists:!0,hasBaseUrl:!1,hasToken:!1}}let i=n.env||{},r=typeof i.ANTHROPIC_BASE_URL=="string"?i.ANTHROPIC_BASE_URL:void 0,s=typeof i.ANTHROPIC_AUTH_TOKEN=="string"&&i.ANTHROPIC_AUTH_TOKEN.length>0,a=typeof n.model=="string"?n.model:void 0;return{exists:!0,hasBaseUrl:!!r,baseUrl:r,hasToken:s,model:a,rawSettings:n}}async function Tn(t=Ce()){return t.exists&&t.hasBaseUrl&&t.hasToken&&await $n({message:`Ph\xE1t hi\u1EC7n AI config global (base URL: ${t.baseUrl}). D\xF9ng cho project n\xE0y?`,choices:[{name:"a. Yes \u2014 copy config global v\xE0o .claude/settings.json (per-project)",value:"use-global"},{name:"b. No \u2014 setup ri\xEAng (ch\u1ECDn provider kh\xE1c)",value:"setup-fresh"}]})==="use-global"?"use-global":await $n({message:"Ch\u1ECDn provider cho AI features:",choices:[{name:"1. Claude Code Subscription (d\xF9ng quota c\xE1 nh\xE2n Anthropic, OAuth login)",value:"subscription"},{name:"2. LLMLite API key (llm.nal.vn \u2014 NAL gateway, key sk-...)",value:"llmlite"},{name:"3. Anthropic API key tr\u1EF1c ti\u1EBFp (console.anthropic.com, key sk-ant-...)",value:"anthropic"}]})}import{password as po,select as mo}from"@inquirer/prompts";var Ut="https://api.anthropic.com",go="2023-06-01",Rn=1e4;function ho(t){return t.length<=12?"sk-ant-***":`${t.slice(0,7)}...${t.slice(-4)}`}function fo(t){let e=t.trim();return e.length===0?"API key b\u1EAFt bu\u1ED9c":e.startsWith("sk-ant-")?!0:"Anthropic API key th\u01B0\u1EDDng b\u1EAFt \u0111\u1EA7u b\u1EB1ng 'sk-ant-' (l\u1EA5y t\u1EEB console.anthropic.com)."}async function ko(){return await po({message:"Anthropic API key (sk-ant-..., \u1EA9n input):",mask:"*",validate:fo})}async function wo(t){let e=new AbortController,n=setTimeout(()=>e.abort(),Rn);try{let i=await fetch(`${Ut}/v1/models`,{method:"GET",headers:{"x-api-key":t,"anthropic-version":go,Accept:"application/json"},signal:e.signal});if(i.status===401)throw new Error("API key invalid (HTTP 401). Check key tr\xEAn console.anthropic.com.");if(i.status===403)throw new Error("API key b\u1ECB reject (HTTP 403). Key c\xF3 th\u1EC3 \u0111\xE3 b\u1ECB revoke ho\u1EB7c thi\u1EBFu permission.");if(i.status===429)throw new Error("Rate limit (HTTP 429). Ch\u1EDD v\xE0i gi\xE2y r\u1ED3i th\u1EED l\u1EA1i.");if(!i.ok)throw new Error(`Fetch models th\u1EA5t b\u1EA1i (HTTP ${i.status}).`);let s=((await i.json()).data||[]).map(a=>typeof a.id=="string"?a.id:null).filter(a=>a!==null);if(s.length===0)throw new Error("Anthropic tr\u1EA3 v\u1EC1 list r\u1ED7ng. Li\xEAn h\u1EC7 support ho\u1EB7c check account.");return s}catch(i){throw i.name==="AbortError"?new Error(`Connect ${Ut} timeout sau ${Rn/1e3}s.`):i}finally{clearTimeout(n)}}async function yo(t){if(t.length===1){let n=t[0];return o.info(`Auto-pick model: ${n} (ch\u1EC9 1 model available)`),n}let e=[...t].sort((n,i)=>{let r=s=>{let a=s.toLowerCase();return a.includes("sonnet")?0:a.includes("opus")?1:a.includes("haiku")?2:3};return r(n)-r(i)});return await mo({message:"Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",choices:e.map(n=>({name:n,value:n}))})}async function En(){let t=await ko();o.info(`Verify key (${ho(t)}) qua ${Ut}/v1/models...`);let e=await wo(t);o.success(`Endpoint OK \u2014 ${e.length} models available`);let n=await yo(e);return{apiKey:t,baseUrl:Ut,model:n}}import{input as bo,password as vo,select as xo}from"@inquirer/prompts";var So="https://llm.nal.vn",_n=1e4;function N(t){return t.length<=8?"sk-***":`${t.slice(0,3)}...${t.slice(-4)}`}async function Co(){return await vo({message:"LLMLite API key (\u1EA9n input):",mask:"*",validate:t=>t.trim().length>0?!0:"API key b\u1EAFt bu\u1ED9c"})}async function Ao(t=So){return(await bo({message:"LLMLite base URL:",default:t,validate:n=>/^https?:\/\//.test(n)?!0:"Ph\u1EA3i l\xE0 URL h\u1EE3p l\u1EC7 (http/https)"})).replace(/\/+$/,"")}async function Po(t,e){let n=new AbortController,i=setTimeout(()=>n.abort(),_n);try{let r=await fetch(`${t}/v1/models`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:n.signal});if(r.status===401||r.status===403)throw new Error(`API key invalid (HTTP ${r.status}).`);if(r.status===404)throw new Error(`Endpoint /v1/models kh\xF4ng t\u1ED3n t\u1EA1i tr\xEAn ${t}.`);if(!r.ok)throw new Error(`Fetch models th\u1EA5t b\u1EA1i (HTTP ${r.status}).`);let a=((await r.json()).data||[]).map(c=>typeof c.id=="string"?c.id:null).filter(c=>c!==null);if(a.length===0)throw new Error("LLMLite tr\u1EA3 v\u1EC1 list r\u1ED7ng. Li\xEAn h\u1EC7 admin NAL.");return a}catch(r){if(r.name==="AbortError"){let c=t.includes("nal.vn")||t.includes("nal-vn")?"\n Hint: llm.nal.vn l\xE0 internal endpoint NAL \u2014 ki\u1EC3m tra VPN NAL \u0111\xE3 b\u1EADt ch\u01B0a, ho\u1EB7c tcp test b\u1EB1ng `curl -v https://llm.nal.vn`.":`
12
+ Hint: check m\u1EA1ng / firewall / VPN, ho\u1EB7c base URL c\xF3 \u0111\xFAng kh\xF4ng.`;throw new Error(`Connect ${t} timeout sau ${_n/1e3}s.${c}`)}let s=r.message||String(r);if(s.toLowerCase().includes("fetch failed")||s.includes("ENOTFOUND")){let c=t.includes("nal.vn")?" (llm.nal.vn c\u1EA7n VPN NAL \u2014 ki\u1EC3m tra VPN \u0111\xE3 b\u1EADt ch\u01B0a)":"";throw new Error(`Network error khi connect ${t}: ${s}${c}`)}throw r}finally{clearTimeout(i)}}async function $o(t){let e=t.filter(i=>i.toLowerCase().includes("claude"));if(e.length===1){let i=e[0];return o.info(`Auto-pick model: ${i} (ch\u1EC9 1 claude alias tr\xEAn endpoint)`),i}let n=e.length>0?e:t;return await xo({message:"Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",choices:n.map(i=>({name:i,value:i}))})}async function In(){let t=await Co(),e=await Ao();o.info(`Verify key (${N(t)}) qua ${e}/v1/models...`);let n=await Po(e,t);o.success(`Endpoint OK \u2014 ${n.length} models available`);let i=await $o(n);return{apiKey:t,baseUrl:e,model:i}}import{promises as To}from"fs";import{join as Ro}from"path";var Ae=384;function Eo(t){return Ro(t,".claude","settings.json")}async function _o(t){if(!await d(t))return{};try{return await C(t)}catch(e){throw new Error(`Kh\xF4ng parse \u0111\u01B0\u1EE3c ${t} (JSON l\u1ED7i): ${e.message}. Backup file r\u1ED3i x\xF3a \u0111\u1EC3 Avatar t\u1EA1o l\u1EA1i.`)}}function Io(t,e){let{env:n,...i}=t,r={...i,model:e};if(n){let{ANTHROPIC_BASE_URL:s,ANTHROPIC_AUTH_TOKEN:a,ANTHROPIC_API_KEY:c,...l}=n;Object.keys(l).length>0&&(r.env=l)}return r}function Oo(t,e,n,i){let{ANTHROPIC_API_KEY:r,...s}=t.env||{};return{...t,env:{...s,ANTHROPIC_BASE_URL:n,ANTHROPIC_AUTH_TOKEN:e},model:i}}function No(t,e,n,i){let{ANTHROPIC_AUTH_TOKEN:r,...s}=t.env||{};return{...t,env:{...s,ANTHROPIC_BASE_URL:n,ANTHROPIC_API_KEY:e},model:i}}function Go(t,e){let n=e.env||{},i=typeof e.model=="string"?e.model:void 0;return{...t,env:{...t.env||{},...n},...i?{model:i}:{}}}async function Q(t,e){let n=Eo(t),i=await _o(n),r;switch(e.provider){case"subscription":r=Io(i,e.model);break;case"llmlite":r=Oo(i,e.apiKey,e.baseUrl,e.model);break;case"anthropic":r=No(i,e.apiKey,e.baseUrl,e.model);break;case"use-global":r=Go(i,e.sourceSettings);break}await $(n,r,Ae);try{await To.chmod(n,Ae)}catch{}return{path:n,mode:Ae}}var T="sonnet";function On(t){o.warn(`\u{1F512} ${t} key \u0111\xE3 l\u01B0u PLAINTEXT v\xE0o .claude/settings.json.
12
13
  File n\xE0y \u0111\u01B0\u1EE3c gitignore t\u1EEB Avatar v1.7.0, nh\u01B0ng workspace c\u0169 c\xF3 th\u1EC3 CH\u01AFA.
13
14
  Ki\u1EC3m tra: grep '.claude/settings.json' .gitignore
14
15
  N\u1EBFu ch\u01B0a c\xF3 \u2192 TH\xCAM NGAY, tr\xE1nh leak key khi commit/push.`)}async function jt(t){try{o.info("Setup AI provider cho workspace...");let e=bt();if(e.installed)o.success(`Claude Code \u0111\xE3 c\xF3${e.version?` v${e.version}`:""}`);else if(o.info("Ch\u01B0a c\xF3 Claude Code \u2014 s\u1EBD t\u1EF1 c\xE0i qua npm."),Pn(),Mt(),e=bt(),!e.installed)throw new Error("C\xE0i Claude Code xong nh\u01B0ng v\u1EABn kh\xF4ng detect \u0111\u01B0\u1EE3c binary.");let n=Ce();switch(await Tn(n)){case"subscription":{let r=ve();if(r.state!=="authenticated"&&(xe(),r=ve()),r.state==="authenticated"&&r.subscriptionType)return await Q(t.workspacePath,{provider:"subscription",model:T}),await f("ai_setup",`provider=subscription,result=ok,plan=${r.subscriptionType},probe=skipped`),o.success(`AI ready \xB7 Subscription (${r.subscriptionType}) \xB7 model=${T}`),{ok:!0,provider:"subscription",model:T};o.dim("Auth status kh\xF4ng tr\u1EA3 subscriptionType \u2014 verify quota (30-60s)...");let s=Se();if(!s.ok&&s.reason==="auth-expired"&&(o.warn("Token Claude Code \u0111\xE3 h\u1EBFt h\u1EA1n. T\u1EF1 \u0111\u1ED9ng re-login..."),xe(),s=Se()),!s.ok&&(s.reason==="timeout"||s.reason==="unknown"))return o.warn(`Probe verify ${s.reason} \u2014 accept trust auth status. Ti\u1EBFp t\u1EE5c.`),s.detail?.trim()&&o.warn(` Chi ti\u1EBFt: ${s.detail.slice(0,200)}`),await Q(t.workspacePath,{provider:"subscription",model:T}),await f("ai_setup",`provider=subscription,result=ok,probe=${s.reason}-soft-pass`),o.success(`AI ready \xB7 Subscription (probe ${s.reason}, soft-pass) \xB7 model=${T}`),{ok:!0,provider:"subscription",model:T};if(!s.ok){let a=s.reason??"unknown";return await f("ai_setup",`provider=subscription,result=no-quota,reason=${a}`),o.warn(`Subscription verify th\u1EA5t b\u1EA1i (${a}).`),s.detail?.trim()&&o.warn(` Chi ti\u1EBFt: ${s.detail.slice(0,200)}`),o.warn(` \u2192 ${xn(a)}`),{ok:!1,reason:`subscription-${a}`,phase:"quota"}}return await Q(t.workspacePath,{provider:"subscription",model:T}),await f("ai_setup","provider=subscription,result=ok"),o.success(`AI ready \xB7 Subscription \xB7 model=${T}`),{ok:!0,provider:"subscription",model:T}}case"llmlite":{let r=await In();return await Q(t.workspacePath,{provider:"llmlite",apiKey:r.apiKey,baseUrl:r.baseUrl,model:r.model}),await f("ai_setup",`provider=llmlite,result=ok,model=${r.model},base=${r.baseUrl}`),o.success(`AI ready \xB7 LLMLite \xB7 model=${r.model} \xB7 ${r.baseUrl}`),On("LLMLite"),{ok:!0,provider:"llmlite",model:r.model}}case"anthropic":{let r=await En();return await Q(t.workspacePath,{provider:"anthropic",apiKey:r.apiKey,baseUrl:r.baseUrl,model:r.model}),await f("ai_setup",`provider=anthropic,result=ok,model=${r.model}`),o.success(`AI ready \xB7 Anthropic Direct \xB7 model=${r.model} \xB7 ${r.baseUrl}`),On("Anthropic Direct API"),{ok:!0,provider:"anthropic",model:r.model}}case"use-global":{if(!n.rawSettings)throw new Error("use-global ch\u1ECDn nh\u01B0ng kh\xF4ng \u0111\u1ECDc \u0111\u01B0\u1EE3c global settings.");return await Q(t.workspacePath,{provider:"use-global",sourceSettings:n.rawSettings}),await f("ai_setup","provider=use-global,result=ok"),o.success(`AI ready \xB7 Copy t\u1EEB global config (${n.baseUrl??"subscription"})`),{ok:!0,provider:"use-global",model:n.model}}}}catch(e){let n=e instanceof Error?e.message:String(e);return o.warn(`AI setup th\u1EA5t b\u1EA1i: ${n}`),o.dim("Workspace v\u1EABn s\u1EB5n s\xE0ng. Setup AI sau qua: avatar ai setup"),await f("ai_setup",`result=failed,error=${n.slice(0,200)}`),{ok:!1,reason:n}}}import{spawnSync as Lo}from"child_process";var Pe=1e4,Nn=3e4,Ln=5,$e="say ok",Gn="2023-06-01";async function Mo(t,e,n){o.info(`Testing LLMLite provider: ${t} (key: ${N(e)})`);let i=new AbortController,r=setTimeout(()=>i.abort(),Pe);try{let s=await fetch(`${t}/v1/models`,{headers:{Authorization:`Bearer ${e}`},signal:i.signal});if(s.status===401||s.status===403)throw new Error(`API key invalid (HTTP ${s.status}). Re-run: avatar ai setup`);if(!s.ok)throw new Error(`Endpoint /v1/models l\u1ED7i (HTTP ${s.status}).`);let c=((await s.json()).data||[]).map(v=>typeof v.id=="string"?v.id:null).filter(v=>v!==null);if(o.success(`Connectivity OK \xB7 ${c.length} models available`),c.length>0){let v=c.slice(0,5).join(", "),P=c.length>5?` ...+${c.length-5} more`:"";o.dim(` Models: ${v}${P}`)}o.info(`Testing chat completion v\u1EDBi model "${n}"...`);let l=await fetch(`${t}/v1/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({model:n,messages:[{role:"user",content:$e}],max_tokens:Ln}),signal:i.signal});if(!l.ok){let v=(await l.text()).slice(0,200);throw new Error(`Chat completion fail (HTTP ${l.status}). ${v}`)}let p=await l.json(),m=typeof p.choices?.[0]?.message?.content=="string"?p.choices[0].message.content:"(empty response)",g=p.usage?.total_tokens??"?";o.success(`Response: "${String(m).trim().slice(0,100)}"`),o.dim(` Tokens used: ${g}`)}catch(s){throw s.name==="AbortError"?new Error(`Timeout ${Pe/1e3}s. Check m\u1EA1ng / endpoint ${t}.`):s}finally{clearTimeout(r)}}function Uo(){o.info("Testing Subscription provider qua `claude --print`...");let t=Lo("claude",["--print",$e],{encoding:"utf8",timeout:Nn});if(t.signal==="SIGTERM")throw new Error(`Timeout ${Nn/1e3}s. Check m\u1EA1ng / endpoint.`);if(t.status!==0){let e=(t.stderr||"").toLowerCase();throw e.includes("401")||e.includes("invalid authentication")||e.includes("unauthorized")?new Error("Token Claude Code stale (401). Fix: `claude auth logout && claude auth login`."):new Error(`Test fail (exit ${t.status}). Stderr: ${(t.stderr||"").slice(0,200)}`)}o.success(`Response: "${(t.stdout||"").trim().slice(0,100)}"`)}async function jo(t,e,n){o.info(`Testing Anthropic Direct provider: ${t} (key: ${N(e)})`);let i=new AbortController,r=setTimeout(()=>i.abort(),Pe);try{let s=await fetch(`${t}/v1/models`,{headers:{"x-api-key":e,"anthropic-version":Gn},signal:i.signal});if(s.status===401||s.status===403)throw new Error(`API key invalid (HTTP ${s.status}). Re-run: avatar ai setup`);if(!s.ok)throw new Error(`Endpoint /v1/models l\u1ED7i (HTTP ${s.status}).`);let c=((await s.json()).data||[]).map(g=>typeof g.id=="string"?g.id:null).filter(g=>g!==null);o.success(`Connectivity OK \xB7 ${c.length} models available`),o.info(`Testing message v\u1EDBi model "${n}"...`);let l=await fetch(`${t}/v1/messages`,{method:"POST",headers:{"x-api-key":e,"anthropic-version":Gn,"Content-Type":"application/json"},body:JSON.stringify({model:n,max_tokens:Ln,messages:[{role:"user",content:$e}]}),signal:i.signal});if(!l.ok){let g=(await l.text()).slice(0,200);throw new Error(`Message endpoint fail (HTTP ${l.status}): ${g}`)}let m=((await l.json()).content||[]).map(g=>typeof g.text=="string"?g.text:"").join("").trim().slice(0,100);o.success(`Response: "${m}"`)}finally{clearTimeout(r)}}async function Mn(t){let e=t.env||{},n=typeof e.ANTHROPIC_BASE_URL=="string"?e.ANTHROPIC_BASE_URL:void 0,i=typeof e.ANTHROPIC_AUTH_TOKEN=="string"?e.ANTHROPIC_AUTH_TOKEN:void 0,r=typeof e.ANTHROPIC_API_KEY=="string"?e.ANTHROPIC_API_KEY:void 0,s=typeof t.model=="string"?t.model:"default";return r&&n?(await jo(n,r,s),{ok:!0,provider:"anthropic",message:"Anthropic Direct provider working"}):n&&i?(await Mo(n,i,s),{ok:!0,provider:"llmlite",message:"LLMLite provider working"}):(Uo(),{ok:!0,provider:"subscription",message:"Subscription provider working"})}async function Ht(){let t=process.cwd(),e=Lt(t);return e||(o.error(`Kh\xF4ng t\xECm th\u1EA5y Avatar workspace t\u1EEB th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i.