@nalvietnam/avatar-cli 3.0.0 → 3.1.0
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
|
@@ -55,7 +55,7 @@ ${r}`);return a==="unknown"&&(s.warn(`[debug] claude --print exit=${t.status} si
|
|
|
55
55
|
Hint: check m\u1EA1ng / firewall / VPN, ho\u1EB7c base URL c\xF3 \u0111\xFAng kh\xF4ng.`;throw new Error(`Connect ${t} timeout sau ${vo/1e3}s.${c}`)}let i=o.message||String(o);if(i.toLowerCase().includes("fetch failed")||i.includes("ENOTFOUND")){let c=t.includes("nal.vn")?" (ai.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}: ${i}${c}`)}throw o}finally{clearTimeout(r)}}async function Uc(t){let e=t.filter(r=>r.toLowerCase().includes("claude"));if(e.length===1){let r=e[0];return s.info(`Auto-pick model: ${r} (ch\u1EC9 1 claude alias tr\xEAn endpoint)`),r}let n=e.length>0?e:t;return await Oc({message:"Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",choices:n.map(r=>({name:r,value:r}))})}async function bo(){let t=await Mc(),e=await Lc();s.info(`Verify key (${q(t)}) qua ${e}/v1/models...`);let n=await jc(e,t);s.success(`Endpoint OK \u2014 ${n.length} models available`);let r=await Uc(n);return{apiKey:t,baseUrl:e,model:r}}f();k();import{promises as Dc}from"fs";import{join as Hc}from"path";var jn=384;function Fc(t){return Hc(t,".claude","settings.json")}async function Kc(t){if(!await m(t))return{};try{return await v(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 Vc(t,e){let{env:n,...r}=t,o={...r,model:e};if(n){let{ANTHROPIC_BASE_URL:i,ANTHROPIC_AUTH_TOKEN:a,ANTHROPIC_API_KEY:c,...l}=n;Object.keys(l).length>0&&(o.env=l)}return o}function xo(t){if(!t)return t;let{ANTHROPIC_AUTH_TOKEN:e,ANTHROPIC_API_KEY:n,...r}=t;return r}function Bc(t,e,n,r,o){let a={...xo(t.env),ANTHROPIC_BASE_URL:n};return o||(a.ANTHROPIC_AUTH_TOKEN=e),{...t,env:a,model:r,avatarProvider:"llmlite"}}function Wc(t,e,n,r,o){let a={...xo(t.env),ANTHROPIC_BASE_URL:n};return o||(a.ANTHROPIC_API_KEY=e),{...t,env:a,model:r,avatarProvider:"anthropic"}}function qc(t,e){let n=e.env||{},r=typeof e.model=="string"?e.model:void 0;return{...t,env:{...t.env||{},...n},...r?{model:r}:{}}}async function ft(t,e){let n=Fc(t),r=await Kc(n),o;switch(e.provider){case"subscription":o=Vc(r,e.model);break;case"llmlite":o=Bc(r,e.apiKey,e.baseUrl,e.model,e.skipApiKey===!0);break;case"anthropic":o=Wc(r,e.apiKey,e.baseUrl,e.model,e.skipApiKey===!0);break;case"use-global":o=qc(r,e.sourceSettings);break}await S(n,o,jn);try{await Dc.chmod(n,jn)}catch{}return{path:n,mode:jn}}var U="sonnet";async function we(t){try{s.info("Setup AI provider cho workspace...");let e=qt();if(e.installed)s.success(`Claude Code \u0111\xE3 c\xF3${e.version?` v${e.version}`:""}`);else if(s.info("Ch\u01B0a c\xF3 Claude Code \u2014 s\u1EBD t\u1EF1 c\xE0i qua npm."),fo(),fe(),e=qt(),!e.installed)throw new Error("C\xE0i Claude Code xong nh\u01B0ng v\u1EABn kh\xF4ng detect \u0111\u01B0\u1EE3c binary.");let n=Ln();switch(await wo(n)){case"subscription":{let o=On();if(o.state!=="authenticated"&&(Gn(),o=On()),o.state==="authenticated"&&o.subscriptionType)return await ft(t.workspacePath,{provider:"subscription",model:U}),await b("ai_setup",`provider=subscription,result=ok,plan=${o.subscriptionType},probe=skipped`),s.success(`AI ready \xB7 Subscription (${o.subscriptionType}) \xB7 model=${U}`),{ok:!0,provider:"subscription",model:U};s.dim("Auth status kh\xF4ng tr\u1EA3 subscriptionType \u2014 verify quota (30-60s)...");let i=Mn();if(!i.ok&&i.reason==="auth-expired"&&(s.warn("Token Claude Code \u0111\xE3 h\u1EBFt h\u1EA1n. T\u1EF1 \u0111\u1ED9ng re-login..."),Gn(),i=Mn()),!i.ok&&(i.reason==="timeout"||i.reason==="unknown"))return s.warn(`Probe verify ${i.reason} \u2014 accept trust auth status. Ti\u1EBFp t\u1EE5c.`),i.detail?.trim()&&s.warn(` Chi ti\u1EBFt: ${i.detail.slice(0,200)}`),await ft(t.workspacePath,{provider:"subscription",model:U}),await b("ai_setup",`provider=subscription,result=ok,probe=${i.reason}-soft-pass`),s.success(`AI ready \xB7 Subscription (probe ${i.reason}, soft-pass) \xB7 model=${U}`),{ok:!0,provider:"subscription",model:U};if(!i.ok){let a=i.reason??"unknown";return await b("ai_setup",`provider=subscription,result=no-quota,reason=${a}`),s.warn(`Subscription verify th\u1EA5t b\u1EA1i (${a}).`),i.detail?.trim()&&s.warn(` Chi ti\u1EBFt: ${i.detail.slice(0,200)}`),s.warn(` \u2192 ${uo(a)}`),{ok:!1,reason:`subscription-${a}`,phase:"quota"}}return await ft(t.workspacePath,{provider:"subscription",model:U}),await b("ai_setup","provider=subscription,result=ok"),s.success(`AI ready \xB7 Subscription \xB7 model=${U}`),{ok:!0,provider:"subscription",model:U}}case"llmlite":{let o=await bo();return await ft(t.workspacePath,{provider:"llmlite",apiKey:o.apiKey,baseUrl:o.baseUrl,model:o.model,skipApiKey:!1}),await b("ai_setup",`provider=llmlite,result=ok,model=${o.model},base=${o.baseUrl},storage=settings.json`),s.success(`AI ready \xB7 LLMLite (NAL) \xB7 model=${o.model} \xB7 ${o.baseUrl}`),{ok:!0,provider:"llmlite",model:o.model}}case"anthropic":{let o=await yo();return await ft(t.workspacePath,{provider:"anthropic",apiKey:o.apiKey,baseUrl:o.baseUrl,model:o.model,skipApiKey:!1}),await b("ai_setup",`provider=anthropic,result=ok,model=${o.model},storage=settings.json`),s.success(`AI ready \xB7 Anthropic Direct \xB7 model=${o.model} \xB7 ${o.baseUrl}`),{ok:!0,provider:"anthropic",model:o.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 ft(t.workspacePath,{provider:"use-global",sourceSettings:n.rawSettings}),await b("ai_setup","provider=use-global,result=ok"),s.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 s.warn(`AI setup th\u1EA5t b\u1EA1i: ${n}`),s.dim("Workspace v\u1EABn s\u1EB5n s\xE0ng. Setup AI sau qua: avatar ai setup"),await b("ai_setup",`result=failed,error=${n.slice(0,200)}`),{ok:!1,reason:n}}}f();import{spawnSync as zc}from"child_process";f();var Un=1e4,Ao=3e4,So=5,Dn="say ok",Co="2023-06-01";async function Jc(t,e,n){s.info(`Testing LLMLite provider: ${t} (key: ${q(e)})`);let r=new AbortController,o=setTimeout(()=>r.abort(),Un);try{let i=await fetch(`${t}/v1/models`,{headers:{Authorization:`Bearer ${e}`},signal:r.signal});if(i.status===401||i.status===403)throw new Error(`API key invalid (HTTP ${i.status}). Re-run: avatar ai setup`);if(!i.ok)throw new Error(`Endpoint /v1/models l\u1ED7i (HTTP ${i.status}).`);let c=((await i.json()).data||[]).map(h=>typeof h.id=="string"?h.id:null).filter(h=>h!==null);if(s.success(`Connectivity OK \xB7 ${c.length} models available`),c.length>0){let h=c.slice(0,5).join(", "),y=c.length>5?` ...+${c.length-5} more`:"";s.dim(` Models: ${h}${y}`)}s.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:Dn}],max_tokens:So}),signal:r.signal});if(!l.ok){let h=(await l.text()).slice(0,200);throw new Error(`Chat completion fail (HTTP ${l.status}). ${h}`)}let u=await l.json(),d=typeof u.choices?.[0]?.message?.content=="string"?u.choices[0].message.content:"(empty response)",g=u.usage?.total_tokens??"?";s.success(`Response: "${String(d).trim().slice(0,100)}"`),s.dim(` Tokens used: ${g}`)}catch(i){throw i.name==="AbortError"?new Error(`Timeout ${Un/1e3}s. Check m\u1EA1ng / endpoint ${t}.`):i}finally{clearTimeout(o)}}function Yc(){s.info("Testing Subscription provider qua `claude --print`...");let t=zc("claude",["--print",Dn],{encoding:"utf8",timeout:Ao});if(t.signal==="SIGTERM")throw new Error(`Timeout ${Ao/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)}`)}s.success(`Response: "${(t.stdout||"").trim().slice(0,100)}"`)}async function Xc(t,e,n){s.info(`Testing Anthropic Direct provider: ${t} (key: ${q(e)})`);let r=new AbortController,o=setTimeout(()=>r.abort(),Un);try{let i=await fetch(`${t}/v1/models`,{headers:{"x-api-key":e,"anthropic-version":Co},signal:r.signal});if(i.status===401||i.status===403)throw new Error(`API key invalid (HTTP ${i.status}). Re-run: avatar ai setup`);if(!i.ok)throw new Error(`Endpoint /v1/models l\u1ED7i (HTTP ${i.status}).`);let c=((await i.json()).data||[]).map(g=>typeof g.id=="string"?g.id:null).filter(g=>g!==null);s.success(`Connectivity OK \xB7 ${c.length} models available`),s.info(`Testing message v\u1EDBi model "${n}"...`);let l=await fetch(`${t}/v1/messages`,{method:"POST",headers:{"x-api-key":e,"anthropic-version":Co,"Content-Type":"application/json"},body:JSON.stringify({model:n,max_tokens:So,messages:[{role:"user",content:Dn}]}),signal:r.signal});if(!l.ok){let g=(await l.text()).slice(0,200);throw new Error(`Message endpoint fail (HTTP ${l.status}): ${g}`)}let d=((await l.json()).content||[]).map(g=>typeof g.text=="string"?g.text:"").join("").trim().slice(0,100);s.success(`Response: "${d}"`)}finally{clearTimeout(o)}}async function To(t){let e=t.env||{},n=typeof e.ANTHROPIC_BASE_URL=="string"?e.ANTHROPIC_BASE_URL:void 0,r=typeof e.ANTHROPIC_AUTH_TOKEN=="string"?e.ANTHROPIC_AUTH_TOKEN:void 0,o=typeof e.ANTHROPIC_API_KEY=="string"?e.ANTHROPIC_API_KEY:void 0,i=typeof t.model=="string"?t.model:"default";return o&&n?(await Xc(n,o,i),{ok:!0,provider:"anthropic",message:"Anthropic Direct provider working"}):n&&r?(await Jc(n,r,i),{ok:!0,provider:"llmlite",message:"LLMLite provider working"}):(Yc(),{ok:!0,provider:"subscription",message:"Subscription provider working"})}async function ke(){let t=process.cwd(),e=St(t);return e||(s.error(`Kh\xF4ng t\xECm th\u1EA5y Avatar workspace t\u1EEB th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i.
|
|
56
56
|
Avatar workspace c\u1EA7n c\xF3: .claude/ + CLAUDE.md + src/ (ho\u1EB7c .gitmodules).
|
|
57
57
|
B\u1EA1n \u0111ang \u1EDF: ${t}
|
|
58
|
-
Cd v\xE0o workspace dir (vd /path/to/<project>-workspace) r\u1ED3i ch\u1EA1y l\u1EA1i.`),process.exit(1)),e!==t&&s.dim(`Detected workspace root: ${e}`),e}async function Hn(t){let e=Po(t,".claude","settings.json");if(!await m(e))return{};try{return await v(e)}catch{return{}}}async function tl(){let t=await ke();await we({workspacePath:t})}async function el(){let t=await ke(),e=await Hn(t),n=e.env||{},r=typeof n.ANTHROPIC_BASE_URL=="string"?n.ANTHROPIC_BASE_URL:void 0,o=typeof n.ANTHROPIC_AUTH_TOKEN=="string"?n.ANTHROPIC_AUTH_TOKEN:void 0,i=typeof n.ANTHROPIC_API_KEY=="string"?n.ANTHROPIC_API_KEY:void 0,a=typeof e.model=="string"?e.model:void 0,c,l;i?(c=r?.includes("api.anthropic.com")||i.startsWith("sk-ant-")?"Anthropic Direct":"Custom (API key)",l=q(i)):r&&o?(c="LLMLite",l=q(o)):o?(c="Custom",l=q(o)):(c="Subscription (default)",l="(kh\xF4ng set \u2014 d\xF9ng subscription auth)"),s.info(`Project: ${t}`),s.info(`Provider: ${c}${r?` (${r})`:""}`),s.info(`Model: ${a??"(default \u2014 Claude Code ch\u1ECDn)"}`),s.info(`Token: ${l}`)}async function nl(){let t=await ke(),e=await Hn(t);try{let n=await To(e);s.success(`\u2713 ${n.message}`)}catch(n){s.error(`Test fail: ${n.message}`),process.exit(1)}}async function rl(t){let e=await ke(),n=Po(e,".claude","settings.json"),r=await Hn(e);if(!t.yes&&!await Zc({message:"X\xF3a AI config (v\u1EC1 d\xF9ng Claude Code Subscription default)?",default:!1})){s.dim("\u0110\xE3 h\u1EE7y.");return}let{env:o,...i}=r,a={...i};if(o){let{ANTHROPIC_BASE_URL:c,ANTHROPIC_AUTH_TOKEN:l,ANTHROPIC_API_KEY:u,...d}=o;Object.keys(d).length>0&&(a.env=d)}Object.keys(a).length===0?(await Qc.unlink(n).catch(()=>{}),s.success("\u0110\xE3 x\xF3a .claude/settings.json (clean state)")):(await S(n,a,384),s.success("\u0110\xE3 reset env block trong .claude/settings.json"))}function Eo(t){let e=t.command("ai").description("Qu\u1EA3n l\xFD AI provider config (M12)");e.command("setup").description("Wizard setup/re-config AI provider cho workspace hi\u1EC7n t\u1EA1i").action(async()=>{await tl()}),e.command("status").description("Show AI config hi\u1EC7n t\u1EA1i (mask token)").action(async()=>{await el()}),e.command("test").description("Verify AI provider qua cheap prompt").action(async()=>{await nl()}),e.command("reset").description("X\xF3a env.ANTHROPIC_* kh\u1ECFi settings.json (v\u1EC1 Subscription default)").option("--yes","Skip confirm").action(async n=>{await rl(n)})}import{spawnSync as Vn}from"child_process";import{promises as Ho}from"fs";import{join as Yt}from"path";import kl from"boxen";import{join as hl}from"path";k();import{promises as ll}from"fs";import{join as No}from"path";k();f();import{promises as ol}from"fs";import Ro,{join as Fn}from"path";async function il(t,e){let r=e.trim().match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);if(!r?.[2])return!0;let o=r[2];if(o.startsWith("/"))return s.warn(`Pack hook reject: absolute path "${o}" \u2014 ch\u1EC9 accept relative path t\u1EDBi workspace.`),!1;let i=Fn(t,o),a=Ro.resolve(t),c=Ro.resolve(i);return!c.startsWith(`${a}/`)&&c!==a?(s.warn(`Pack hook reject: path "${o}" resolve ra ngo\xE0i workspace (path traversal).`),!1):await m(i)}function Kn(t){let e=new Date,n=`${e.getFullYear().toString().slice(-2)+String(e.getMonth()+1).padStart(2,"0")+String(e.getDate()).padStart(2,"0")}-${String(e.getHours()).padStart(2,"0")}${String(e.getMinutes()).padStart(2,"0")}${String(e.getSeconds()).padStart(2,"0")}`;return`${t}.backup-${n}`}function zt(t,e){let n=new Set,r=[];for(let o of[...t,...e]){let i=typeof o=="string"?o:JSON.stringify(o);n.has(i)||(n.add(i),r.push(o))}return r}function sl(t){return t.replace(/\$\{CLAUDE_PROJECT_DIR\}\//g,"").trim()}function al(t){if(typeof t!="object"||t===null)return[];let e=t,n=Array.isArray(e.hooks)?e.hooks:[],r=[];for(let o of n)if(typeof o=="object"&&o!==null){let i=o.command;typeof i=="string"&&r.push(i)}return r}function $o(t){let e=t.map((i,a)=>{let c=al(i),l=c.some(u=>u.includes("${CLAUDE_PROJECT_DIR}"));return{index:a,entry:i,commands:c,hasVar:l}}),n=new Map;for(let i of e){let a=i.commands.map(sl).sort().join("|");if(!a)continue;let c=n.get(a)??[];c.push(i),n.set(a,c)}let r=new Set;for(let[,i]of n)if(!(i.length<2||!i.some(c=>c.hasVar)))for(let c of i)c.hasVar||r.add(c.index);return r.size===0?{entries:t,droppedCount:0}:{entries:t.filter((i,a)=>!r.has(a)),droppedCount:r.size}}function cl(t,e){let n=[],r={...e},o=0;for(let i of Object.keys(r)){let a=r[i]||[],{entries:c,droppedCount:l}=$o(a);l>0&&(r[i]=c,o+=l,n.includes(i)||n.push(i))}for(let[i,a]of Object.entries(t)){let c=r[i]||[],l=zt(c,a),{entries:u,droppedCount:d}=$o(l);d>0&&(o+=d),u.length!==c.length&&(n.includes(i)||n.push(i)),r[i]=u}return{merged:r,touchedEvents:n,migratedCount:o}}async function ye(t){let e=Fn(t,".claude","pack","templates","settings.json.tpl"),n=Fn(t,".claude","settings.json");if(!await m(e))return{action:"no-pack-template",changes:[]};let r;try{let u=await O(e);r=JSON.parse(u)}catch(u){throw new Error(`Pack settings template kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: ${u.message}. Path: ${e}`)}let o={},i=!1;if(await m(n)){i=!0;try{o=await v(n)}catch(u){throw new Error(`Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${u.message}. Manual fix tr\u01B0\u1EDBc khi sync.`)}}let a=[],c={...o};if(r.statusLine&&!o.statusLine&&(await il(t,r.statusLine.command)?(c.statusLine=r.statusLine,a.push("statusLine added")):a.push(`statusLine SKIPPED (file ref '${r.statusLine.command}' kh\xF4ng t\u1ED3n t\u1EA1i)`)),typeof r.includeCoAuthoredBy=="boolean"&&typeof o.includeCoAuthoredBy!="boolean"&&(c.includeCoAuthoredBy=r.includeCoAuthoredBy,a.push("includeCoAuthoredBy added")),r.model&&!o.model&&(c.model=r.model,a.push("model added")),r.env||o.env){let u={...o.env||{}},d=!1,g=!1;for(let[h,y]of Object.entries(u))typeof y=="string"&&y.includes("llm.nal.vn")&&(u[h]=y.replace(/llm\.nal\.vn/g,"ai.nal.vn"),d=!0);if(d&&a.push("rewrote legacy llm.nal.vn \u2192 ai.nal.vn in env vars"),r.env)for(let[h,y]of Object.entries(r.env))h in u||(u[h]=y,g=!0);g&&a.push("env vars added from pack"),(d||g)&&(c.env=u)}if(r.permissions){let u=o.permissions?.allow||[],d=o.permissions?.deny||[],g=r.permissions.allow||[],h=r.permissions.deny||[],y=zt(u,g),C=zt(d,h);(y.length!==u.length||C.length!==d.length)&&(c.permissions={allow:y,deny:C},a.push(`permissions union (+${y.length-u.length} allow, +${C.length-d.length} deny)`))}if(r.hooks){let u=o.hooks||{},{merged:d,touchedEvents:g,migratedCount:h}=cl(r.hooks,u);(g.length>0||h>0)&&(c.hooks=d,g.length>0&&a.push(`hooks added for events: ${g.join(", ")}`),h>0&&a.push(`migrated ${h} stale relative-path hook entries to use \${CLAUDE_PROJECT_DIR} version (fixes hook failure when shell cwd changes)`))}if(a.length===0)return{action:"no-change",changes:[]};let l;return i&&(l=Kn(n),await ol.copyFile(n,l)),await S(n,c),{action:"merged",backupPath:l,changes:a}}function ul(t,e){return No(t,".claude","pack","tools",e,"tool.json")}async function Jt(t,e){let n=ul(t,e);if(!await m(n))return null;try{return await v(n)}catch{return null}}var pl=[".claude","settings.json"];function Oo(t){return No(t,...pl)}function ve(t){let e=(t.hooks??[]).map(n=>n.command??"").sort().join("\0");return`${t.matcher??""}${e}`}async function Go(t){let e=Oo(t);if(!await m(e))return{settings:{},existed:!1};try{return{settings:await v(e),existed:!0}}catch(n){throw new Error(`Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${n.message}. Manual fix tr\u01B0\u1EDBc khi enable/disable tool.`)}}async function Mo(t,e,n){let r=Oo(t),o;return n&&(o=Kn(r),await ll.copyFile(r,o)),await S(r,e),o}function _o(t){let e=(t.hooks??[]).map(n=>(n.command??"").replace(/\$\{CLAUDE_PROJECT_DIR\}\//g,"").trim()).sort().join("|");return`${t.matcher??""}::${e}`}function Io(t){return(t.hooks??[]).some(e=>(e.command??"").includes("${CLAUDE_PROJECT_DIR}"))}function ml(t,e){let n=new Set;for(let i of e)Io(i)&&n.add(_o(i));if(n.size===0)return{migratedUser:t,droppedCount:0};let r=0;return{migratedUser:t.filter(i=>Io(i)?!0:n.has(_o(i))?(r++,!1):!0),droppedCount:r}}async function be(t,e){let{settings:n,existed:r}=await Go(t),o={...n},i=[],a=e.settings.hooks??{};if(Object.keys(a).length>0){let u=n.hooks??{},d={...u},g=[],h=0;for(let[y,C]of Object.entries(a)){let _=u[y]??[],{migratedUser:F,droppedCount:P}=ml(_,C);P>0&&(h+=P);let T=new Set(F.map(ve)),R=C.filter(et=>!T.has(ve(et)));(R.length>0||P>0)&&(d[y]=[...F,...R],g.push(y))}g.length>0&&(o.hooks=d,i.push(`hooks added: ${g.join(", ")}`),h>0&&i.push(`migrated ${h} stale relative-path entries \u2192 \\{CLAUDE_PROJECT_DIR\\} version`))}let c=e.settings.permissions?.deny??[];if(c.length>0){let u=n.permissions?.deny??[],d=zt(u,c);d.length!==u.length&&(o.permissions={...n.permissions,deny:d},i.push(`deny +${d.length-u.length}`))}return i.length===0?{action:"no-change",changes:[]}:{action:"enabled",backupPath:await Mo(t,o,r),changes:i}}async function Lo(t,e){let{settings:n,existed:r}=await Go(t);if(!r)return{action:"no-change",changes:[]};let{hooks:o,permissions:i,...a}=n,c={...a},l=[],u=e.settings.hooks??{},d=n.hooks??{},g={},h=[];for(let[P,T]of Object.entries(d)){let R=u[P];if(!R){g[P]=T;continue}let et=new Set(R.map(ve)),kt=T.filter(nt=>!et.has(ve(nt)));kt.length!==T.length&&h.push(P),kt.length>0&&(g[P]=kt)}Object.keys(g).length>0&&(c.hooks=g),h.length>0&&l.push(`hooks removed: ${h.join(", ")}`);let y=new Set(e.settings.permissions?.deny??[]),C=n.permissions?.deny??[],_=y.size>0?C.filter(P=>!y.has(P)):C;if(n.permissions){let{deny:P,...T}=n.permissions,R={...T,..._.length>0?{deny:_}:{}};Object.keys(R).length>0&&(c.permissions=R)}return _.length!==C.length&&l.push(`deny -${C.length-_.length}`),l.length===0?{action:"no-change",changes:[]}:{action:"disabled",backupPath:await Mo(t,c,r),changes:l}}k();k();import{join as dl}from"path";var gl=".claude/avatar-tools.json";function jo(){return{tools:{}}}function Uo(t){return dl(t,gl)}async function Pt(t){let e=Uo(t);if(!await m(e))return jo();try{return{tools:(await v(e)).tools??{}}}catch{return jo()}}async function fl(t,e){await S(Uo(t),e)}async function xe(t,e,n){let r=await Pt(t);return r.tools[e]={enabled:n.enabled,version:n.version,appliedAt:new Date().toISOString()},await fl(t,r),r}async function Et(t){let e=await Pt(t);return Object.entries(e.tools).filter(([,n])=>n.enabled).map(([n])=>n)}function wl(t,e){let n=e.settings.hooks??{},r=t.hooks??{};for(let[o,i]of Object.entries(n)){let a=r[o]??[],c=new Set(a.flatMap(l=>(l.hooks??[]).map(u=>u.command??"")));for(let l of i)for(let u of l.hooks??[])if(u.command&&!c.has(u.command))return!1}return!0}async function Do(t){let e=await Et(t);if(e.length===0)return[];let n=hl(t,".claude","settings.json"),r={};if(await m(n))try{r=await v(n)}catch{r={}}let o=[];for(let i of e){let a=await Jt(t,i);if(!a){o.push({name:`Tool: ${i}`,status:"warn",detail:"state=enabled nh\u01B0ng pack thi\u1EBFu manifest (version skew). Ch\u1EA1y 'avatar sync'.",fixable:!1});continue}wl(r,a)?o.push({name:`Tool: ${i}`,status:"ok",detail:`enabled, hook active (v${a.version})`,fixable:!1}):o.push({name:`Tool: ${i}`,status:"fail",detail:"state=enabled nh\u01B0ng hook thi\u1EBFu trong settings.json \u2014 fixable (re-apply)",fixable:!0,fix:async()=>{await be(t,a)}})}return o}k();f();function Fo(t){t.command("doctor").description("Ch\u1EA9n \u0111o\xE1n c\xE0i \u0111\u1EB7t Avatar: hooks, MCP, login, submodule, ...").option("--fix","T\u1EF1 \u0111\u1ED9ng fix c\xE1c issue c\xF3 th\u1EC3 fix t\u1EF1 \u0111\u1ED9ng").action(async e=>{try{let n=await yl(process.cwd());vl(n),e.fix&&await bl(n)}catch(n){s.error(n instanceof Error?n.message:String(n)),process.exit(1)}})}async function yl(t){let e=[],n=process.versions.node,[r,o]=n.split(".").map(T=>Number.parseInt(T,10)),i=(r??0)>18||(r??0)===18&&(o??0)>=17;e.push({name:"Node.js version",status:i?"ok":"fail",detail:`v${n}${i?"":" (c\u1EA7n >= 18.17)"}`,fixable:!1});let a=await L();a?mt(a)?e.push({name:"Login status",status:"warn",detail:`Token h\u1EBFt h\u1EA1n (${a.email}) \u2014 ch\u1EA1y 'avatar login'`,fixable:!1}):e.push({name:"Login status",status:"ok",detail:`Logged in: ${a.email}`,fixable:!1}):e.push({name:"Login status",status:"fail",detail:"Ch\u01B0a \u0111\u0103ng nh\u1EADp \u2014 ch\u1EA1y 'avatar login'",fixable:!1});let c=Yt(t,".claude","pack"),l=Yt(t,"CLAUDE.md"),[u,d]=await Promise.all([m(c),m(l)]);if(e.push({name:"team-ai-pack installation",status:u?"ok":"warn",detail:u?c:"Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",fixable:!1}),e.push({name:"CLAUDE.md",status:d?"ok":"warn",detail:d?"t\u1ED3n t\u1EA1i \u1EDF project root":"thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",fixable:!1}),u){let T=Yt(t,".claude",".gitignore"),R=!1;await m(T)&&(R=(await Ho.readFile(T,"utf8")).includes("settings.json")),e.push({name:"\u{1F512} settings.json gitignored (.claude/.gitignore)",status:R?"ok":"fail",detail:R?"an to\xE0n \u2014 settings.json kh\xF4ng commit nh\u1EA7m":"CRITICAL: settings.json ch\u1EE9a API key \u2014 ch\u1EA1y 'avatar doctor --fix' \u0111\u1EC3 b\u1EA3o v\u1EC7",fixable:!R,fix:R?void 0:async()=>{let{writeClaudeGitignore:et}=await Promise.resolve().then(()=>(Nt(),yr));await et(t)}})}let g=Vn("which",["python"]),h=Vn("which",["python3"]),y=g.status===0,C=h.status===0;C&&!y?e.push({name:"Python binary alias",status:"warn",detail:`Ch\u1EC9 c\xF3 python3 (modern macOS). Pack scripts th\u01B0\u1EDDng ref 'python' \u2192 suggest: ln -s ${h.stdout.toString().trim()} ~/.local/bin/python`,fixable:!1}):y?e.push({name:"Python binary",status:"ok",detail:`python: ${g.stdout.toString().trim()}`,fixable:!1}):C&&e.push({name:"Python binary",status:"ok",detail:`python3: ${h.stdout.toString().trim()}`,fixable:!1});let _=Yt(t,".claude","settings.json");if(await m(_))try{let T=await Ho.readFile(_,"utf8"),R=JSON.parse(T);if(R.statusLine?.command){let kt=R.statusLine.command.trim().match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);if(kt?.[2]){let nt=kt[2],Zi=nt.startsWith("/")?nt:Yt(t,nt),or=await m(Zi);e.push({name:"statusLine command",status:or?"ok":"fail",detail:or?`ref OK: ${nt}`:`BROKEN: settings.json ref '${nt}' nh\u01B0ng file kh\xF4ng t\u1ED3n t\u1EA1i. Strip field statusLine ho\u1EB7c fix path.`,fixable:!1})}}}catch{}let F=Vn("which",["claude"]),P=F.status===0;if(e.push({name:"Claude Code CLI",status:P?"ok":"warn",detail:P?F.stdout.toString().trim():"kh\xF4ng t\xECm th\u1EA5y 'claude' tr\xEAn PATH",fixable:!1}),u){let T=await Do(t);e.push(...T)}return e}function vl(t){let e=[p.bold("Avatar Doctor"),"\u2500".repeat(48)],n=0,r=0,o=0;for(let i of t){let a=i.status==="ok"?p.green("\u2713"):i.status==="warn"?p.yellow("\u26A0"):p.red("\u2717");e.push(`${a} ${i.name.padEnd(28)} ${p.dim(i.detail)}`),i.status==="ok"?n+=1:(r+=1,i.fixable&&(o+=1))}e.push("\u2500".repeat(48)),e.push(`${n} checks passed, ${r} issue${r===1?"":"s"}${o>0?` (${o} fixable \u2014 ch\u1EA1y 'avatar doctor --fix')`:""}`),process.stdout.write(`${kl(e.join(`
|
|
58
|
+
Cd v\xE0o workspace dir (vd /path/to/<project>-workspace) r\u1ED3i ch\u1EA1y l\u1EA1i.`),process.exit(1)),e!==t&&s.dim(`Detected workspace root: ${e}`),e}async function Hn(t){let e=Po(t,".claude","settings.json");if(!await m(e))return{};try{return await v(e)}catch{return{}}}async function tl(){let t=await ke();await we({workspacePath:t})}async function el(){let t=await ke(),e=await Hn(t),n=e.env||{},r=typeof n.ANTHROPIC_BASE_URL=="string"?n.ANTHROPIC_BASE_URL:void 0,o=typeof n.ANTHROPIC_AUTH_TOKEN=="string"?n.ANTHROPIC_AUTH_TOKEN:void 0,i=typeof n.ANTHROPIC_API_KEY=="string"?n.ANTHROPIC_API_KEY:void 0,a=typeof e.model=="string"?e.model:void 0,c,l;i?(c=r?.includes("api.anthropic.com")||i.startsWith("sk-ant-")?"Anthropic Direct":"Custom (API key)",l=q(i)):r&&o?(c="LLMLite",l=q(o)):o?(c="Custom",l=q(o)):(c="Subscription (default)",l="(kh\xF4ng set \u2014 d\xF9ng subscription auth)"),s.info(`Project: ${t}`),s.info(`Provider: ${c}${r?` (${r})`:""}`),s.info(`Model: ${a??"(default \u2014 Claude Code ch\u1ECDn)"}`),s.info(`Token: ${l}`)}async function nl(){let t=await ke(),e=await Hn(t);try{let n=await To(e);s.success(`\u2713 ${n.message}`)}catch(n){s.error(`Test fail: ${n.message}`),process.exit(1)}}async function rl(t){let e=await ke(),n=Po(e,".claude","settings.json"),r=await Hn(e);if(!t.yes&&!await Zc({message:"X\xF3a AI config (v\u1EC1 d\xF9ng Claude Code Subscription default)?",default:!1})){s.dim("\u0110\xE3 h\u1EE7y.");return}let{env:o,...i}=r,a={...i};if(o){let{ANTHROPIC_BASE_URL:c,ANTHROPIC_AUTH_TOKEN:l,ANTHROPIC_API_KEY:u,...d}=o;Object.keys(d).length>0&&(a.env=d)}Object.keys(a).length===0?(await Qc.unlink(n).catch(()=>{}),s.success("\u0110\xE3 x\xF3a .claude/settings.json (clean state)")):(await S(n,a,384),s.success("\u0110\xE3 reset env block trong .claude/settings.json"))}function Eo(t){let e=t.command("ai").description("Qu\u1EA3n l\xFD AI provider config (M12)");e.command("setup").description("Wizard setup/re-config AI provider cho workspace hi\u1EC7n t\u1EA1i").action(async()=>{await tl()}),e.command("status").description("Show AI config hi\u1EC7n t\u1EA1i (mask token)").action(async()=>{await el()}),e.command("test").description("Verify AI provider qua cheap prompt").action(async()=>{await nl()}),e.command("reset").description("X\xF3a env.ANTHROPIC_* kh\u1ECFi settings.json (v\u1EC1 Subscription default)").option("--yes","Skip confirm").action(async n=>{await rl(n)})}import{spawnSync as Vn}from"child_process";import{promises as Ho}from"fs";import{join as Yt}from"path";import kl from"boxen";import{join as hl}from"path";k();import{promises as ll}from"fs";import{join as No}from"path";k();f();import{promises as ol}from"fs";import Ro,{join as Fn}from"path";async function il(t,e){let r=e.trim().match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);if(!r?.[2])return!0;let o=r[2];if(o.startsWith("/"))return s.warn(`Pack hook reject: absolute path "${o}" \u2014 ch\u1EC9 accept relative path t\u1EDBi workspace.`),!1;let i=Fn(t,o),a=Ro.resolve(t),c=Ro.resolve(i);return!c.startsWith(`${a}/`)&&c!==a?(s.warn(`Pack hook reject: path "${o}" resolve ra ngo\xE0i workspace (path traversal).`),!1):await m(i)}function Kn(t){let e=new Date,n=`${e.getFullYear().toString().slice(-2)+String(e.getMonth()+1).padStart(2,"0")+String(e.getDate()).padStart(2,"0")}-${String(e.getHours()).padStart(2,"0")}${String(e.getMinutes()).padStart(2,"0")}${String(e.getSeconds()).padStart(2,"0")}`;return`${t}.backup-${n}`}function zt(t,e){let n=new Set,r=[];for(let o of[...t,...e]){let i=typeof o=="string"?o:JSON.stringify(o);n.has(i)||(n.add(i),r.push(o))}return r}function sl(t){return t.replace(/\$\{CLAUDE_PROJECT_DIR\}\//g,"").trim()}function al(t){if(typeof t!="object"||t===null)return[];let e=t,n=Array.isArray(e.hooks)?e.hooks:[],r=[];for(let o of n)if(typeof o=="object"&&o!==null){let i=o.command;typeof i=="string"&&r.push(i)}return r}function $o(t){let e=t.map((i,a)=>{let c=al(i),l=c.some(u=>u.includes("${CLAUDE_PROJECT_DIR}"));return{index:a,entry:i,commands:c,hasVar:l}}),n=new Map;for(let i of e){let a=i.commands.map(sl).sort().join("|");if(!a)continue;let c=n.get(a)??[];c.push(i),n.set(a,c)}let r=new Set;for(let[,i]of n)if(!(i.length<2||!i.some(c=>c.hasVar)))for(let c of i)c.hasVar||r.add(c.index);return r.size===0?{entries:t,droppedCount:0}:{entries:t.filter((i,a)=>!r.has(a)),droppedCount:r.size}}function cl(t,e){let n=[],r={...e},o=0;for(let i of Object.keys(r)){let a=r[i]||[],{entries:c,droppedCount:l}=$o(a);l>0&&(r[i]=c,o+=l,n.includes(i)||n.push(i))}for(let[i,a]of Object.entries(t)){let c=r[i]||[],l=zt(c,a),{entries:u,droppedCount:d}=$o(l);d>0&&(o+=d),u.length!==c.length&&(n.includes(i)||n.push(i)),r[i]=u}return{merged:r,touchedEvents:n,migratedCount:o}}async function ye(t){let e=Fn(t,".claude","pack","templates","settings.json.tpl"),n=Fn(t,".claude","settings.json");if(!await m(e))return{action:"no-pack-template",changes:[]};let r;try{let u=await O(e);r=JSON.parse(u)}catch(u){throw new Error(`Pack settings template kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: ${u.message}. Path: ${e}`)}let o={},i=!1;if(await m(n)){i=!0;try{o=await v(n)}catch(u){throw new Error(`Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${u.message}. Manual fix tr\u01B0\u1EDBc khi sync.`)}}let a=[],c={...o};if(typeof o.disableWorkflows!="boolean"&&(c.disableWorkflows=!0,a.push("disableWorkflows=true (Avatar t\u1EAFt Dynamic Workflows)")),r.statusLine&&!o.statusLine&&(await il(t,r.statusLine.command)?(c.statusLine=r.statusLine,a.push("statusLine added")):a.push(`statusLine SKIPPED (file ref '${r.statusLine.command}' kh\xF4ng t\u1ED3n t\u1EA1i)`)),typeof r.includeCoAuthoredBy=="boolean"&&typeof o.includeCoAuthoredBy!="boolean"&&(c.includeCoAuthoredBy=r.includeCoAuthoredBy,a.push("includeCoAuthoredBy added")),r.model&&!o.model&&(c.model=r.model,a.push("model added")),r.env||o.env){let u={...o.env||{}},d=!1,g=!1;for(let[h,y]of Object.entries(u))typeof y=="string"&&y.includes("llm.nal.vn")&&(u[h]=y.replace(/llm\.nal\.vn/g,"ai.nal.vn"),d=!0);if(d&&a.push("rewrote legacy llm.nal.vn \u2192 ai.nal.vn in env vars"),r.env)for(let[h,y]of Object.entries(r.env))h in u||(u[h]=y,g=!0);g&&a.push("env vars added from pack"),(d||g)&&(c.env=u)}if(r.permissions){let u=o.permissions?.allow||[],d=o.permissions?.deny||[],g=r.permissions.allow||[],h=r.permissions.deny||[],y=zt(u,g),C=zt(d,h);(y.length!==u.length||C.length!==d.length)&&(c.permissions={allow:y,deny:C},a.push(`permissions union (+${y.length-u.length} allow, +${C.length-d.length} deny)`))}if(r.hooks){let u=o.hooks||{},{merged:d,touchedEvents:g,migratedCount:h}=cl(r.hooks,u);(g.length>0||h>0)&&(c.hooks=d,g.length>0&&a.push(`hooks added for events: ${g.join(", ")}`),h>0&&a.push(`migrated ${h} stale relative-path hook entries to use \${CLAUDE_PROJECT_DIR} version (fixes hook failure when shell cwd changes)`))}if(a.length===0)return{action:"no-change",changes:[]};let l;return i&&(l=Kn(n),await ol.copyFile(n,l)),await S(n,c),{action:"merged",backupPath:l,changes:a}}function ul(t,e){return No(t,".claude","pack","tools",e,"tool.json")}async function Jt(t,e){let n=ul(t,e);if(!await m(n))return null;try{return await v(n)}catch{return null}}var pl=[".claude","settings.json"];function Oo(t){return No(t,...pl)}function ve(t){let e=(t.hooks??[]).map(n=>n.command??"").sort().join("\0");return`${t.matcher??""}${e}`}async function Go(t){let e=Oo(t);if(!await m(e))return{settings:{},existed:!1};try{return{settings:await v(e),existed:!0}}catch(n){throw new Error(`Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${n.message}. Manual fix tr\u01B0\u1EDBc khi enable/disable tool.`)}}async function Mo(t,e,n){let r=Oo(t),o;return n&&(o=Kn(r),await ll.copyFile(r,o)),await S(r,e),o}function _o(t){let e=(t.hooks??[]).map(n=>(n.command??"").replace(/\$\{CLAUDE_PROJECT_DIR\}\//g,"").trim()).sort().join("|");return`${t.matcher??""}::${e}`}function Io(t){return(t.hooks??[]).some(e=>(e.command??"").includes("${CLAUDE_PROJECT_DIR}"))}function ml(t,e){let n=new Set;for(let i of e)Io(i)&&n.add(_o(i));if(n.size===0)return{migratedUser:t,droppedCount:0};let r=0;return{migratedUser:t.filter(i=>Io(i)?!0:n.has(_o(i))?(r++,!1):!0),droppedCount:r}}async function be(t,e){let{settings:n,existed:r}=await Go(t),o={...n},i=[],a=e.settings.hooks??{};if(Object.keys(a).length>0){let u=n.hooks??{},d={...u},g=[],h=0;for(let[y,C]of Object.entries(a)){let _=u[y]??[],{migratedUser:F,droppedCount:P}=ml(_,C);P>0&&(h+=P);let T=new Set(F.map(ve)),R=C.filter(et=>!T.has(ve(et)));(R.length>0||P>0)&&(d[y]=[...F,...R],g.push(y))}g.length>0&&(o.hooks=d,i.push(`hooks added: ${g.join(", ")}`),h>0&&i.push(`migrated ${h} stale relative-path entries \u2192 \\{CLAUDE_PROJECT_DIR\\} version`))}let c=e.settings.permissions?.deny??[];if(c.length>0){let u=n.permissions?.deny??[],d=zt(u,c);d.length!==u.length&&(o.permissions={...n.permissions,deny:d},i.push(`deny +${d.length-u.length}`))}return i.length===0?{action:"no-change",changes:[]}:{action:"enabled",backupPath:await Mo(t,o,r),changes:i}}async function Lo(t,e){let{settings:n,existed:r}=await Go(t);if(!r)return{action:"no-change",changes:[]};let{hooks:o,permissions:i,...a}=n,c={...a},l=[],u=e.settings.hooks??{},d=n.hooks??{},g={},h=[];for(let[P,T]of Object.entries(d)){let R=u[P];if(!R){g[P]=T;continue}let et=new Set(R.map(ve)),kt=T.filter(nt=>!et.has(ve(nt)));kt.length!==T.length&&h.push(P),kt.length>0&&(g[P]=kt)}Object.keys(g).length>0&&(c.hooks=g),h.length>0&&l.push(`hooks removed: ${h.join(", ")}`);let y=new Set(e.settings.permissions?.deny??[]),C=n.permissions?.deny??[],_=y.size>0?C.filter(P=>!y.has(P)):C;if(n.permissions){let{deny:P,...T}=n.permissions,R={...T,..._.length>0?{deny:_}:{}};Object.keys(R).length>0&&(c.permissions=R)}return _.length!==C.length&&l.push(`deny -${C.length-_.length}`),l.length===0?{action:"no-change",changes:[]}:{action:"disabled",backupPath:await Mo(t,c,r),changes:l}}k();k();import{join as dl}from"path";var gl=".claude/avatar-tools.json";function jo(){return{tools:{}}}function Uo(t){return dl(t,gl)}async function Pt(t){let e=Uo(t);if(!await m(e))return jo();try{return{tools:(await v(e)).tools??{}}}catch{return jo()}}async function fl(t,e){await S(Uo(t),e)}async function xe(t,e,n){let r=await Pt(t);return r.tools[e]={enabled:n.enabled,version:n.version,appliedAt:new Date().toISOString()},await fl(t,r),r}async function Et(t){let e=await Pt(t);return Object.entries(e.tools).filter(([,n])=>n.enabled).map(([n])=>n)}function wl(t,e){let n=e.settings.hooks??{},r=t.hooks??{};for(let[o,i]of Object.entries(n)){let a=r[o]??[],c=new Set(a.flatMap(l=>(l.hooks??[]).map(u=>u.command??"")));for(let l of i)for(let u of l.hooks??[])if(u.command&&!c.has(u.command))return!1}return!0}async function Do(t){let e=await Et(t);if(e.length===0)return[];let n=hl(t,".claude","settings.json"),r={};if(await m(n))try{r=await v(n)}catch{r={}}let o=[];for(let i of e){let a=await Jt(t,i);if(!a){o.push({name:`Tool: ${i}`,status:"warn",detail:"state=enabled nh\u01B0ng pack thi\u1EBFu manifest (version skew). Ch\u1EA1y 'avatar sync'.",fixable:!1});continue}wl(r,a)?o.push({name:`Tool: ${i}`,status:"ok",detail:`enabled, hook active (v${a.version})`,fixable:!1}):o.push({name:`Tool: ${i}`,status:"fail",detail:"state=enabled nh\u01B0ng hook thi\u1EBFu trong settings.json \u2014 fixable (re-apply)",fixable:!0,fix:async()=>{await be(t,a)}})}return o}k();f();function Fo(t){t.command("doctor").description("Ch\u1EA9n \u0111o\xE1n c\xE0i \u0111\u1EB7t Avatar: hooks, MCP, login, submodule, ...").option("--fix","T\u1EF1 \u0111\u1ED9ng fix c\xE1c issue c\xF3 th\u1EC3 fix t\u1EF1 \u0111\u1ED9ng").action(async e=>{try{let n=await yl(process.cwd());vl(n),e.fix&&await bl(n)}catch(n){s.error(n instanceof Error?n.message:String(n)),process.exit(1)}})}async function yl(t){let e=[],n=process.versions.node,[r,o]=n.split(".").map(T=>Number.parseInt(T,10)),i=(r??0)>18||(r??0)===18&&(o??0)>=17;e.push({name:"Node.js version",status:i?"ok":"fail",detail:`v${n}${i?"":" (c\u1EA7n >= 18.17)"}`,fixable:!1});let a=await L();a?mt(a)?e.push({name:"Login status",status:"warn",detail:`Token h\u1EBFt h\u1EA1n (${a.email}) \u2014 ch\u1EA1y 'avatar login'`,fixable:!1}):e.push({name:"Login status",status:"ok",detail:`Logged in: ${a.email}`,fixable:!1}):e.push({name:"Login status",status:"fail",detail:"Ch\u01B0a \u0111\u0103ng nh\u1EADp \u2014 ch\u1EA1y 'avatar login'",fixable:!1});let c=Yt(t,".claude","pack"),l=Yt(t,"CLAUDE.md"),[u,d]=await Promise.all([m(c),m(l)]);if(e.push({name:"team-ai-pack installation",status:u?"ok":"warn",detail:u?c:"Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",fixable:!1}),e.push({name:"CLAUDE.md",status:d?"ok":"warn",detail:d?"t\u1ED3n t\u1EA1i \u1EDF project root":"thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",fixable:!1}),u){let T=Yt(t,".claude",".gitignore"),R=!1;await m(T)&&(R=(await Ho.readFile(T,"utf8")).includes("settings.json")),e.push({name:"\u{1F512} settings.json gitignored (.claude/.gitignore)",status:R?"ok":"fail",detail:R?"an to\xE0n \u2014 settings.json kh\xF4ng commit nh\u1EA7m":"CRITICAL: settings.json ch\u1EE9a API key \u2014 ch\u1EA1y 'avatar doctor --fix' \u0111\u1EC3 b\u1EA3o v\u1EC7",fixable:!R,fix:R?void 0:async()=>{let{writeClaudeGitignore:et}=await Promise.resolve().then(()=>(Nt(),yr));await et(t)}})}let g=Vn("which",["python"]),h=Vn("which",["python3"]),y=g.status===0,C=h.status===0;C&&!y?e.push({name:"Python binary alias",status:"warn",detail:`Ch\u1EC9 c\xF3 python3 (modern macOS). Pack scripts th\u01B0\u1EDDng ref 'python' \u2192 suggest: ln -s ${h.stdout.toString().trim()} ~/.local/bin/python`,fixable:!1}):y?e.push({name:"Python binary",status:"ok",detail:`python: ${g.stdout.toString().trim()}`,fixable:!1}):C&&e.push({name:"Python binary",status:"ok",detail:`python3: ${h.stdout.toString().trim()}`,fixable:!1});let _=Yt(t,".claude","settings.json");if(await m(_))try{let T=await Ho.readFile(_,"utf8"),R=JSON.parse(T);if(R.statusLine?.command){let kt=R.statusLine.command.trim().match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);if(kt?.[2]){let nt=kt[2],Zi=nt.startsWith("/")?nt:Yt(t,nt),or=await m(Zi);e.push({name:"statusLine command",status:or?"ok":"fail",detail:or?`ref OK: ${nt}`:`BROKEN: settings.json ref '${nt}' nh\u01B0ng file kh\xF4ng t\u1ED3n t\u1EA1i. Strip field statusLine ho\u1EB7c fix path.`,fixable:!1})}}}catch{}let F=Vn("which",["claude"]),P=F.status===0;if(e.push({name:"Claude Code CLI",status:P?"ok":"warn",detail:P?F.stdout.toString().trim():"kh\xF4ng t\xECm th\u1EA5y 'claude' tr\xEAn PATH",fixable:!1}),u){let T=await Do(t);e.push(...T)}return e}function vl(t){let e=[p.bold("Avatar Doctor"),"\u2500".repeat(48)],n=0,r=0,o=0;for(let i of t){let a=i.status==="ok"?p.green("\u2713"):i.status==="warn"?p.yellow("\u26A0"):p.red("\u2717");e.push(`${a} ${i.name.padEnd(28)} ${p.dim(i.detail)}`),i.status==="ok"?n+=1:(r+=1,i.fixable&&(o+=1))}e.push("\u2500".repeat(48)),e.push(`${n} checks passed, ${r} issue${r===1?"":"s"}${o>0?` (${o} fixable \u2014 ch\u1EA1y 'avatar doctor --fix')`:""}`),process.stdout.write(`${kl(e.join(`
|
|
59
59
|
`),{padding:1,borderStyle:"round"})}
|
|
60
60
|
`)}async function bl(t){let e=0;for(let n of t)if(n.fixable&&n.fix)try{await n.fix(),s.success(`Fixed: ${n.name}`),e+=1}catch(r){s.error(`Failed to fix ${n.name}: ${r instanceof Error?r.message:String(r)}`)}e===0&&s.dim("Kh\xF4ng c\xF3 g\xEC \u0111\u1EC3 fix t\u1EF1 \u0111\u1ED9ng.")}k();import{promises as Yo}from"fs";import{join as Xt}from"path";import{confirm as Rl}from"@inquirer/prompts";import $l from"boxen";import{spawnSync as xl}from"child_process";f();var Ko=300*1e3,Vo="gitnexus",D=class extends Error{reason;exitCode;constructor(e,n,r=null){super(n),this.name="InstallGitnexusError",this.reason=e,this.exitCode=r}};function Al(t,e){let n=e.toLowerCase();return n.includes("eacces")||n.includes("permission denied")?new D("permission-denied",`npm install -g c\u1EA7n quy\u1EC1n. Th\u1EED: sudo npm install -g ${Vo} ho\u1EB7c fix npm prefix (npm config set prefix ~/.npm-global).`,t):n.includes("enospc")||n.includes("no space")?new D("disk-full","\u0110\u0129a \u0111\u1EA7y. Free disk space r\u1ED3i th\u1EED l\u1EA1i.",t):new D("generic",`npm install th\u1EA5t b\u1EA1i (exit ${t??"null"}). Xem log npm ph\xEDa tr\xEAn.`,t)}function Bo(){s.info("\u0110ang c\xE0i GitNexus qua npm (c\xF3 th\u1EC3 m\u1EA5t 1-2 ph\xFAt)...");let t=xl("npm",["install","-g",Vo],{stdio:["inherit","inherit","pipe"],timeout:Ko,encoding:"utf8"});if(t.signal==="SIGTERM")throw new D("timeout",`npm install timeout sau ${Ko/1e3}s. Check m\u1EA1ng r\u1ED3i th\u1EED l\u1EA1i.`,null);if(t.status!==0)throw t.stderr&&process.stderr.write(t.stderr),Al(t.status,t.stderr||"");ae();let e=ut();if(!e.installed||!e.path)throw new D("binary-not-in-path","npm c\xE0i xong nh\u01B0ng `gitnexus` kh\xF4ng trong PATH. Reload shell (source ~/.zshrc) ho\u1EB7c th\xEAm npm global bin v\xE0o PATH.",null);return s.success(`\u0110\xE3 c\xE0i GitNexus${e.version?` v${e.version}`:""} t\u1EA1i ${e.path}`),{version:e.version,path:e.path}}k();f();import{promises as zo}from"fs";import{homedir as Cl}from"os";import{join as Sl}from"path";var Wo=384,qo={command:"gitnexus",args:["mcp"]};function Tl(){return Sl(Cl(),".claude","mcp_servers.json")}function Pl(t,e){return t===e?!0:typeof t!="object"||typeof e!="object"||t===null||e===null?!1:JSON.stringify(t)===JSON.stringify(e)}async function El(t){let e=new Date().toISOString().replace(/[:.]/g,"-"),n=`${t}.avatar-backup-${e}`;return await zo.copyFile(t,n),n}async function Jo(){let t=Tl(),e={},n=!1;if(await m(t)){n=!0;try{e=await v(t)}catch(a){throw new Error(`MCP config corrupted (${t}): ${a.message}. Backup + x\xF3a file \u0111\u1EC3 Avatar t\u1EA1o l\u1EA1i.`)}}let r=e.mcp_servers?.gitnexus;if(r&&Pl(r,qo))return s.dim(`MCP entry gitnexus \u0111\xE3 \u0111\xFAng t\u1EA1i ${t} (no-op)`),{path:t,wasUpdated:!1};let o;n&&(o=await El(t),s.dim(`Backup ${t} \u2192 ${o}`));let i={...e,mcp_servers:{...e.mcp_servers||{},gitnexus:qo}};await S(t,i,Wo);try{await zo.chmod(t,Wo)}catch{}return s.success(`Registered MCP server: gitnexus \u2192 ${t}`),{path:t,wasUpdated:!0,backup:o}}f();async function _l(){let t=[p.bold("\u{1F9E0} GitNexus ch\u01B0a c\xE0i"),"","GitNexus = code intelligence layer cho Claude Code:"," \u2022 Architectural awareness (impact analysis)"," \u2022 Call chain debug + blast radius tr\u01B0\u1EDBc refactor"," \u2022 Wiki HTML t\u1EF1 gen m\xF4 t\u1EA3 codebase","",`S\u1EBD c\xE0i: ${p.cyan("npm install -g gitnexus")} (global)`];return process.stdout.write(`${$l(t.join(`
|
|
61
61
|
`),{padding:1,borderStyle:"round",borderColor:"cyan"})}
|