ccem 1.8.0 → 2.0.0-beta

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
@@ -1,25 +1,2850 @@
1
1
  #!/usr/bin/env node
2
- import{Command as uo}from"commander";import go from"conf";import k from"inquirer";import u from"chalk";import Ot from"cli-table3";import{spawn as It}from"child_process";import*as wt from"fs";import*as ye from"path";import{fileURLToPath as fo}from"url";import le from"crypto";import ee from"fs";import B from"path";var be="aes-256-cbc",De=le.scryptSync("claude-code-env-manager-secret","salt",32),J=e=>{if(!e)return e;let t=le.randomBytes(16),o=le.createCipheriv(be,De,t),s=o.update(e,"utf8","hex");return s+=o.final("hex"),`enc:${t.toString("hex")}:${s}`},Y=e=>{if(!e||!e.startsWith("enc:"))return e;try{let t=e.split(":");if(t.length!==3)return e;let o=Buffer.from(t[1],"hex"),s=t[2],n=le.createDecipheriv(be,De,o),i=n.update(s,"hex","utf8");return i+=n.final("utf8"),i}catch{return e}},Be=()=>{let e=process.cwd(),t=B.parse(e).root;for(;e!==t;){if(ee.existsSync(B.join(e,".git"))||ee.existsSync(B.join(e,"package.json")))return e;e=B.dirname(e)}return process.cwd()},de=(e=!0)=>{let t=Be(),o=B.join(t,".claude"),s=e?"settings.local.json":"settings.json";return B.join(o,s)},$e=()=>{let e=Be(),t=B.join(e,".claude");return ee.existsSync(t)||ee.mkdirSync(t,{recursive:!0}),t},Ae=()=>process.env.HOME||process.env.USERPROFILE||"",Pe=()=>B.join(Ae(),".claude.json"),Se=()=>B.join(Ae(),".claude","settings.json"),Ue=()=>{let e=B.join(Ae(),".claude");return ee.existsSync(e)||ee.mkdirSync(e,{recursive:!0}),e};var Re={GLM:{ANTHROPIC_BASE_URL:"https://open.bigmodel.cn/api/anthropic",ANTHROPIC_MODEL:"glm-4.6",ANTHROPIC_SMALL_FAST_MODEL:"glm-4.5-air"},KIMI:{ANTHROPIC_BASE_URL:"https://api.moonshot.cn/anthropic",ANTHROPIC_MODEL:"kimi-k2-thinking-turbo",ANTHROPIC_SMALL_FAST_MODEL:"kimi-k2-turbo-preview"},MiniMax:{ANTHROPIC_BASE_URL:"https://api.minimaxi.com/anthropic",ANTHROPIC_MODEL:"MiniMax-M2",ANTHROPIC_SMALL_FAST_MODEL:"MiniMax-M2"},DeepSeek:{ANTHROPIC_BASE_URL:"https://api.deepseek.com/anthropic",ANTHROPIC_MODEL:"deepseek-chat",ANTHROPIC_SMALL_FAST_MODEL:"deepseek-chat"}},w={yolo:{name:"YOLO \u6A21\u5F0F",description:"\u5168\u90E8\u653E\u5F00\uFF0C\u65E0\u4EFB\u4F55\u9650\u5236",permissionMode:"bypassPermissions",permissions:{allow:["Bash(*)","Read(*)","Edit(*)","Write(*)","WebFetch(*)","WebSearch(*)","Glob(*)","Grep(*)","LSP(*)","NotebookEdit(*)"],deny:[]}},dev:{name:"\u5F00\u53D1\u6A21\u5F0F",description:"\u65E5\u5E38\u5F00\u53D1\u6743\u9650\uFF0C\u4FDD\u62A4\u654F\u611F\u6587\u4EF6",permissionMode:"acceptEdits",permissions:{allow:["Read(*)","Edit(*)","Write(*)","Glob(*)","Grep(*)","LSP(*)","NotebookEdit(*)","Bash(npm:*)","Bash(pnpm:*)","Bash(yarn:*)","Bash(bun:*)","Bash(node:*)","Bash(npx:*)","Bash(git:*)","Bash(tsc:*)","Bash(tsx:*)","Bash(eslint:*)","Bash(prettier:*)","Bash(jest:*)","Bash(vitest:*)","Bash(cargo:*)","Bash(python:*)","Bash(pip:*)","Bash(go:*)","Bash(make:*)","Bash(cmake:*)","Bash(ls:*)","Bash(cat:*)","Bash(head:*)","Bash(tail:*)","Bash(find:*)","Bash(wc:*)","Bash(mkdir:*)","Bash(cp:*)","Bash(mv:*)","Bash(touch:*)","WebSearch"],deny:["Read(.env)","Read(.env.*)","Read(**/secrets/**)","Read(**/*.pem)","Read(**/*.key)","Read(**/*credential*)","Bash(rm -rf:*)","Bash(sudo:*)","Bash(chmod:*)","Bash(chown:*)"]}},readonly:{name:"\u53EA\u8BFB\u6A21\u5F0F",description:"\u4EC5\u5141\u8BB8\u8BFB\u53D6\u548C\u641C\u7D22\uFF0C\u7981\u6B62\u4EFB\u4F55\u4FEE\u6539",permissionMode:"plan",permissions:{allow:["Read(*)","Glob(*)","Grep(*)","LSP(*)","Bash(git status:*)","Bash(git log:*)","Bash(git diff:*)","Bash(git branch:*)","Bash(git show:*)","Bash(ls:*)","Bash(cat:*)","Bash(head:*)","Bash(tail:*)","Bash(find:*)","Bash(wc:*)","Bash(file:*)","WebSearch"],deny:["Edit(*)","Write(*)","NotebookEdit(*)","Bash(rm:*)","Bash(mv:*)","Bash(cp:*)","Bash(mkdir:*)","Bash(touch:*)","Bash(git add:*)","Bash(git commit:*)","Bash(git push:*)","Bash(git checkout:*)","Bash(git reset:*)","Bash(npm install:*)","Bash(pnpm install:*)","Bash(yarn add:*)"]}},safe:{name:"\u5B89\u5168\u6A21\u5F0F",description:"\u4FDD\u5B88\u6743\u9650\uFF0C\u9002\u5408\u4E0D\u719F\u6089\u7684\u4EE3\u7801\u5E93",permissionMode:"default",permissions:{allow:["Read(*)","Glob(*)","Grep(*)","LSP(*)","Bash(git status:*)","Bash(git log:*)","Bash(git diff:*)","Bash(ls:*)","Bash(cat:*)","Bash(head:*)","Bash(tail:*)","Bash(find:*)","Bash(wc:*)"],deny:["Read(.env)","Read(.env.*)","Read(**/secrets/**)","Read(**/*.pem)","Read(**/*.key)","Read(**/*credential*)","Read(**/*password*)","Edit(*)","Write(*)","NotebookEdit(*)","Bash(curl:*)","Bash(wget:*)","Bash(ssh:*)","Bash(scp:*)","Bash(rm:*)","Bash(mv:*)","WebFetch(*)"]}},ci:{name:"CI/CD \u6A21\u5F0F",description:"\u9002\u5408\u81EA\u52A8\u5316\u6D41\u6C34\u7EBF\u7684\u6743\u9650",permissionMode:"default",permissions:{allow:["Read(*)","Edit(*)","Write(*)","Glob(*)","Grep(*)","LSP(*)","Bash(npm:*)","Bash(pnpm:*)","Bash(yarn:*)","Bash(node:*)","Bash(git:*)","Bash(docker:*)","Bash(make:*)","Bash(cargo:*)","Bash(go:*)","Bash(python:*)","Bash(pip:*)","Bash(pytest:*)","Bash(jest:*)","Bash(vitest:*)"],deny:["Read(.env.local)","Read(**/secrets/**)","Bash(sudo:*)","Bash(ssh:*)","Bash(scp:*)","WebFetch(*)","WebSearch"]}},audit:{name:"\u5BA1\u8BA1\u6A21\u5F0F",description:"\u4EC5\u8BFB\u53D6\u548C\u641C\u7D22\uFF0C\u7528\u4E8E\u5B89\u5168\u5BA1\u8BA1",permissionMode:"plan",permissions:{allow:["Read(*)","Glob(*)","Grep(*)","LSP(*)","Bash(git log:*)","Bash(git blame:*)","Bash(git show:*)","Bash(git diff:*)","Bash(ls:*)","Bash(find:*)","Bash(wc:*)","Bash(file:*)","Bash(stat:*)"],deny:["Edit(*)","Write(*)","NotebookEdit(*)","Bash(rm:*)","Bash(mv:*)","Bash(cp:*)","Bash(curl:*)","Bash(wget:*)","Bash(ssh:*)","WebFetch(*)"]}}},Ee=()=>Object.keys(w);import D from"chalk";import Ge from"cli-table3";import*as z from"fs";import*as M from"fs/promises";import*as G from"path";import*as Ce from"os";import*as Fe from"readline";var Te=G.join(Ce.homedir(),".claude","projects"),pe=G.join(Ce.homedir(),".ccem"),Oe=1,Ie=()=>G.join(pe,"usage-cache.json"),vt=()=>G.join(pe,"model-prices.json");async function je(){try{await M.access(pe)}catch{await M.mkdir(pe,{recursive:!0})}}var Ht="https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",bt=()=>G.join(__dirname,"..","model-prices.json"),oe={"claude-opus-4-5":{input_cost_per_token:5e-6,output_cost_per_token:25e-6,cache_read_input_token_cost:5e-7,cache_creation_input_token_cost:625e-8},"claude-sonnet-4-5":{input_cost_per_token:3e-6,output_cost_per_token:15e-6,cache_read_input_token_cost:3e-7,cache_creation_input_token_cost:375e-8},"claude-haiku-4-5":{input_cost_per_token:1e-6,output_cost_per_token:5e-6,cache_read_input_token_cost:1e-7,cache_creation_input_token_cost:125e-8}};function me(e){return e.replace(/-20\d{6}.*$/,"").replace(/-v\d+:\d+$/,"").replace(/^anthropic\./,"").replace(/^vertex_ai\//,"").replace(/@.*$/,"")}var te=null;async function Dt(){if(te)return te;await je();let e=vt();try{let t=await fetch(Ht,{signal:AbortSignal.timeout(1e3)});if(t.ok){let o=await t.json(),s={};for(let[n,i]of Object.entries(o))i.input_cost_per_token&&i.output_cost_per_token&&(s[n]={input_cost_per_token:i.input_cost_per_token,output_cost_per_token:i.output_cost_per_token,cache_read_input_token_cost:i.cache_read_input_token_cost,cache_creation_input_token_cost:i.cache_creation_input_token_cost});return await M.writeFile(e,JSON.stringify(s,null,2)),te=s,s}}catch{}try{let t=await M.readFile(e,"utf-8"),o=JSON.parse(t);return te=o,o}catch{}try{let t=bt(),o=await M.readFile(t,"utf-8"),s=JSON.parse(o);return te=s,s}catch{}return te=oe,oe}function Bt(e,t){if(t[e])return t[e];let o=me(e);if(t[o])return t[o];for(let[s,n]of Object.entries(t))if(s.includes(o)||o.includes(me(s)))return n;return e.includes("opus")?oe["claude-opus-4-5"]:e.includes("sonnet")?oe["claude-sonnet-4-5"]:e.includes("haiku")?oe["claude-haiku-4-5"]:oe["claude-sonnet-4-5"]}function $t(e,t){return e.inputTokens*t.input_cost_per_token+e.outputTokens*t.output_cost_per_token+e.cacheReadTokens*(t.cache_read_input_token_cost||0)+e.cacheCreationTokens*(t.cache_creation_input_token_cost||0)}function $(){return{inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,cost:0}}function U(e,t){return{inputTokens:e.inputTokens+t.inputTokens,outputTokens:e.outputTokens+t.outputTokens,cacheReadTokens:e.cacheReadTokens+t.cacheReadTokens,cacheCreationTokens:e.cacheCreationTokens+t.cacheCreationTokens,cost:e.cost+t.cost}}async function Ut(e){try{let t=await M.stat(e);return{mtime:t.mtimeMs,size:t.size}}catch{return null}}function Ft(){try{let e=Ie();if(!z.existsSync(e))return null;let t=JSON.parse(z.readFileSync(e,"utf-8"));return t.version!==Oe?null:t}catch{return null}}async function jt(){try{let e=Ie(),t=await M.readFile(e,"utf-8"),o=JSON.parse(t);return o.version!==Oe?null:o}catch{return null}}function Ke(){let e=Ft();if(!e)return null;let t=new Date,o=new Date(t.getFullYear(),t.getMonth(),t.getDate()),s=new Date(o);s.setDate(s.getDate()-s.getDay());let n={today:$(),week:$(),total:$(),dailyHistory:{},byModel:{},lastUpdated:t.toISOString()};for(let i of Object.values(e.files))for(let a of i.stats.entries){let l=new Date(a.timestamp),c=a.timestamp.split("T")[0];n.total=U(n.total,a.usage),n.dailyHistory[c]||(n.dailyHistory[c]=$()),n.dailyHistory[c]=U(n.dailyHistory[c],a.usage);let p=me(a.model);n.byModel[p]||(n.byModel[p]=$()),n.byModel[p]=U(n.byModel[p],a.usage),l>=o&&(n.today=U(n.today,a.usage)),l>=s&&(n.week=U(n.week,a.usage))}return n}async function Kt(e){try{await je(),await M.writeFile(Ie(),JSON.stringify(e,null,2))}catch{}}async function Yt(e,t,o){let s=[];try{let n=z.createReadStream(e,{encoding:"utf-8"}),i=Fe.createInterface({input:n,crlfDelay:1/0}),a=0;for await(let l of i){if(a++,a%100===0){if(o?.aborted)throw i.close(),n.destroy(),new Error("Aborted");a%1e3===0&&await new Promise(c=>setTimeout(c,0))}if(l.trim())try{let c=JSON.parse(l);if(c.type!=="assistant"||!c.message?.usage)continue;let p=c.message.model||"unknown",d=c.message.usage,_={inputTokens:d.input_tokens||0,outputTokens:d.output_tokens||0,cacheReadTokens:d.cache_read_input_tokens||0,cacheCreationTokens:d.cache_creation_input_tokens||0},m=Bt(p,t),g=$t(_,m);s.push({timestamp:c.timestamp||new Date().toISOString(),model:p,usage:{..._,cost:g}})}catch{}}}catch(n){if(n.message==="Aborted")throw n}return{entries:s}}async function Gt(){let e=[];try{if(!await M.access(Te).then(()=>!0).catch(()=>!1))return e;let o=await M.readdir(Te);for(let s of o){let n=G.join(Te,s);try{if((await M.stat(n)).isDirectory()){let a=await M.readdir(n);for(let l of a)l.endsWith(".jsonl")&&e.push(G.join(n,l))}}catch{}}}catch{}return e}async function Ye(e){if(e?.aborted)throw new Error("Aborted");let t=await Dt();if(e?.aborted)throw new Error("Aborted");let o=await Gt();if(e?.aborted)throw new Error("Aborted");let s=await jt(),n={version:Oe,files:{},lastUpdated:new Date().toISOString()},i=[],a=5,l=[];for(let m=0;m<o.length;m+=a)l.push(o.slice(m,m+a));for(let m of l){if(e?.aborted)throw new Error("Aborted");let g=m.map(async y=>{let A=await Ut(y);if(!A)return null;let C=s?.files[y],T=C&&C.meta.mtime===A.mtime&&C.meta.size===A.size,O;return T?O=C.stats:(O=await Yt(y,t,e),await new Promise(R=>setTimeout(R,0))),{file:y,meta:A,stats:O}}),h=await Promise.all(g);for(let y of h)y&&(n.files[y.file]={meta:y.meta,stats:y.stats},i.push(...y.stats.entries))}e?.aborted||Kt(n).catch(()=>{});let c=new Date,p=new Date(c.getFullYear(),c.getMonth(),c.getDate()),d=new Date(p);d.setDate(d.getDate()-d.getDay());let _={today:$(),week:$(),total:$(),dailyHistory:{},byModel:{},lastUpdated:c.toISOString()};for(let m of i){let g=new Date(m.timestamp),h=m.timestamp.split("T")[0];_.total=U(_.total,m.usage),_.dailyHistory[h]||(_.dailyHistory[h]=$()),_.dailyHistory[h]=U(_.dailyHistory[h],m.usage);let y=me(m.model);_.byModel[y]||(_.byModel[y]=$()),_.byModel[y]=U(_.byModel[y],m.usage),g>=p&&(_.today=U(_.today,m.usage)),g>=d&&(_.week=U(_.week,m.usage))}return _}function b(e){return e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"K":e.toString()}function ne(e){return e>=1||e>=.01?"$"+e.toFixed(2):"$"+e.toFixed(4)}function q(e){return e.inputTokens+e.outputTokens+e.cacheReadTokens+e.cacheCreationTokens}var r={primary:D.hex("#89B4FA"),accent:D.hex("#CBA6F7"),success:D.hex("#A6E3A1"),warning:D.hex("#F9E2AF"),danger:D.hex("#F38BA8"),text:D.white,muted:D.hex("#6C7086"),dim:D.hex("#45475A")},We=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],we=0,re=null,Wt=()=>r.primary(We[we]),Je=e=>{re||(re=setInterval(()=>{we=(we+1)%We.length,e()},80))},ue=()=>{re&&(clearInterval(re),re=null)};var ze=()=>process.stdout.columns||80,qe=(e,t,o)=>{let s=D.hex("#89B4FA"),n=D.hex("#45475A"),i=D.hex("#6C7086"),a=ze(),l=a<60,c=a<45,p=r.primary("CCEM")+" "+r.muted("Claude Code Env Manager"),d=r.primary("CCEM"),_=r.muted("Env: ")+r.primary(e),m=t.ANTHROPIC_BASE_URL||"-",g=t.ANTHROPIC_MODEL||"-",h=t.ANTHROPIC_SMALL_FAST_MODEL||"-",y=t.ANTHROPIC_API_KEY?t.ANTHROPIC_API_KEY.slice(0,2)+"\u2022\u2022\u2022\u2022"+t.ANTHROPIC_API_KEY.slice(-4):"-",A=(I,K)=>I.length>K?I.slice(0,K-3)+"...":I,C=(I,K)=>{if(I.length<=K)return I;try{let j=new URL(I),Mt=j.protocol+"//",He=j.host,_e=j.pathname+j.search,kt=He.slice(0,8),Nt=He.slice(-4),xt=_e.length>10?_e.slice(0,7)+"...":_e;return`${Mt}${kt}...${Nt}${xt}`}catch{return A(I,K)}},T=7,O;l?O=[_,r.muted("Model:".padEnd(T))+r.dim(A(g,25)),r.muted("Key:".padEnd(T))+r.dim(y)]:O=[_+(o&&w[o]?" "+r.accent(`[${w[o].name}]`):""),r.muted("URL:".padEnd(T))+r.dim(C(m,40)),r.muted("Model:".padEnd(T))+r.dim(A(g,15))+" "+r.muted("Fast:".padEnd(T))+r.dim(A(h,15)),r.muted("Key:".padEnd(T))+r.dim(y)];let R=[];if(c)R.push(` ${i("*")} ${n("\u2584\u2588\u2584")} ${i("*")}`),R.push(` ${i("*")} ${s("\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u259C\u258C")} ${i("*")}`),R.push(` ${i("*")} ${s("\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259B\u2598")} ${i("*")}`),R.push(` ${i("*")} ${s("\u2598\u2598 \u259D\u259D")} ${i("*")}`),R.push(""),R.push(d),R.push(""),O.forEach(I=>R.push(I));else{let I=l?" ":" ";R.push(""),R.push(` ${n("\u2584\u2588\u2588\u2584")} ${I}${l?d:p}`),R.push(` ${i("*")} ${s("\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u259C\u258C")} ${i("*")} ${I}${O[0]||""}`),R.push(` ${i("*")} ${s("\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259B\u2598")} ${i("*")} ${I}${O[1]||""}`),R.push(` ${i("*")} ${s("\u2598\u2598 \u259D\u259D")} ${i("*")} ${I}${O[2]||""}`),O[3]&&R.push(` ${I}${O[3]}`)}return R.join(`
3
- `)},Ve=(e,t)=>{if(t)return r.muted(" Usage: ")+Wt()+r.dim(" Loading...");if(!e)return r.muted(" Usage: ")+r.dim("No data");let s=ze()<70,n=q(e.today),i=q(e.week),a=q(e.total);return s?r.muted(" Usage: ")+r.text("Today ")+r.primary(b(n))+r.dim(" | ")+r.text("Week ")+r.primary(b(i))+r.dim(" | ")+r.text("Total ")+r.primary(b(a)):r.muted(" Usage: ")+r.text("Today ")+r.primary(b(n).padStart(6))+r.dim(` (${ne(e.today.cost)})`)+r.dim(" | ")+r.text("Week ")+r.primary(b(i).padStart(6))+r.dim(` (${ne(e.week.cost)})`)+r.dim(" | ")+r.text("Total ")+r.primary(b(a).padStart(6))+r.dim(` (${ne(e.total.cost)})`)},Xe=()=>{let e=process.stdout.columns||80;return r.dim("\u2500".repeat(e))};var Qe=e=>{let t="Start Claude Code";return e&&w[e]&&(t=`Start Claude Code ${r.muted(`(${w[e].name})`)}`),[{name:r.success(t),value:"start",short:"Start"},{name:r.primary("Switch Environment"),value:"switch",short:"Switch"},{name:r.primary("Permission Mode"),value:"perm",short:"Permission"},{name:r.accent("View Usage"),value:"usage",short:"Usage"},{name:r.text("Set Default Mode"),value:"setDefault",short:"Default"},{name:r.muted("Exit"),value:"exit",short:"Exit"}]},Jt={yolo:r.danger,dev:r.success,readonly:r.primary,safe:r.warning,ci:r.accent,audit:r.primary},Le=(e,t=!1)=>{let s=["yolo","dev","readonly","safe","ci","audit"].map(n=>{let i=w[n],a=Jt[n],c=t&&n===e?r.success(" *"):"";return{name:a(i.name.padEnd(12))+r.muted(i.description)+c,value:n,short:i.name}});return t&&s.push({name:r.muted("Clear default"),value:"clear",short:"Clear"}),s.push({name:r.muted("Back"),value:"back",short:"Back"}),s};var v={success:e=>console.log(r.success("\u2713 ")+r.text(e)),error:e=>console.log(r.danger("\u2717 ")+r.text(e)),warning:e=>console.log(r.warning("! ")+r.text(e)),info:e=>console.log(r.primary("\u203A ")+r.text(e))},Ze=()=>r.muted("Starting Claude Code...");var zt=(e,t=6)=>{let o=[],s=new Date,n=new Date(s);n.setMonth(n.getMonth()-t);let i=n.getDay(),a=n.getDate()-i+(i===0?-6:1);n.setDate(a),n.setHours(0,0,0,0);let l=[],c=new Date(n);for(;(c<=s||c.getDay()!==1)&&(l.push(c.toISOString().split("T")[0]),c.setDate(c.getDate()+1),!(c>s&&c.getDay()===1)););let p=0;for(let A of l){let C=e.dailyHistory[A];if(C){let T=q(C);T>p&&(p=T)}}let d=" ",_=-1,m=Math.ceil(l.length/7);for(let A=0;A<m;A++){let C=A*7;if(C<l.length){let T=new Date(l[C]),O=T.getMonth();if(O!==_&&A<m-1){let R=T.toLocaleString("default",{month:"short"});d+=r.muted(R),_=O}}}d=" ";let g=-1;for(let A=0;A<m;A++){let C=A*7;if(C>=l.length)break;let T=new Date(l[C]),O=T.getMonth();if(O!==g){let R=T.toLocaleString("default",{month:"short"});d+=r.muted(R.padEnd(4)),A++,g=O}else d+=" "}o.push(d);let h=["Mon","","Wed","","Fri","","Sun"],y=[" ","\u2591","\u2592","\u2593","\u2588"];for(let A=0;A<7;A++){let C=r.muted((h[A]||"").padEnd(4)+" ");for(let T=0;T<m;T++){let O=T*7+A;if(O<l.length){let R=l[O];if(new Date(R)>s)C+=" ";else{let I=e.dailyHistory[R],K=I?q(I):0,j=0;K>0&&(p===0?j=0:j=Math.ceil(K/p*4)),C+=(j===0?r.dim("\xB7"):r.primary(y[j]))+" "}}}o.push(C)}return o.push(""),o.push(" "+r.dim("Less ")+r.dim("\xB7")+" "+r.primary(y[1])+" "+r.primary(y[2])+" "+r.primary(y[3])+" "+r.primary(y[4])+" "+r.dim(" More")),o.join(`
4
- `)},et=e=>{let t=[];t.push(""),t.push(r.primary(" Token Usage Statistics")),t.push(r.dim("\u2500".repeat(60))),t.push(""),t.push(zt(e)),t.push(""),t.push(r.dim("\u2500".repeat(60)));let o=new Ge({head:[r.muted("Period"),r.muted("Input"),r.muted("Output"),r.muted("Cache Read"),r.muted("Cost")],style:{head:[],border:[]},chars:{top:"","top-mid":"","top-left":"","top-right":"",bottom:"","bottom-mid":"","bottom-left":"","bottom-right":"",left:" ","left-mid":"",mid:"","mid-mid":"",right:"","right-mid":"",middle:" "}}),s=(i,a)=>[r.text(i),r.primary(b(a.inputTokens)),r.primary(b(a.outputTokens)),r.primary(b(a.cacheReadTokens)),r.success(ne(a.cost))];o.push(s("Today",e.today)),o.push(s("This Week",e.week)),o.push(s("All Time",e.total)),t.push(o.toString());let n=Object.entries(e.byModel).sort((i,a)=>a[1].cost-i[1].cost);if(n.length>0){t.push(""),t.push(r.dim("\u2500".repeat(60))),t.push(r.muted(" By Model")),t.push("");let i=new Ge({head:[r.muted("Model"),r.muted("Tokens"),r.muted("Cost")],style:{head:[],border:[]},chars:{top:"","top-mid":"","top-left":"","top-right":"",bottom:"","bottom-mid":"","bottom-left":"","bottom-right":"",left:" ","left-mid":"",mid:"","mid-mid":"",right:"","right-mid":"",middle:" "}});for(let[a,l]of n){let c=q(l);i.push([r.text(a),r.primary(b(c)),r.success(ne(l.cost))])}t.push(i.toString())}return t.push(""),t.push(r.dim("\u2500".repeat(60))),t.push(r.muted(` Last updated: ${new Date(e.lastUpdated).toLocaleString()}`)),t.join(`
5
- `)},tt=(e,t)=>new Promise(o=>{let s=Object.keys(e),n=s.indexOf(t);n===-1&&(n=0);let i=process.stdin,a=i.isRaw;i.setRawMode(!0),i.resume();let l=1+s.length,c=!0,p=()=>{process.stdout.write("\x1B[?25l"),c||(process.stdout.write(`\x1B[${l}A`),process.stdout.write("\x1B[J")),c=!1,console.log(r.muted("?")+" "+r.text("Select environment")+" "+r.dim("(\u2191\u2193 navigate, Enter select, e edit, r rename, c copy, d delete)")),s.forEach((m,g)=>{let h=m===t,y=g===n,A=y?r.primary("\u276F "):" ",C=h?r.success(" *"):"",T=y||h?r.primary(m):r.text(m);console.log(A+T+C)})};p();let d=()=>{i.setRawMode(a??!1),i.removeListener("data",_),process.stdout.write("\x1B[?25h")},_=m=>{let g=m.toString();if(g===""&&(d(),process.exit(0)),g==="\x1B"&&m.length===1){d(),o({action:"cancel"});return}if(g==="\x1B[A"||g==="k"){n=Math.max(0,n-1),p();return}if(g==="\x1B[B"||g==="j"){n=Math.min(s.length-1,n+1),p();return}if(g==="\r"||g===`
6
- `){d(),o({action:"select",name:s[n]});return}if(g==="e"||g==="E"){d(),o({action:"edit",name:s[n]});return}if(g==="r"||g==="R"){d(),o({action:"rename",name:s[n]});return}if(g==="c"||g==="C"){d(),o({action:"copy",name:s[n]});return}if(g==="d"||g==="D"){d(),o({action:"delete",name:s[n]});return}};i.on("data",_)});import V from"fs";import S from"chalk";import ot from"cli-table3";import{spawn as qt}from"child_process";var Me=e=>{if(V.existsSync(e))try{let t=V.readFileSync(e,"utf-8");return JSON.parse(t)}catch{console.warn(S.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${e}\uFF0C\u5C06\u521B\u5EFA\u5907\u4EFD`));let t=e+".error."+Date.now();return V.copyFileSync(e,t),console.log(S.gray(`\u5907\u4EFD\u5DF2\u4FDD\u5B58\u5230: ${t}`)),{}}return{}},nt=(e,t)=>{$e(),V.writeFileSync(e,JSON.stringify(t,null,2)+`
7
- `)},Vt=(e,t)=>{let o=e.permissions?.allow||[],s=e.permissions?.deny||[],n=[...new Set([...t.allow,...o])],i=[...new Set([...t.deny,...s])];return{...e,permissions:{allow:n,deny:i}}},st=e=>{let t=w[e];t||(console.error(S.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${e}`)),console.log(S.yellow("\u53EF\u7528\u6A21\u5F0F: "+Ee().join(", "))),process.exit(1));let o=de(!0),s=Me(o),n=Vt(s,t.permissions);nt(o,n),console.log(S.green(`\u5DF2\u5E94\u7528 ${t.name}`)),console.log(S.gray(`\u914D\u7F6E\u5DF2\u5199\u5165: ${o}`)),console.log(S.gray(`\u8BF4\u660E: ${t.description}`))},rt=()=>{let e=de(!0);if(!V.existsSync(e)){console.log(S.yellow("\u6CA1\u6709\u81EA\u5B9A\u4E49\u6743\u9650\u914D\u7F6E\u9700\u8981\u91CD\u7F6E"));return}let t=Me(e);delete t.permissions,Object.keys(t).length===0?(V.unlinkSync(e),console.log(S.green("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF08\u6587\u4EF6\u4E3A\u7A7A\uFF09"))):(nt(e,t),console.log(S.green("\u6743\u9650\u914D\u7F6E\u5DF2\u91CD\u7F6E"))),console.log(S.gray(`\u6587\u4EF6: ${e}`))},it=()=>{let e=de(!0);if(!V.existsSync(e)){console.log(S.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650")),console.log(S.gray(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`));return}let t=Me(e);if(!t.permissions){console.log(S.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));return}let o=Object.entries(w).find(([a,l])=>{let c=new Set(t.permissions?.allow||[]),p=new Set(t.permissions?.deny||[]),d=new Set(l.permissions.allow),_=new Set(l.permissions.deny),m=l.permissions.allow.every(h=>c.has(h)),g=l.permissions.deny.every(h=>p.has(h));return m&&g});console.log(o?S.green(`\u5F53\u524D\u6A21\u5F0F: ${o[0]} (${o[1].name})`):S.yellow("\u5F53\u524D\u6A21\u5F0F: \u81EA\u5B9A\u4E49")),console.log(S.gray(`\u914D\u7F6E\u6587\u4EF6: ${e}`));let s=new ot({head:["\u7C7B\u578B","\u89C4\u5219"],style:{head:["cyan"]},colWidths:[10,70]}),n=t.permissions.allow||[],i=t.permissions.deny||[];s.push(["Allow",n.length>0?n.join(`
8
- `):"(\u65E0)"]),s.push(["Deny",i.length>0?i.join(`
9
- `):"(\u65E0)"]),console.log(s.toString())},at=()=>{let e=new ot({head:["\u6A21\u5F0F","\u6807\u5FD7","\u8BF4\u660E"],style:{head:["cyan"]},colWidths:[15,15,50]});Object.entries(w).forEach(([t,o])=>{e.push([o.name,`--${t}`,o.description])}),console.log(S.bold(`\u53EF\u7528\u6743\u9650\u6A21\u5F0F:
10
- `)),console.log(e.toString()),console.log(S.gray(`
11
- \u4E34\u65F6\u6A21\u5F0F: ccem <mode>`)),console.log(S.gray("\u6C38\u4E45\u6A21\u5F0F: ccem setup perms --<mode>"))},ge=async(e,t)=>{let o=w[e];o||(console.error(S.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${e}`)),console.log(S.yellow("\u53EF\u7528\u6A21\u5F0F: "+Ee().join(", "))),process.exit(1));let s=[];if(s.push("--permission-mode",o.permissionMode),o.permissions.allow.length>0){let i=o.permissions.allow.map(a=>`"${a}"`).join(" ");s.push("--allowedTools",i)}if(o.permissions.deny.length>0){let i=o.permissions.deny.map(a=>`"${a}"`).join(" ");s.push("--disallowedTools",i)}console.log(S.green(`\u5DF2\u5E94\u7528 ${o.name}\uFF08\u4E34\u65F6\uFF09`)),console.log(S.gray(`\u8BF4\u660E: ${o.description}`)),console.log("");let n={...process.env};return t&&(t.ANTHROPIC_BASE_URL&&(n.ANTHROPIC_BASE_URL=t.ANTHROPIC_BASE_URL),t.ANTHROPIC_API_KEY&&(n.ANTHROPIC_API_KEY=Y(t.ANTHROPIC_API_KEY)),t.ANTHROPIC_MODEL&&(n.ANTHROPIC_MODEL=t.ANTHROPIC_MODEL),t.ANTHROPIC_SMALL_FAST_MODEL&&(n.ANTHROPIC_SMALL_FAST_MODEL=t.ANTHROPIC_SMALL_FAST_MODEL)),new Promise(i=>{let a=qt("claude",s,{stdio:"inherit",shell:!0,env:n});a.on("exit",l=>{process.exit(l??0)}),a.on("error",l=>{console.error(S.red(`\u542F\u52A8 Claude Code \u5931\u8D25: ${l.message}`)),process.exit(1)})})};import ke from"fs";import P from"chalk";import{spawn as Xt}from"child_process";var ct=e=>{if(ke.existsSync(e))try{let t=ke.readFileSync(e,"utf-8");return JSON.parse(t)}catch{return console.warn(P.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${e}`)),{}}return{}},lt=(e,t)=>{ke.writeFileSync(e,JSON.stringify(t,null,2)+`
12
- `)},Qt=()=>{let e=Pe();try{let t=ct(e);return t.hasCompletedOnboarding===!0?(console.log(P.gray(" \u2713 hasCompletedOnboarding \u5DF2\u8BBE\u7F6E")),!0):(t.hasCompletedOnboarding=!0,lt(e,t),console.log(P.green(" \u2713 \u5DF2\u8BBE\u7F6E hasCompletedOnboarding: true")),!0)}catch(t){return console.error(P.red(` \u2717 \u8BBE\u7F6E hasCompletedOnboarding \u5931\u8D25: ${t}`)),!1}},Zt=()=>{let e=Se();try{Ue();let t=ct(e);(!t.env||typeof t.env!="object")&&(t.env={});let o=t.env,s={DISABLE_BUG_COMMAND:"1",DISABLE_ERROR_REPORTING:"1",DISABLE_TELEMETRY:"1"},n=!1;for(let[i,a]of Object.entries(s))o[i]!==a&&(o[i]=a,n=!0);return n?(lt(e,t),console.log(P.green(" \u2713 \u5DF2\u914D\u7F6E\u73AF\u5883\u53D8\u91CF:")),console.log(P.gray(" DISABLE_BUG_COMMAND=1")),console.log(P.gray(" DISABLE_ERROR_REPORTING=1")),console.log(P.gray(" DISABLE_TELEMETRY=1")),!0):(console.log(P.gray(" \u2713 \u73AF\u5883\u53D8\u91CF\u5DF2\u914D\u7F6E")),!0)}catch(t){return console.error(P.red(` \u2717 \u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u5931\u8D25: ${t}`)),!1}},eo=()=>new Promise(e=>{console.log(P.cyan(" \u2192 \u6B63\u5728\u6DFB\u52A0 chrome-devtools MCP \u5DE5\u5177..."));let t=Xt("claude",["mcp","add","chrome-devtools","npx","chrome-devtools-mcp@latest","--scope","user"],{stdio:"pipe",shell:!0}),o="",s="";t.stdout?.on("data",n=>{o+=n.toString()}),t.stderr?.on("data",n=>{s+=n.toString()}),t.on("exit",n=>{n===0?(console.log(P.green(" \u2713 \u5DF2\u6DFB\u52A0 chrome-devtools MCP \u5DE5\u5177")),e(!0)):s.includes("already exists")||o.includes("already exists")?(console.log(P.gray(" \u2713 chrome-devtools MCP \u5DE5\u5177\u5DF2\u5B58\u5728")),e(!0)):(console.error(P.red(` \u2717 \u6DFB\u52A0 MCP \u5DE5\u5177\u5931\u8D25 (code: ${n})`)),s&&console.error(P.gray(` ${s.trim()}`)),e(!1))}),t.on("error",n=>{console.error(P.red(` \u2717 \u6267\u884C claude \u547D\u4EE4\u5931\u8D25: ${n.message}`)),console.log(P.yellow(" \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Claude Code CLI")),e(!1)})}),dt=async()=>{console.log(P.bold(`
13
- \u{1F527} Claude Code \u521D\u59CB\u5316\u8BBE\u7F6E
14
- `)),console.log(P.cyan("1. \u8BBE\u7F6E onboarding \u72B6\u6001"));let e=Qt();console.log(P.cyan(`
15
- 2. \u914D\u7F6E\u9690\u79C1\u8BBE\u7F6E`));let t=Zt();console.log(P.cyan(`
16
- 3. \u5B89\u88C5 MCP \u5DE5\u5177`));let o=await eo();console.log(""),console.log(e&&t&&o?P.green.bold("\u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01"):P.yellow.bold("\u26A0\uFE0F \u90E8\u5206\u6B65\u9AA4\u672A\u5B8C\u6210\uFF0C\u8BF7\u68C0\u67E5\u4E0A\u8FF0\u9519\u8BEF")),console.log(P.gray(`
17
- \u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E:`)),console.log(P.gray(` - ${Pe()}`)),console.log(P.gray(` - ${Se()}`)),console.log("")};import{execSync as se}from"child_process";import*as E from"fs";import*as H from"path";import L from"chalk";var mt={official:{label:"\u5B98\u65B9",icon:"\u{1F3E2}"},featured:{label:"\u7CBE\u9009",icon:"\u2B50"},others:{label:"\u5176\u4ED6",icon:"\u{1F4E6}"}},ut=[{name:"frontend-design",description:"\u521B\u5EFA\u9AD8\u8D28\u91CF\u524D\u7AEF\u754C\u9762\u8BBE\u8BA1",group:"official",install:{type:"preset",name:"frontend-design"}},{name:"skill-creator",description:"\u521B\u5EFA\u65B0\u7684 Claude Code skills",group:"official",install:{type:"preset",name:"skill-creator"}},{name:"web-artifacts-builder",description:"\u6784\u5EFA\u53EF\u4EA4\u4E92\u7684 Web \u7EC4\u4EF6",group:"official",install:{type:"preset",name:"web-artifacts-builder"}},{name:"canvas-design",description:"Canvas \u7ED8\u56FE\u8BBE\u8BA1",group:"official",install:{type:"preset",name:"canvas-design"}},{name:"algorithmic-art",description:"\u7B97\u6CD5\u827A\u672F\u751F\u6210",group:"official",install:{type:"preset",name:"algorithmic-art"}},{name:"theme-factory",description:"\u4E3B\u9898\u5DE5\u5382 - \u521B\u5EFA UI \u4E3B\u9898",group:"official",install:{type:"preset",name:"theme-factory"}},{name:"mcp-builder",description:"\u6784\u5EFA MCP \u670D\u52A1\u5668",group:"official",install:{type:"preset",name:"mcp-builder"}},{name:"webapp-testing",description:"Web \u5E94\u7528\u6D4B\u8BD5",group:"official",install:{type:"preset",name:"webapp-testing"}},{name:"pdf",description:"PDF \u6587\u6863\u5904\u7406",group:"official",install:{type:"preset",name:"pdf"}},{name:"docx",description:"Word \u6587\u6863\u5904\u7406",group:"official",install:{type:"preset",name:"docx"}},{name:"pptx",description:"PowerPoint \u6F14\u793A\u6587\u7A3F\u5904\u7406",group:"official",install:{type:"preset",name:"pptx"}},{name:"xlsx",description:"Excel \u8868\u683C\u5904\u7406",group:"official",install:{type:"preset",name:"xlsx"}},{name:"brand-guidelines",description:"\u54C1\u724C\u6307\u5357\u751F\u6210",group:"official",install:{type:"preset",name:"brand-guidelines"}},{name:"doc-coauthoring",description:"\u6587\u6863\u534F\u4F5C\u7F16\u5199",group:"official",install:{type:"preset",name:"doc-coauthoring"}},{name:"internal-comms",description:"\u5185\u90E8\u901A\u4FE1\u6587\u6863",group:"official",install:{type:"preset",name:"internal-comms"}},{name:"slack-gif-creator",description:"Slack GIF \u521B\u5EFA\u5668",group:"official",install:{type:"preset",name:"slack-gif-creator"}},{name:"superpowers",description:"Claude Code Plan\u6A21\u5F0F\u5347\u7EA7\u7248\uFF0C\u8FDE\u7EED\u8FFD\u95EE\u8BA8\u8BBA\u786E\u5B9A\u5F00\u53D1\u65B9\u6848",group:"featured",install:{type:"plugin",marketplace:"obra/superpowers-marketplace",package:"superpowers@superpowers-marketplace"}},{name:"ui-ux-pro-max",description:"\u4E13\u4E1A UI/UX \u8BBE\u8BA1",group:"featured",install:{type:"github",url:"https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/tree/main/.claude/skills/ui-ux-pro-max"}},{name:"Humanizer-zh",description:"\u53BB\u9664\u6587\u672C\u4E2D AI \u751F\u6210\u75D5\u8FF9\uFF0C\u6539\u5199\u5F97\u66F4\u81EA\u7136\u3001\u66F4\u50CF\u4EBA\u7C7B\u4E66\u5199",group:"featured",install:{type:"github",url:"https://github.com/op7418/Humanizer-zh"}},{name:"skill-writer",description:"\u6307\u5BFC\u7528\u6237\u4E3A Claude Code \u521B\u5EFA\u4EE3\u7406\u6280\u80FD",group:"others",install:{type:"github",url:"https://github.com/pytorch/pytorch/tree/main/.claude/skills/skill-writer"}}];function gt(e){return ut.filter(t=>t.group===e)}function ft(){return["official","featured","others"]}function pt(e){if(/^[\w-]+\/[\w-]+$/.test(e)){let[a,l]=e.split("/");return{owner:a,repo:l,branch:"main",path:""}}let t=e.match(/github\.com\/([^/]+)\/([^/]+)(?:\/tree\/([^/]+)(?:\/(.*))?)?/);if(!t)return null;let[,o,s,n="main",i=""]=t;return{owner:o,repo:s.replace(/\.git$/,""),branch:n,path:i}}function Ne(){return H.join(process.cwd(),".claude","skills")}function to(){let e=Ne();return E.existsSync(e)?oo(e):E.mkdirSync(e,{recursive:!0}),e}function oo(e){try{let t=E.readdirSync(e,{withFileTypes:!0});for(let o of t)if(o.isDirectory()&&o.name.startsWith(".tmp-")){let s=H.join(e,o.name);E.rmSync(s,{recursive:!0})}}catch{}}function fe(e,t,o,s,n){let i=to(),a=H.join(i,n);E.existsSync(a)&&(console.log(L.yellow(`Skill "${n}" already exists. Updating...`)),E.rmSync(a,{recursive:!0}));let l=`https://github.com/${e}/${t}.git`,c=H.join(i,`.tmp-${Date.now()}`);try{E.mkdirSync(c,{recursive:!0}),se("git init",{cwd:c,stdio:"pipe"}),se(`git remote add origin ${l}`,{cwd:c,stdio:"pipe"}),se("git config core.sparseCheckout true",{cwd:c,stdio:"pipe"});let p=H.join(c,".git","info","sparse-checkout");E.writeFileSync(p,s?`${s}/
18
- `:`*
19
- `),se(`git pull --depth=1 origin ${o}`,{cwd:c,stdio:"pipe"});let d=s?H.join(c,s):c;if(!E.existsSync(d))throw new Error(`Path "${s}" not found in repository`);return ht(d,a),console.log(L.green(`Successfully installed skill "${n}"`)),!0}catch(p){let d=p instanceof Error?p.message:String(p);return console.error(L.red(`Failed to download skill: ${d}`)),!1}finally{E.existsSync(c)&&E.rmSync(c,{recursive:!0})}}function ht(e,t){E.mkdirSync(t,{recursive:!0});let o=E.readdirSync(e,{withFileTypes:!0});for(let s of o){if(s.name===".git")continue;let n=H.join(e,s.name),i=H.join(t,s.name);s.isDirectory()?ht(n,i):E.copyFileSync(n,i)}}function he(e){let t=ut.find(n=>n.name===e);if(t){if(t.install.type==="preset")return fe("anthropics","skills","main",`skills/${t.install.name}`,t.name);if(t.install.type==="github"){let n=pt(t.install.url);return n?fe(n.owner,n.repo,n.branch,n.path,t.name):(console.error(L.red(`Invalid GitHub URL in preset: ${t.install.url}`)),!1)}else if(t.install.type==="plugin")return console.error(L.yellow(`Plugin installation not yet supported for "${t.name}"`)),console.log(L.gray(`Marketplace: ${t.install.marketplace}`)),console.log(L.gray(`Package: ${t.install.package}`)),!1}let o=pt(e);if(!o)return console.error(L.red("Invalid GitHub URL or preset name")),console.log(L.gray("Examples:")),console.log(L.gray(" ccem skill add frontend-design")),console.log(L.gray(" ccem skill add https://github.com/owner/repo")),console.log(L.gray(" ccem skill add https://github.com/owner/repo/tree/main/path")),!1;let s;return o.path?s=H.basename(o.path):s=o.repo,fe(o.owner,o.repo,o.branch,o.path,s)}function yt(){let e=Ne();return E.existsSync(e)?E.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>({name:o.name,path:H.join(e,o.name)})):[]}function _t(e){let t=Ne(),o=H.join(t,e);return E.existsSync(o)?(E.rmSync(o,{recursive:!0}),console.log(L.green(`Removed skill "${e}"`)),!0):(console.error(L.red(`Skill "${e}" not found`)),!1)}function no(e,t){try{return console.log(L.cyan(`Adding marketplace: ${e}...`)),se(`claude plugin marketplace add ${e}`,{stdio:"inherit"}),console.log(L.cyan(`Installing package: ${t}...`)),se(`claude plugin install ${t}`,{stdio:"inherit"}),console.log(L.green(`Successfully installed ${t}`)),!0}catch(o){let s=o instanceof Error?o.message:String(o);return console.error(L.red(`Failed to install from marketplace: ${s}`)),!1}}function At(e){switch(console.log(L.cyan(`Installing ${e.name}...`)),e.install.type){case"preset":let t={repo:"anthropics/skills",path:`skills/${e.install.name}`,branch:"main"},[o,s]=t.repo.split("/");return fe(o,s,t.branch,t.path,e.name);case"github":return he(e.install.url);case"plugin":return no(e.install.marketplace,e.install.package)}}import{render as ao}from"ink";import{useState as Pt,useEffect as so}from"react";import{Box as W,Text as X,useInput as ro,useApp as io}from"ink";import{jsx as F,jsxs as ie}from"react/jsx-runtime";function St({onSelect:e,onCancel:t}){let{exit:o}=io(),s=ft(),[n,i]=Pt(0),[a,l]=Pt(0),c=s[n],d=[...gt(c),null],_=d.length-1;return so(()=>{l(0)},[n]),ro((m,g)=>{if(g.tab&&!g.shift){i(h=>(h+1)%s.length);return}if(g.tab&&g.shift){i(h=>(h-1+s.length)%s.length);return}if(g.upArrow){l(h=>Math.max(0,h-1));return}if(g.downArrow){l(h=>Math.min(_,h+1));return}if(g.return){let h=d[a];e(h===null?"custom":h);return}if(g.escape||m==="q"){t(),o();return}}),ie(W,{flexDirection:"column",children:[F(W,{marginBottom:1,children:s.map((m,g)=>{let h=mt[m],y=g===n;return F(W,{marginRight:2,children:ie(X,{bold:y,color:y?"cyan":"gray",inverse:y,children:[" ",h.icon," ",h.label," "]})},m)})}),F(W,{marginBottom:1,children:F(X,{color:"gray",children:"\u2500".repeat(50)})}),F(W,{flexDirection:"column",children:d.map((m,g)=>{let h=g===a,y=h?"\u276F ":" ";return m===null?F(W,{children:ie(X,{color:h?"yellow":"gray",children:[y,"\u8F93\u5165\u81EA\u5B9A\u4E49 GitHub URL"]})},"custom"):F(W,{children:ie(X,{color:h?"cyan":void 0,children:[y,F(X,{bold:h,children:m.name}),ie(X,{color:"gray",children:[" - ",m.description]})]})},m.name)})}),F(W,{marginTop:1,children:F(X,{color:"gray",children:"Tab \u5207\u6362\u5206\u7EC4 | \u2191\u2193 \u9009\u62E9 | Enter \u786E\u8BA4 | Esc \u53D6\u6D88"})})]})}import{jsx as co}from"react/jsx-runtime";async function Rt(){return new Promise(e=>{let t=!1,{unmount:o,waitUntilExit:s}=ao(co(St,{onSelect:n=>{t||(t=!0,o(),e(n==="custom"?{type:"custom"}:{type:"skill",skill:n}))},onCancel:()=>{t||(t=!0,o(),e({type:"cancelled"}))}}));s().then(()=>{t||e({type:"cancelled"})})})}import Et from"crypto";import x from"chalk";import lo from"conf";var Tt=new lo({projectName:"claude-code-env-manager"}),po=(e,t)=>{let o=Et.scryptSync(t,"ccem-salt",32),s=Buffer.from(e,"base64"),n=s.subarray(0,16),i=s.subarray(16).toString("hex"),a=Et.createDecipheriv("aes-256-cbc",o,n),l=a.update(i,"hex","utf8");return l+=a.final("utf8"),l},mo=(e,t)=>{if(!t.has(e))return e;let o=1,s=`${e}-remote`;for(;t.has(s);)o++,s=`${e}-remote-${o}`;return s},Ct=async(e,t)=>{console.log(x.gray("Fetching from remote..."));let o;try{o=await fetch(e)}catch(c){console.error(x.red("Error: Failed to connect to server")),console.error(x.gray(c.message)),process.exit(1)}o.status===401&&(console.error(x.red("Error: Invalid key (HTTP 401)")),process.exit(1)),o.status===429&&(console.error(x.red("Error: Too many requests, please try again later")),process.exit(1)),o.ok||(console.error(x.red(`Error: Server returned HTTP ${o.status}`)),process.exit(1));let s;try{s=await o.json()}catch{console.error(x.red("Error: Invalid response format from server")),process.exit(1)}s.encrypted||(console.error(x.red("Error: Invalid response format from server")),process.exit(1));let n;try{let c=po(s.encrypted,t);n=JSON.parse(c)}catch{console.error(x.red("Error: Decryption failed, check your --secret")),process.exit(1)}(!n.environments||typeof n.environments!="object")&&(console.error(x.red("Error: Invalid response format from server")),process.exit(1));let i=Tt.get("registries"),a=new Set(Object.keys(i)),l=[];for(let[c,p]of Object.entries(n.environments)){let d=mo(c,a),_=d!==c,m={...p};m.ANTHROPIC_API_KEY&&(m.ANTHROPIC_API_KEY=J(m.ANTHROPIC_API_KEY)),i[d]=m,a.add(d),l.push({name:d,originalName:c,renamed:_})}Tt.set("registries",i),console.log(x.green(`
20
- Loaded ${l.length} environment(s) from remote:`));for(let c of l)c.renamed?console.log(x.yellow(` + ${c.originalName} \u2192 ${c.name} (renamed, local exists)`)):console.log(x.green(` + ${c.name} (new)`));console.log(x.gray(`
21
- Run 'ccem ls' to see all environments.`))};var ho=fo(import.meta.url),yo=ye.dirname(ho),_o=ye.resolve(yo,"..","package.json"),Ao=JSON.parse(wt.readFileSync(_o,"utf-8")),N=new uo,f=new go({projectName:"claude-code-env-manager",defaults:{registries:{official:{ANTHROPIC_BASE_URL:"https://api.anthropic.com",ANTHROPIC_MODEL:"claude-sonnet-4-5-20250929",ANTHROPIC_SMALL_FAST_MODEL:"claude-haiku-4-5-20251001"}},current:"official",defaultMode:null}}),ce=["yolo","dev","readonly","safe","ci","audit"],Q=null,ae=!0;var Z=null,Po=e=>{let t=Ke();t?(Q=t,ae=!1):e&&Je(e),Z&&Z.abort(),Z=new AbortController;let o=Z.signal;Ye(o).then(s=>{if(o.aborted)return;let n=ae||Q&&s&&Q.lastUpdated!==s.lastUpdated;Q=s,ae=!1,ue(),n&&e&&e()}).catch(s=>{s.message!=="Aborted"&&(ae=!1,ue())})};N.name("ccem").description("Claude Code Environment Manager - \u7BA1\u7406 Claude Code \u73AF\u5883\u53D8\u91CF\u548C\u6743\u9650").version(Ao.version).option("--mode","\u67E5\u770B\u5F53\u524D\u6743\u9650\u6A21\u5F0F").option("--list-modes","\u5217\u51FA\u6240\u6709\u53EF\u7528\u6743\u9650\u6A21\u5F0F");ce.forEach(e=>{let t=w[e];N.command(e).description(`\u4E34\u65F6\u5E94\u7528 ${t.name}\uFF0C\u9000\u51FA\u540E\u8FD8\u539F`).action(async()=>{let o=f.get("registries"),s=f.get("current"),n=o[s];await ge(e,n)})});var Lt=(e,t)=>{if(!process.stdout.isTTY)return;let o=f.get("current"),n=f.get("registries")[o],i=f.get("defaultMode");n&&(console.log(qe(o,{ANTHROPIC_BASE_URL:n.ANTHROPIC_BASE_URL,ANTHROPIC_API_KEY:n.ANTHROPIC_API_KEY?Y(n.ANTHROPIC_API_KEY):void 0,ANTHROPIC_MODEL:n.ANTHROPIC_MODEL,ANTHROPIC_SMALL_FAST_MODEL:n.ANTHROPIC_SMALL_FAST_MODEL},i)),console.log(""),console.log(Ve(e,t)),console.log(Xe()),console.log(""))},So=async e=>{let t=f.get("registries");if(!t[e]){console.error(u.red(`Environment '${e}' not found.`));return}f.set("current",e),process.stdout.isTTY?console.log(u.green(`Switched to environment '${e}'`)):console.error(u.green(`Switched to environment '${e}'`)),Lt(null,!1);let o=t[e],s=[];o.ANTHROPIC_BASE_URL&&s.push(`export ANTHROPIC_BASE_URL="${o.ANTHROPIC_BASE_URL}"`),o.ANTHROPIC_API_KEY&&s.push(`export ANTHROPIC_API_KEY="${Y(o.ANTHROPIC_API_KEY)}"`),o.ANTHROPIC_MODEL&&s.push(`export ANTHROPIC_MODEL="${o.ANTHROPIC_MODEL}"`),o.ANTHROPIC_SMALL_FAST_MODEL&&s.push(`export ANTHROPIC_SMALL_FAST_MODEL="${o.ANTHROPIC_SMALL_FAST_MODEL}"`),process.stdout.isTTY?(console.log(u.yellow(`
22
- To apply to current shell immediately, run:`)),console.log(u.cyan("eval $(ccem env)")),console.log(u.yellow(`
23
- Or manually export:`)),s.forEach(n=>console.log(n))):s.forEach(n=>console.log(n))};N.command("ls").description("List all configured environments").action(()=>{let e=f.get("registries"),t=f.get("current"),o=new Ot({head:["Name","Base URL","Model"],style:{head:["cyan"]}});Object.keys(e).forEach(s=>{let n=e[s],i=s===t?u.green("* "):" ";o.push([i+s,n.ANTHROPIC_BASE_URL||"-",n.ANTHROPIC_MODEL||"-"])}),console.log(o.toString())});N.command("use <name>").description("Switch to a specific environment").action(async e=>{await So(e)});N.command("add <name>").description("Add a new environment configuration").action(async e=>{let t=f.get("registries");if(t[e]){console.log(u.red(`Environment '${e}' already exists.`));return}let{usePreset:o}=await k.prompt([{type:"confirm",name:"usePreset",message:"Do you want to use a preset configuration?",default:!0}]),s={};if(o){let{presetName:i}=await k.prompt([{type:"list",name:"presetName",message:"Select a preset:",choices:Object.keys(Re)}]);s=Re[i]}let n=await k.prompt([{type:"input",name:"ANTHROPIC_BASE_URL",message:"Enter ANTHROPIC_BASE_URL:",default:s.ANTHROPIC_BASE_URL||"https://api.anthropic.com"},{type:"password",name:"ANTHROPIC_API_KEY",message:"Enter ANTHROPIC_API_KEY:"},{type:"input",name:"ANTHROPIC_MODEL",message:"Enter ANTHROPIC_MODEL:",default:s.ANTHROPIC_MODEL||"claude-sonnet-4-5-20250929"},{type:"input",name:"ANTHROPIC_SMALL_FAST_MODEL",message:"Enter ANTHROPIC_SMALL_FAST_MODEL:",default:s.ANTHROPIC_SMALL_FAST_MODEL||"claude-haiku-4-5-20251001"}]);n.ANTHROPIC_API_KEY&&(n.ANTHROPIC_API_KEY=J(n.ANTHROPIC_API_KEY)),t[e]=n,f.set("registries",t),console.log(u.green(`Environment '${e}' added successfully.`))});N.command("del <name>").description("Delete an environment configuration").action(e=>{let t=f.get("registries");if(!t[e]){console.log(u.red(`Environment '${e}' not found.`));return}if(e==="official"){console.log(u.red("Cannot delete default 'official' environment."));return}delete t[e],f.set("registries",t),f.get("current")===e&&(f.set("current","official"),console.log(u.yellow("Deleted current environment. Switched back to 'official'."))),console.log(u.green(`Environment '${e}' deleted.`))});N.command("rename <old> <new>").description("Rename an environment configuration").action((e,t)=>{let o=f.get("registries");if(!o[e]){console.log(u.red(`Environment '${e}' not found.`));return}if(o[t]){console.log(u.red(`Environment '${t}' already exists.`));return}if(e==="official"){console.log(u.red("Cannot rename default 'official' environment."));return}o[t]=o[e],delete o[e],f.set("registries",o),f.get("current")===e&&f.set("current",t),console.log(u.green(`Environment '${e}' renamed to '${t}'.`))});N.command("cp <source> <target>").description("Copy an environment configuration").action(async(e,t)=>{let o=f.get("registries");if(!o[e]){console.log(u.red(`Environment '${e}' not found.`));return}if(o[t]){console.log(u.red(`Environment '${t}' already exists.`));return}o[t]={...o[e]},f.set("registries",o),console.log(u.green(`Environment '${e}' copied to '${t}'.`));let{modify:s}=await k.prompt([{type:"confirm",name:"modify",message:"Do you want to modify the configuration?",default:!1}]);if(s){let n=o[t],i=await k.prompt([{type:"input",name:"ANTHROPIC_BASE_URL",message:"ANTHROPIC_BASE_URL:",default:n.ANTHROPIC_BASE_URL},{type:"password",name:"ANTHROPIC_API_KEY",message:"ANTHROPIC_API_KEY (leave empty to keep current):"},{type:"input",name:"ANTHROPIC_MODEL",message:"ANTHROPIC_MODEL:",default:n.ANTHROPIC_MODEL},{type:"input",name:"ANTHROPIC_SMALL_FAST_MODEL",message:"ANTHROPIC_SMALL_FAST_MODEL:",default:n.ANTHROPIC_SMALL_FAST_MODEL}]);i.ANTHROPIC_BASE_URL&&(n.ANTHROPIC_BASE_URL=i.ANTHROPIC_BASE_URL),i.ANTHROPIC_API_KEY&&(n.ANTHROPIC_API_KEY=J(i.ANTHROPIC_API_KEY)),i.ANTHROPIC_MODEL&&(n.ANTHROPIC_MODEL=i.ANTHROPIC_MODEL),i.ANTHROPIC_SMALL_FAST_MODEL&&(n.ANTHROPIC_SMALL_FAST_MODEL=i.ANTHROPIC_SMALL_FAST_MODEL),o[t]=n,f.set("registries",o),console.log(u.green(`Environment '${t}' updated.`))}});N.command("current").description("Show current environment name").action(()=>{let e=f.get("current");console.log(u.green(e))});N.command("env").description("Output environment variables for shell eval").option("--json","Output as JSON").action(e=>{let t=f.get("registries"),o=f.get("current"),s=t[o];if(!s)return;let n={...s};n.ANTHROPIC_API_KEY&&(n.ANTHROPIC_API_KEY=Y(n.ANTHROPIC_API_KEY)),e.json?console.log(JSON.stringify(n,null,2)):(n.ANTHROPIC_BASE_URL&&console.log(`export ANTHROPIC_BASE_URL="${n.ANTHROPIC_BASE_URL}"`),n.ANTHROPIC_API_KEY&&console.log(`export ANTHROPIC_API_KEY="${n.ANTHROPIC_API_KEY}"`),n.ANTHROPIC_MODEL&&console.log(`export ANTHROPIC_MODEL="${n.ANTHROPIC_MODEL}"`),n.ANTHROPIC_SMALL_FAST_MODEL&&console.log(`export ANTHROPIC_SMALL_FAST_MODEL="${n.ANTHROPIC_SMALL_FAST_MODEL}"`))});N.command("run <command...>").description("Run a command with the current environment variables").action(e=>{let t=f.get("registries"),o=f.get("current"),s=t[o];s||(console.error(u.red("No environment configuration found.")),process.exit(1));let n={...process.env};s.ANTHROPIC_BASE_URL&&(n.ANTHROPIC_BASE_URL=s.ANTHROPIC_BASE_URL),s.ANTHROPIC_API_KEY&&(n.ANTHROPIC_API_KEY=Y(s.ANTHROPIC_API_KEY||"")),s.ANTHROPIC_MODEL&&(n.ANTHROPIC_MODEL=s.ANTHROPIC_MODEL),s.ANTHROPIC_SMALL_FAST_MODEL&&(n.ANTHROPIC_SMALL_FAST_MODEL=s.ANTHROPIC_SMALL_FAST_MODEL);let[i,...a]=e;It(i,a,{env:n,stdio:"inherit",shell:!0}).on("exit",c=>{process.exit(c??0)})});var xe=N.command("setup").description("Setup commands for permanent configurations");xe.command("perms").description("\u6C38\u4E45\u914D\u7F6E\u6743\u9650\u6A21\u5F0F").option("--yolo","\u5E94\u7528 YOLO \u6A21\u5F0F\uFF08\u5168\u90E8\u653E\u5F00\uFF09").option("--dev","\u5E94\u7528\u5F00\u53D1\u6A21\u5F0F").option("--readonly","\u5E94\u7528\u53EA\u8BFB\u6A21\u5F0F").option("--safe","\u5E94\u7528\u5B89\u5168\u6A21\u5F0F").option("--ci","\u5E94\u7528 CI/CD \u6A21\u5F0F").option("--audit","\u5E94\u7528\u5BA1\u8BA1\u6A21\u5F0F").option("--reset","\u91CD\u7F6E\u6743\u9650\u914D\u7F6E").action(function(){let e=this.opts();if(e.reset){rt();return}for(let t of ce)if(e[t]){st(t);return}console.log(u.yellow("\u8BF7\u6307\u5B9A\u4E00\u4E2A\u6743\u9650\u6A21\u5F0F\uFF0C\u4F8B\u5982: ccem setup perms --dev")),console.log(u.gray("\u53EF\u7528\u6A21\u5F0F: "+ce.join(", "))),console.log(u.gray("\u91CD\u7F6E\u6743\u9650: ccem setup perms --reset"))});xe.command("default-mode").description("\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F").option("--yolo","YOLO \u6A21\u5F0F").option("--dev","\u5F00\u53D1\u6A21\u5F0F").option("--readonly","\u53EA\u8BFB\u6A21\u5F0F").option("--safe","\u5B89\u5168\u6A21\u5F0F").option("--ci","CI/CD \u6A21\u5F0F").option("--audit","\u5BA1\u8BA1\u6A21\u5F0F").option("--reset","\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F").action(function(){let e=this.opts();if(e.reset){f.set("defaultMode",null),console.log(u.green("\u5DF2\u6E05\u9664\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F"));return}for(let o of ce)if(e[o]){f.set("defaultMode",o),console.log(u.green(`\u5DF2\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F: ${w[o].name}`)),console.log(u.gray("\u4E0B\u6B21\u542F\u52A8 ccem \u65F6\u5C06\u9ED8\u8BA4\u4F7F\u7528\u6B64\u6A21\u5F0F"));return}let t=f.get("defaultMode");t&&w[t]?console.log(u.green(`\u5F53\u524D\u9ED8\u8BA4\u6A21\u5F0F: ${w[t].name}`)):console.log(u.yellow("\u672A\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F")),console.log(u.gray(`
24
- \u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --dev`)),console.log(u.gray("\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --reset")),console.log(u.gray("\u53EF\u7528\u6A21\u5F0F: "+ce.join(", ")))});xe.command("init").description("\u521D\u59CB\u5316 Claude Code \u5168\u5C40\u914D\u7F6E\uFF08\u8DF3\u8FC7 onboarding\u3001\u7981\u7528\u9065\u6D4B\u3001\u5B89\u88C5 MCP \u5DE5\u5177\uFF09").action(async()=>{await dt()});var ve=N.command("skill").description("\u7BA1\u7406 Claude Code skills");ve.command("add [url]").description("\u6DFB\u52A0 skill\uFF08\u4ECE\u5B98\u65B9\u9884\u8BBE\u6216 GitHub URL\uFF09").action(async e=>{if(e)he(e);else{let t=await Rt();if(t.type==="cancelled"){console.log(u.yellow("\u5DF2\u53D6\u6D88"));return}if(t.type==="custom"){let{customUrl:o}=await k.prompt([{type:"input",name:"customUrl",message:"\u8F93\u5165 GitHub URL:",validate:s=>s.trim()?!s.includes("github.com")&&!/^[\w-]+\/[\w-]+$/.test(s)?"\u8BF7\u8F93\u5165\u6709\u6548\u7684 GitHub URL \u6216 owner/repo \u683C\u5F0F":!0:"\u8BF7\u8F93\u5165\u6709\u6548\u7684 URL"}]);he(o)}else t.skill&&At(t.skill)}});ve.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills").action(()=>{let e=yt();if(e.length===0){console.log(u.yellow("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709\u5B89\u88C5\u4EFB\u4F55 skill")),console.log(u.gray("\u4F7F\u7528 ccem skill add \u6DFB\u52A0 skills"));return}let t=new Ot({head:["Name","Path"],style:{head:["cyan"]}});e.forEach(o=>{t.push([u.green(o.name),u.gray(o.path)])}),console.log(t.toString())});ve.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action(e=>{_t(e)});N.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>","\u89E3\u5BC6\u5BC6\u94A5").action(async(e,t)=>{await Ct(e,t.secret)});N.action(async e=>{if(e.mode){it();return}if(e.listModes){at();return}for(Po();;){console.clear(),Lt(Q,ae),console.log("");let t=f.get("defaultMode"),o=f.get("registries"),s=f.get("current"),n=o[s],{action:i}=await k.prompt([{type:"list",name:"action",message:u.gray("Select action"),choices:Qe(t),prefix:u.gray("?")}]);if(i==="start"){if(Z&&(Z.abort(),Z=null),ue(),n||(v.error("No environment configuration found."),process.exit(1)),t&&w[t])await ge(t,n);else{let a={...process.env};n.ANTHROPIC_BASE_URL&&(a.ANTHROPIC_BASE_URL=n.ANTHROPIC_BASE_URL),n.ANTHROPIC_API_KEY&&(a.ANTHROPIC_API_KEY=Y(n.ANTHROPIC_API_KEY||"")),n.ANTHROPIC_MODEL&&(a.ANTHROPIC_MODEL=n.ANTHROPIC_MODEL),n.ANTHROPIC_SMALL_FAST_MODEL&&(a.ANTHROPIC_SMALL_FAST_MODEL=n.ANTHROPIC_SMALL_FAST_MODEL),console.log(Ze()),It("claude",[],{env:a,stdio:"inherit",shell:!0}).on("exit",c=>{process.exit(c??0)})}return}else if(i==="usage")console.clear(),Q?console.log(et(Q)):v.warning("No usage data available"),await k.prompt([{type:"input",name:"continue",message:u.gray("Press Enter to continue..."),prefix:""}]);else if(i==="switch"){let a=await tt(o,s);if(a.action==="select")f.set("current",a.name);else if(a.action==="edit"){let l=o[a.name];console.log(u.yellow(`
25
- Editing environment '${a.name}'`));let c=await k.prompt([{type:"input",name:"ANTHROPIC_BASE_URL",message:"ANTHROPIC_BASE_URL:",default:l.ANTHROPIC_BASE_URL},{type:"password",name:"ANTHROPIC_API_KEY",message:"ANTHROPIC_API_KEY (leave empty to keep current):"},{type:"input",name:"ANTHROPIC_MODEL",message:"ANTHROPIC_MODEL:",default:l.ANTHROPIC_MODEL},{type:"input",name:"ANTHROPIC_SMALL_FAST_MODEL",message:"ANTHROPIC_SMALL_FAST_MODEL:",default:l.ANTHROPIC_SMALL_FAST_MODEL}]);c.ANTHROPIC_BASE_URL&&(l.ANTHROPIC_BASE_URL=c.ANTHROPIC_BASE_URL),c.ANTHROPIC_API_KEY&&(l.ANTHROPIC_API_KEY=J(c.ANTHROPIC_API_KEY)),c.ANTHROPIC_MODEL&&(l.ANTHROPIC_MODEL=c.ANTHROPIC_MODEL),c.ANTHROPIC_SMALL_FAST_MODEL&&(l.ANTHROPIC_SMALL_FAST_MODEL=c.ANTHROPIC_SMALL_FAST_MODEL),o[a.name]=l,f.set("registries",o),v.success(`Environment '${a.name}' updated.`),await new Promise(p=>setTimeout(p,800))}else if(a.action==="rename")if(a.name==="official")v.error("Cannot rename default 'official' environment."),await new Promise(l=>setTimeout(l,800));else{let{newName:l}=await k.prompt([{type:"input",name:"newName",message:`Rename '${a.name}' to:`,validate:c=>c.trim()?o[c]?`Environment '${c}' already exists`:!0:"Name cannot be empty"}]);o[l]=o[a.name],delete o[a.name],f.set("registries",o),s===a.name&&f.set("current",l),v.success(`Environment '${a.name}' renamed to '${l}'.`),await new Promise(c=>setTimeout(c,800))}else if(a.action==="copy"){let{targetName:l}=await k.prompt([{type:"input",name:"targetName",message:`Copy '${a.name}' to:`,validate:p=>p.trim()?o[p]?`Environment '${p}' already exists`:!0:"Name cannot be empty"}]);o[l]={...o[a.name]},f.set("registries",o),v.success(`Environment '${a.name}' copied to '${l}'.`);let{modify:c}=await k.prompt([{type:"confirm",name:"modify",message:"Do you want to modify the configuration?",default:!1}]);if(c){let p=o[l],d=await k.prompt([{type:"input",name:"ANTHROPIC_BASE_URL",message:"ANTHROPIC_BASE_URL:",default:p.ANTHROPIC_BASE_URL},{type:"password",name:"ANTHROPIC_API_KEY",message:"ANTHROPIC_API_KEY (leave empty to keep current):"},{type:"input",name:"ANTHROPIC_MODEL",message:"ANTHROPIC_MODEL:",default:p.ANTHROPIC_MODEL},{type:"input",name:"ANTHROPIC_SMALL_FAST_MODEL",message:"ANTHROPIC_SMALL_FAST_MODEL:",default:p.ANTHROPIC_SMALL_FAST_MODEL}]);d.ANTHROPIC_BASE_URL&&(p.ANTHROPIC_BASE_URL=d.ANTHROPIC_BASE_URL),d.ANTHROPIC_API_KEY&&(p.ANTHROPIC_API_KEY=J(d.ANTHROPIC_API_KEY)),d.ANTHROPIC_MODEL&&(p.ANTHROPIC_MODEL=d.ANTHROPIC_MODEL),d.ANTHROPIC_SMALL_FAST_MODEL&&(p.ANTHROPIC_SMALL_FAST_MODEL=d.ANTHROPIC_SMALL_FAST_MODEL),o[l]=p,f.set("registries",o),v.success(`Environment '${l}' updated.`)}await new Promise(p=>setTimeout(p,800))}else if(a.action==="delete")if(a.name==="official")v.error("Cannot delete default 'official' environment."),await new Promise(l=>setTimeout(l,800));else{let{confirm:l}=await k.prompt([{type:"confirm",name:"confirm",message:`Are you sure you want to delete '${a.name}'?`,default:!1}]);l&&(delete o[a.name],f.set("registries",o),s===a.name?(f.set("current","official"),v.warning("Deleted current environment. Switched back to 'official'.")):v.success(`Environment '${a.name}' deleted.`)),await new Promise(c=>setTimeout(c,800))}}else if(i==="perm"){let{permMode:a}=await k.prompt([{type:"list",name:"permMode",message:u.gray("Select permission mode"),choices:Le(null,!1),prefix:u.gray("?")}]);if(a!=="back"){await ge(a,n);return}}else if(i==="setDefault"){let a=f.get("defaultMode"),{selectedMode:l}=await k.prompt([{type:"list",name:"selectedMode",message:u.gray("Set default permission mode"),choices:Le(a,!0),prefix:u.gray("?")}]);l==="clear"?(f.set("defaultMode",null),v.success("Default mode cleared"),await new Promise(c=>setTimeout(c,800))):l!=="back"&&(f.set("defaultMode",l),v.success(`Default mode set: ${w[l].name}`),await new Promise(c=>setTimeout(c,800)))}else process.exit(0)}});N.parse(process.argv);
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import Conf2 from "conf";
6
+ import inquirer from "inquirer";
7
+ import chalk7 from "chalk";
8
+ import Table3 from "cli-table3";
9
+ import { spawn as spawn3 } from "child_process";
10
+ import * as fs7 from "fs";
11
+ import * as path5 from "path";
12
+ import { fileURLToPath } from "url";
13
+ import { encrypt as encrypt2, decrypt as decrypt2, ENV_PRESETS, PERMISSION_PRESETS as PERMISSION_PRESETS4, getCcemConfigDir, ensureCcemDir as ensureCcemDir3, getCcemConfigPath, getLegacyConfigPath } from "@ccem/core";
14
+
15
+ // src/ui.ts
16
+ import chalk from "chalk";
17
+ import Table from "cli-table3";
18
+ import { PERMISSION_PRESETS } from "@ccem/core";
19
+
20
+ // src/usage.ts
21
+ import * as fs from "fs";
22
+ import * as fsPromises from "fs/promises";
23
+ import * as path from "path";
24
+ import * as os from "os";
25
+ import * as readline from "readline";
26
+ var CLAUDE_PROJECTS_DIR = path.join(os.homedir(), ".claude", "projects");
27
+ var CCEM_DIR = path.join(os.homedir(), ".ccem");
28
+ var CACHE_VERSION = 1;
29
+ var getCachePath = () => path.join(CCEM_DIR, "usage-cache.json");
30
+ var getPricesPath = () => path.join(CCEM_DIR, "model-prices.json");
31
+ async function ensureCcemDir() {
32
+ try {
33
+ await fsPromises.access(CCEM_DIR);
34
+ } catch {
35
+ await fsPromises.mkdir(CCEM_DIR, { recursive: true });
36
+ }
37
+ }
38
+ var LITELLM_PRICES_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
39
+ var getBundledPricesPath = () => {
40
+ return path.join(__dirname, "..", "model-prices.json");
41
+ };
42
+ var DEFAULT_PRICES = {
43
+ "claude-opus-4-5": {
44
+ input_cost_per_token: 5e-6,
45
+ output_cost_per_token: 25e-6,
46
+ cache_read_input_token_cost: 5e-7,
47
+ cache_creation_input_token_cost: 625e-8
48
+ },
49
+ "claude-sonnet-4-5": {
50
+ input_cost_per_token: 3e-6,
51
+ output_cost_per_token: 15e-6,
52
+ cache_read_input_token_cost: 3e-7,
53
+ cache_creation_input_token_cost: 375e-8
54
+ },
55
+ "claude-haiku-4-5": {
56
+ input_cost_per_token: 1e-6,
57
+ output_cost_per_token: 5e-6,
58
+ cache_read_input_token_cost: 1e-7,
59
+ cache_creation_input_token_cost: 125e-8
60
+ }
61
+ };
62
+ function normalizeModelName(model) {
63
+ const normalized = model.replace(/-20\d{6}.*$/, "").replace(/-v\d+:\d+$/, "").replace(/^anthropic\./, "").replace(/^vertex_ai\//, "").replace(/@.*$/, "");
64
+ return normalized;
65
+ }
66
+ var pricesCache = null;
67
+ async function loadPrices() {
68
+ if (pricesCache) return pricesCache;
69
+ await ensureCcemDir();
70
+ const pricesPath = getPricesPath();
71
+ try {
72
+ const response = await fetch(LITELLM_PRICES_URL, { signal: AbortSignal.timeout(1e3) });
73
+ if (response.ok) {
74
+ const data = await response.json();
75
+ const prices = {};
76
+ for (const [key, value] of Object.entries(data)) {
77
+ if (value.input_cost_per_token && value.output_cost_per_token) {
78
+ prices[key] = {
79
+ input_cost_per_token: value.input_cost_per_token,
80
+ output_cost_per_token: value.output_cost_per_token,
81
+ cache_read_input_token_cost: value.cache_read_input_token_cost,
82
+ cache_creation_input_token_cost: value.cache_creation_input_token_cost
83
+ };
84
+ }
85
+ }
86
+ await fsPromises.writeFile(pricesPath, JSON.stringify(prices, null, 2));
87
+ pricesCache = prices;
88
+ return prices;
89
+ }
90
+ } catch {
91
+ }
92
+ try {
93
+ const content = await fsPromises.readFile(pricesPath, "utf-8");
94
+ const data = JSON.parse(content);
95
+ pricesCache = data;
96
+ return data;
97
+ } catch {
98
+ }
99
+ try {
100
+ const bundledPath = getBundledPricesPath();
101
+ const content = await fsPromises.readFile(bundledPath, "utf-8");
102
+ const data = JSON.parse(content);
103
+ pricesCache = data;
104
+ return data;
105
+ } catch {
106
+ }
107
+ pricesCache = DEFAULT_PRICES;
108
+ return DEFAULT_PRICES;
109
+ }
110
+ function getModelPrice(model, prices) {
111
+ if (prices[model]) return prices[model];
112
+ const normalized = normalizeModelName(model);
113
+ if (prices[normalized]) return prices[normalized];
114
+ for (const [key, value] of Object.entries(prices)) {
115
+ if (key.includes(normalized) || normalized.includes(normalizeModelName(key))) {
116
+ return value;
117
+ }
118
+ }
119
+ if (model.includes("opus")) return DEFAULT_PRICES["claude-opus-4-5"];
120
+ if (model.includes("sonnet")) return DEFAULT_PRICES["claude-sonnet-4-5"];
121
+ if (model.includes("haiku")) return DEFAULT_PRICES["claude-haiku-4-5"];
122
+ return DEFAULT_PRICES["claude-sonnet-4-5"];
123
+ }
124
+ function calculateCost(usage, price) {
125
+ return usage.inputTokens * price.input_cost_per_token + usage.outputTokens * price.output_cost_per_token + usage.cacheReadTokens * (price.cache_read_input_token_cost || 0) + usage.cacheCreationTokens * (price.cache_creation_input_token_cost || 0);
126
+ }
127
+ function emptyUsage() {
128
+ return {
129
+ inputTokens: 0,
130
+ outputTokens: 0,
131
+ cacheReadTokens: 0,
132
+ cacheCreationTokens: 0,
133
+ cost: 0
134
+ };
135
+ }
136
+ function mergeUsage(a, b) {
137
+ return {
138
+ inputTokens: a.inputTokens + b.inputTokens,
139
+ outputTokens: a.outputTokens + b.outputTokens,
140
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
141
+ cacheCreationTokens: a.cacheCreationTokens + b.cacheCreationTokens,
142
+ cost: a.cost + b.cost
143
+ };
144
+ }
145
+ async function getFileMetaAsync(filePath) {
146
+ try {
147
+ const stat2 = await fsPromises.stat(filePath);
148
+ return {
149
+ mtime: stat2.mtimeMs,
150
+ size: stat2.size
151
+ };
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+ function loadCacheSync() {
157
+ try {
158
+ const cachePath = getCachePath();
159
+ if (!fs.existsSync(cachePath)) return null;
160
+ const data = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
161
+ if (data.version !== CACHE_VERSION) return null;
162
+ return data;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ async function loadCacheAsync() {
168
+ try {
169
+ const cachePath = getCachePath();
170
+ const content = await fsPromises.readFile(cachePath, "utf-8");
171
+ const data = JSON.parse(content);
172
+ if (data.version !== CACHE_VERSION) return null;
173
+ return data;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+ function getUsageStatsFromCache() {
179
+ const cache = loadCacheSync();
180
+ if (!cache) return null;
181
+ const now = /* @__PURE__ */ new Date();
182
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
183
+ const weekStart = new Date(todayStart);
184
+ weekStart.setDate(weekStart.getDate() - weekStart.getDay());
185
+ const stats = {
186
+ today: emptyUsage(),
187
+ week: emptyUsage(),
188
+ total: emptyUsage(),
189
+ dailyHistory: {},
190
+ byModel: {},
191
+ lastUpdated: now.toISOString()
192
+ };
193
+ for (const fileData of Object.values(cache.files)) {
194
+ for (const entry of fileData.stats.entries) {
195
+ const entryTime = new Date(entry.timestamp);
196
+ const dateKey = entry.timestamp.split("T")[0];
197
+ stats.total = mergeUsage(stats.total, entry.usage);
198
+ if (!stats.dailyHistory[dateKey]) {
199
+ stats.dailyHistory[dateKey] = emptyUsage();
200
+ }
201
+ stats.dailyHistory[dateKey] = mergeUsage(stats.dailyHistory[dateKey], entry.usage);
202
+ const normalizedModel = normalizeModelName(entry.model);
203
+ if (!stats.byModel[normalizedModel]) {
204
+ stats.byModel[normalizedModel] = emptyUsage();
205
+ }
206
+ stats.byModel[normalizedModel] = mergeUsage(stats.byModel[normalizedModel], entry.usage);
207
+ if (entryTime >= todayStart) {
208
+ stats.today = mergeUsage(stats.today, entry.usage);
209
+ }
210
+ if (entryTime >= weekStart) {
211
+ stats.week = mergeUsage(stats.week, entry.usage);
212
+ }
213
+ }
214
+ }
215
+ return stats;
216
+ }
217
+ async function saveCacheAsync(cache) {
218
+ try {
219
+ await ensureCcemDir();
220
+ await fsPromises.writeFile(getCachePath(), JSON.stringify(cache, null, 2));
221
+ } catch {
222
+ }
223
+ }
224
+ async function parseJSONLFileAsync(filePath, prices, signal) {
225
+ const entries = [];
226
+ try {
227
+ const fileStream = fs.createReadStream(filePath, { encoding: "utf-8" });
228
+ const rl = readline.createInterface({
229
+ input: fileStream,
230
+ crlfDelay: Infinity
231
+ });
232
+ let lineCount = 0;
233
+ for await (const line of rl) {
234
+ lineCount++;
235
+ if (lineCount % 100 === 0) {
236
+ if (signal?.aborted) {
237
+ rl.close();
238
+ fileStream.destroy();
239
+ throw new Error("Aborted");
240
+ }
241
+ if (lineCount % 1e3 === 0) {
242
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
243
+ }
244
+ }
245
+ if (!line.trim()) continue;
246
+ try {
247
+ const entry = JSON.parse(line);
248
+ if (entry.type !== "assistant" || !entry.message?.usage) continue;
249
+ const model = entry.message.model || "unknown";
250
+ const rawUsage = entry.message.usage;
251
+ const usage = {
252
+ inputTokens: rawUsage.input_tokens || 0,
253
+ outputTokens: rawUsage.output_tokens || 0,
254
+ cacheReadTokens: rawUsage.cache_read_input_tokens || 0,
255
+ cacheCreationTokens: rawUsage.cache_creation_input_tokens || 0
256
+ };
257
+ const price = getModelPrice(model, prices);
258
+ const cost = calculateCost(usage, price);
259
+ entries.push({
260
+ timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
261
+ model,
262
+ usage: { ...usage, cost }
263
+ });
264
+ } catch {
265
+ }
266
+ }
267
+ } catch (err) {
268
+ if (err.message === "Aborted") {
269
+ throw err;
270
+ }
271
+ }
272
+ return { entries };
273
+ }
274
+ async function getAllJSONLFilesAsync() {
275
+ const files = [];
276
+ try {
277
+ const projectsDirExists = await fsPromises.access(CLAUDE_PROJECTS_DIR).then(() => true).catch(() => false);
278
+ if (!projectsDirExists) return files;
279
+ const projects = await fsPromises.readdir(CLAUDE_PROJECTS_DIR);
280
+ for (const project of projects) {
281
+ const projectPath = path.join(CLAUDE_PROJECTS_DIR, project);
282
+ try {
283
+ const stat2 = await fsPromises.stat(projectPath);
284
+ if (stat2.isDirectory()) {
285
+ const projectFiles = await fsPromises.readdir(projectPath);
286
+ for (const file of projectFiles) {
287
+ if (file.endsWith(".jsonl")) {
288
+ files.push(path.join(projectPath, file));
289
+ }
290
+ }
291
+ }
292
+ } catch {
293
+ }
294
+ }
295
+ } catch {
296
+ }
297
+ return files;
298
+ }
299
+ async function getUsageStats(signal) {
300
+ if (signal?.aborted) throw new Error("Aborted");
301
+ const prices = await loadPrices();
302
+ if (signal?.aborted) throw new Error("Aborted");
303
+ const files = await getAllJSONLFilesAsync();
304
+ if (signal?.aborted) throw new Error("Aborted");
305
+ const cache = await loadCacheAsync();
306
+ const newCache = {
307
+ version: CACHE_VERSION,
308
+ files: {},
309
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
310
+ };
311
+ const allEntries = [];
312
+ const CONCURRENCY_LIMIT = 5;
313
+ const chunks = [];
314
+ for (let i = 0; i < files.length; i += CONCURRENCY_LIMIT) {
315
+ chunks.push(files.slice(i, i + CONCURRENCY_LIMIT));
316
+ }
317
+ for (const chunk of chunks) {
318
+ if (signal?.aborted) {
319
+ throw new Error("Aborted");
320
+ }
321
+ const chunkPromises = chunk.map(async (file) => {
322
+ const meta = await getFileMetaAsync(file);
323
+ if (!meta) return null;
324
+ const cachedFile = cache?.files[file];
325
+ const cacheValid = cachedFile && cachedFile.meta.mtime === meta.mtime && cachedFile.meta.size === meta.size;
326
+ let fileStats;
327
+ if (cacheValid) {
328
+ fileStats = cachedFile.stats;
329
+ } else {
330
+ fileStats = await parseJSONLFileAsync(file, prices, signal);
331
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
332
+ }
333
+ return {
334
+ file,
335
+ meta,
336
+ stats: fileStats
337
+ };
338
+ });
339
+ const chunkResults = await Promise.all(chunkPromises);
340
+ for (const result of chunkResults) {
341
+ if (!result) continue;
342
+ newCache.files[result.file] = {
343
+ meta: result.meta,
344
+ stats: result.stats
345
+ };
346
+ allEntries.push(...result.stats.entries);
347
+ }
348
+ }
349
+ if (!signal?.aborted) {
350
+ saveCacheAsync(newCache).catch(() => {
351
+ });
352
+ }
353
+ const now = /* @__PURE__ */ new Date();
354
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
355
+ const weekStart = new Date(todayStart);
356
+ weekStart.setDate(weekStart.getDate() - weekStart.getDay());
357
+ const stats = {
358
+ today: emptyUsage(),
359
+ week: emptyUsage(),
360
+ total: emptyUsage(),
361
+ dailyHistory: {},
362
+ byModel: {},
363
+ lastUpdated: now.toISOString()
364
+ };
365
+ for (const entry of allEntries) {
366
+ const entryTime = new Date(entry.timestamp);
367
+ const dateKey = entry.timestamp.split("T")[0];
368
+ stats.total = mergeUsage(stats.total, entry.usage);
369
+ if (!stats.dailyHistory[dateKey]) {
370
+ stats.dailyHistory[dateKey] = emptyUsage();
371
+ }
372
+ stats.dailyHistory[dateKey] = mergeUsage(stats.dailyHistory[dateKey], entry.usage);
373
+ const normalizedModel = normalizeModelName(entry.model);
374
+ if (!stats.byModel[normalizedModel]) {
375
+ stats.byModel[normalizedModel] = emptyUsage();
376
+ }
377
+ stats.byModel[normalizedModel] = mergeUsage(stats.byModel[normalizedModel], entry.usage);
378
+ if (entryTime >= todayStart) {
379
+ stats.today = mergeUsage(stats.today, entry.usage);
380
+ }
381
+ if (entryTime >= weekStart) {
382
+ stats.week = mergeUsage(stats.week, entry.usage);
383
+ }
384
+ }
385
+ return stats;
386
+ }
387
+ function formatTokens(n) {
388
+ if (n >= 1e6) {
389
+ return (n / 1e6).toFixed(1) + "M";
390
+ }
391
+ if (n >= 1e3) {
392
+ return (n / 1e3).toFixed(1) + "K";
393
+ }
394
+ return n.toString();
395
+ }
396
+ function formatCost(n) {
397
+ if (n >= 1) {
398
+ return "$" + n.toFixed(2);
399
+ }
400
+ if (n >= 0.01) {
401
+ return "$" + n.toFixed(2);
402
+ }
403
+ return "$" + n.toFixed(4);
404
+ }
405
+ function getTotalTokens(usage) {
406
+ return usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheCreationTokens;
407
+ }
408
+
409
+ // src/logo-generated.ts
410
+ var LOGO_MINIMAL = {
411
+ width: 8,
412
+ height: 5,
413
+ ansi: ` \x1B[38;5;1m \x1B[38;5;103m\u2582\x1B[38;5;109m\u2584\x1B[7m\x1B[38;5;60m\u2583\x1B[38;5;61m\u2598\x1B[0m\x1B[38;5;60;48;5;67m\u2583\x1B[38;5;23m\u2596\x1B[38;5;67;48;5;239m\u2586\x1B[0m\x1B[38;5;60m\u259E \x1B[7m\x1B[38;5;24m\u2586\x1B[38;5;25m\u2585\x1B[38;5;24m\u2583\u2582\x1B[0m\x1B[38;5;1m \x1B[0m
414
+ \x1B[38;5;1m \x1B[38;5;103m\u2584\x1B[7m\u2582\x1B[38;5;110m\u2585\x1B[0m\x1B[38;5;60m\u2597\x1B[38;5;67m\u259E\x1B[7m\x1B[38;5;240m\u2586\x1B[38;5;25m\u258D\x1B[0m\x1B[38;5;23m\u258E\x1B[38;5;67m\u258C\x1B[7m\u2596\x1B[0m\x1B[38;5;67m\u2596\x1B[7m\x1B[38;5;60m\u2584\x1B[0m\x1B[38;5;1m \x1B[0m
415
+ \x1B[38;5;1m \x1B[38;5;60m\u2597\x1B[7m\u2584\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;25m\u258F\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;67m\u2583\x1B[0m\x1B[38;5;1m \x1B[0m
416
+ \x1B[38;5;1m \x1B[7m\x1B[38;5;25m\u2585\x1B[0m\x1B[38;5;1m \x1B[0m
417
+ \x1B[?25h`
418
+ };
419
+ var LOGO_COMPACT = {
420
+ width: 12,
421
+ height: 6,
422
+ ansi: ` \x1B[38;5;1m \x1B[38;5;239m\u2581\x1B[38;5;60m\u2583 \x1B[38;5;103m\u2581\x1B[38;5;110m\u2584 \x1B[38;5;61m\u2586\x1B[38;5;67m\u2586\u2586\x1B[38;5;68m\u2586\x1B[38;5;67m\u2582\x1B[38;5;66m\u2584 \x1B[38;5;23m\u259D\x1B[7m\x1B[38;5;24m\u2583\x1B[0m\x1B[38;5;24m\u2586\x1B[38;5;25m\u2585\u2584 \x1B[0m
423
+ \x1B[38;5;1m \x1B[38;5;66m\u2581\x1B[38;5;110m\u2584\u2586\x1B[7m\u2584\x1B[0m\x1B[38;5;239m\u2597\x1B[38;5;68m\u259E\x1B[7m\x1B[38;5;67m\u2585\x1B[38;5;25m\u258D\x1B[0m\x1B[38;5;25m\u258E\x1B[38;5;109m\u258C\x1B[38;5;239;48;5;68m\u2596\x1B[0m\x1B[38;5;67m\u2596\x1B[7m\u2585\x1B[0m\x1B[38;5;66m\u2585 \x1B[0m
424
+ \x1B[38;5;1m \x1B[7m\x1B[38;5;110m\u2583\x1B[38;5;67m\u2585\x1B[0m\x1B[38;5;1m \x1B[38;5;68m\u2597\x1B[7m\x1B[38;5;61m\u2597\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;25m\u258F\x1B[0m\x1B[38;5;23m\u258E\x1B[7m\x1B[38;5;103m\u258D\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;68m\u2596\x1B[0m\x1B[38;5;68m\u2596 \x1B[7m\x1B[38;5;67m\u2585\x1B[0m\x1B[38;5;60m\u2584 \x1B[0m
425
+ \x1B[38;5;1m \x1B[38;5;60m\u259D\x1B[38;5;67m\u2598 \x1B[7m\x1B[38;5;24m\u258A\x1B[0m\x1B[38;5;24;48;5;25m\u2595\x1B[0m\x1B[38;5;24m \x1B[7m\x1B[38;5;68m\u2584\x1B[0m\x1B[38;5;1m \x1B[0m
426
+ \x1B[38;5;1m \x1B[7m\x1B[38;5;24m\u2585\x1B[0m\x1B[38;5;1m \x1B[0m
427
+ \x1B[?25h`
428
+ };
429
+ var LOGO_FULL = {
430
+ width: 19,
431
+ height: 9,
432
+ ansi: ` \x1B[38;5;1m \x1B[38;5;109m\u2597\x1B[38;5;67m\u2581\x1B[38;5;23m\u2584\u2581 \x1B[7m\x1B[38;5;68m\u2596\u259D\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;110m\u258E\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;25m\u258B\x1B[0m\x1B[38;5;237;48;5;25m\u2595\x1B[0m\x1B[38;5;237m \x1B[7m\x1B[38;5;110m\u2598\u2583\x1B[0m\x1B[38;5;60m\u258C\x1B[38;5;24m\u2598 \x1B[0m
433
+ \x1B[38;5;1m \x1B[7m\x1B[38;5;110m\u2585\x1B[38;5;67m\u259E\x1B[0m\x1B[38;5;110m\u2583\x1B[7m\x1B[38;5;23m\u259E\x1B[0m\x1B[38;5;24m\u2583 \x1B[7m\x1B[38;5;68m\u2596\u259D\x1B[38;5;103m\u258B\x1B[0m\x1B[38;5;109m\u258D\x1B[7m\x1B[38;5;25m\u258C\x1B[0m\x1B[38;5;25m\u258B \x1B[38;5;103m\u2584\x1B[7m\x1B[38;5;110m\u2597\x1B[38;5;61m\u259E\x1B[38;5;24m\u2583\x1B[0m\x1B[38;5;1m \x1B[38;5;60m\u2582\x1B[38;5;67m\u2584\x1B[38;5;60m\u2585\x1B[7m\u2584\x1B[0m\x1B[38;5;1m \x1B[0m
434
+ \x1B[38;5;1m \x1B[38;5;24m\u2597\x1B[38;5;25m\u2585\u2584\u2583\x1B[38;5;61m\u2581\x1B[7m\x1B[38;5;60m\u2586\x1B[38;5;103m\u2584\x1B[0m\x1B[38;5;67m\u2585\x1B[7m\x1B[38;5;60m\u2597\x1B[0m\x1B[38;5;24m\u2585\x1B[48;5;68m\u2596\x1B[0m\x1B[7m\x1B[38;5;68m\u259D\x1B[0m\x1B[38;5;103m\u258A\x1B[7m\x1B[38;5;25m\u258E\x1B[0m\x1B[38;5;68;48;5;24m\u2584\x1B[0m\x1B[38;5;110m\u259E\x1B[7m\x1B[38;5;67m\u259E\x1B[0m\x1B[38;5;25m\u259E\x1B[38;5;67m\u2581\x1B[38;5;68m\u2583\x1B[38;5;60m\u2586\x1B[7m\x1B[38;5;67m\u2583\x1B[38;5;68m\u2585\x1B[0m\x1B[38;5;61m\u2594 \x1B[0m
435
+ \x1B[38;5;1m \x1B[38;5;238m\u2594\x1B[38;5;23m\u2594\x1B[38;5;24m\u2594 \x1B[38;5;95m\u2582\x1B[38;5;131m\u2583\x1B[38;5;66m\u2594 \x1B[38;5;131m\u2582\u2583\x1B[38;5;60m\u2594\x1B[38;5;95m\u2582\x1B[38;5;131m\u2583\x1B[38;5;94m\u2596\x1B[38;5;95m\u2597 \x1B[38;5;60m\u2594\x1B[38;5;95m\u2583 \x1B[0m
436
+ \x1B[38;5;1m \x1B[7m\x1B[38;5;95m\u258B\x1B[0m\x1B[38;5;131m\u258C \x1B[38;5;95m\u2581\x1B[7m\x1B[38;5;131m\u258C\x1B[0m\x1B[38;5;131m\u258D \x1B[38;5;95m\u2581\x1B[7m\x1B[38;5;131m\u258D\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;131m\u258D\x1B[38;5;95m\u2596\x1B[0m\x1B[38;5;131m\u2584\x1B[7m\u2596\x1B[0m\x1B[38;5;131m\u258E \x1B[0m
437
+ \x1B[38;5;1m \x1B[7m\x1B[38;5;95m\u2584\x1B[38;5;94m\u2583\x1B[38;5;95m\u2585\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;95m\u2584\x1B[38;5;94m\u2584\x1B[0m\x1B[38;5;95m\u2598\x1B[38;5;167m\u259D\x1B[7m\x1B[38;5;95m\u2584\u2585\x1B[0m\x1B[38;5;131m\u259D \x1B[38;5;95m\u2594\x1B[38;5;238m\u259D\x1B[38;5;131m\u2598 \x1B[0m
438
+ \x1B[38;5;1m \x1B[38;5;60m\u2583\x1B[38;5;240m\u2596 \x1B[38;5;109m\u2582\x1B[38;5;110m\u2584\x1B[7m\x1B[38;5;67m\u2583\x1B[38;5;60m\u259E\x1B[0m\x1B[38;5;240;48;5;68m\u2594\x1B[0m\x1B[7m\x1B[38;5;67m\u2596\x1B[38;5;61m\u2597\x1B[0m\x1B[38;5;67;48;5;110m\u2594\x1B[0m\x1B[38;5;68m\u2585\x1B[7m\x1B[38;5;60m\u2597\x1B[0m\x1B[38;5;66m\u2585\x1B[38;5;60m\u2581\x1B[7m\x1B[38;5;25m\u2586\u2584\u2583\u2582\u2594\x1B[0m\x1B[38;5;25m\u2585\x1B[38;5;24m\u2596 \x1B[0m
439
+ \x1B[38;5;1m \x1B[38;5;60m\u2594\x1B[38;5;67m\u2582\x1B[38;5;110m\u2584\x1B[7m\u2594\u2584\x1B[0m\x1B[38;5;1m \x1B[38;5;61m\u2584\x1B[7m\x1B[38;5;67m\u2597\x1B[38;5;60m\u2597\x1B[38;5;25m\u258E\x1B[0m\x1B[38;5;25m\u258D\x1B[7m\x1B[38;5;110m\u259E\x1B[38;5;67m\u2596\x1B[0m\x1B[38;5;60;48;5;68m\u259D\x1B[0m\x1B[38;5;60m\u2598\x1B[7m\x1B[38;5;109m\u2585\x1B[0m\x1B[38;5;67m\u2585 \x1B[38;5;24m\u2594 \x1B[0m
440
+ \x1B[38;5;1m \x1B[38;5;66m\u2597\x1B[38;5;153m\u2586\x1B[7m\x1B[38;5;110m\u2582\u2585\x1B[0m\x1B[38;5;1m \x1B[7m\x1B[38;5;68m\u2598\x1B[38;5;61m\u2584\x1B[0m\x1B[38;5;1m \x1B[38;5;24;48;5;25m\u258F\x1B[0m\x1B[38;5;24m\u258E\x1B[7m\x1B[38;5;110m\u258B\x1B[0m\x1B[38;5;103m\u258D\x1B[7m\x1B[38;5;61m\u2596\x1B[0m\x1B[38;5;239;48;5;68m\u259D\x1B[0m\x1B[38;5;60m\u2596 \x1B[7m\x1B[38;5;109m\u2585\x1B[38;5;66m\u259D\x1B[0m\x1B[38;5;1m \x1B[0m
441
+ `
442
+ };
443
+
444
+ // src/ui.ts
445
+ var theme = {
446
+ primary: chalk.hex("#89B4FA"),
447
+ // 亮蓝
448
+ accent: chalk.hex("#CBA6F7"),
449
+ // 亮紫
450
+ success: chalk.hex("#A6E3A1"),
451
+ // 亮绿
452
+ warning: chalk.hex("#F9E2AF"),
453
+ // 亮黄
454
+ danger: chalk.hex("#F38BA8"),
455
+ // 亮红
456
+ text: chalk.white,
457
+ muted: chalk.hex("#6C7086"),
458
+ // 中灰
459
+ dim: chalk.hex("#45475A")
460
+ // 暗灰
461
+ };
462
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
463
+ var spinnerIndex = 0;
464
+ var spinnerInterval = null;
465
+ var getSpinnerFrame = () => {
466
+ return theme.primary(SPINNER_FRAMES[spinnerIndex]);
467
+ };
468
+ var startSpinner = (onFrame) => {
469
+ if (spinnerInterval) return;
470
+ spinnerInterval = setInterval(() => {
471
+ spinnerIndex = (spinnerIndex + 1) % SPINNER_FRAMES.length;
472
+ onFrame();
473
+ }, 80);
474
+ };
475
+ var stopSpinner = () => {
476
+ if (spinnerInterval) {
477
+ clearInterval(spinnerInterval);
478
+ spinnerInterval = null;
479
+ }
480
+ };
481
+ var getTerminalWidth = () => {
482
+ return process.stdout.columns || 80;
483
+ };
484
+ var selectLogo = (termWidth) => {
485
+ if (termWidth < 45) return LOGO_MINIMAL;
486
+ if (termWidth < 60) return LOGO_COMPACT;
487
+ return LOGO_FULL;
488
+ };
489
+ var renderLogoWithEnvPanel = (envName, env, defaultMode) => {
490
+ const termWidth = getTerminalWidth();
491
+ const isNarrow = termWidth < 60;
492
+ const isVeryNarrow = termWidth < 45;
493
+ const logo = selectLogo(termWidth);
494
+ const logoLines = logo.ansi.split("\n").filter((l) => l.length > 0);
495
+ const title = theme.primary("CCEM") + " " + theme.muted("Claude Code Env Manager");
496
+ const titleShort = theme.primary("CCEM");
497
+ const envLabel = theme.muted("Env: ") + theme.primary(envName);
498
+ const baseUrl = env.ANTHROPIC_BASE_URL || "-";
499
+ const model = env.ANTHROPIC_MODEL || "-";
500
+ const fastModel = env.ANTHROPIC_SMALL_FAST_MODEL || "-";
501
+ const apiKey = env.ANTHROPIC_API_KEY ? env.ANTHROPIC_API_KEY.slice(0, 2) + "\u2022\u2022\u2022\u2022" + env.ANTHROPIC_API_KEY.slice(-4) : "-";
502
+ const truncate = (s, max) => s.length > max ? s.slice(0, max - 3) + "..." : s;
503
+ const maskUrl = (url, max) => {
504
+ if (url.length <= max) return url;
505
+ try {
506
+ const parsed = new URL(url);
507
+ const protocol = parsed.protocol + "//";
508
+ const host = parsed.host;
509
+ const path6 = parsed.pathname + parsed.search;
510
+ const hostStart = host.slice(0, 8);
511
+ const hostEnd = host.slice(-4);
512
+ const pathPart = path6.length > 10 ? path6.slice(0, 7) + "..." : path6;
513
+ return `${protocol}${hostStart}...${hostEnd}${pathPart}`;
514
+ } catch {
515
+ return truncate(url, max);
516
+ }
517
+ };
518
+ const labelWidth = 7;
519
+ let envLines;
520
+ if (isNarrow) {
521
+ envLines = [
522
+ envLabel,
523
+ theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 25)),
524
+ theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
525
+ ];
526
+ } else {
527
+ envLines = [
528
+ envLabel + (defaultMode && PERMISSION_PRESETS[defaultMode] ? " " + theme.accent(`[${PERMISSION_PRESETS[defaultMode].name}]`) : ""),
529
+ theme.muted("URL:".padEnd(labelWidth)) + theme.dim(maskUrl(baseUrl, 40)),
530
+ theme.muted("Model:".padEnd(labelWidth)) + theme.dim(truncate(model, 15)) + " " + theme.muted("Fast:".padEnd(labelWidth)) + theme.dim(truncate(fastModel, 15)),
531
+ theme.muted("Key:".padEnd(labelWidth)) + theme.dim(apiKey)
532
+ ];
533
+ }
534
+ const lines = [];
535
+ if (isVeryNarrow) {
536
+ logoLines.forEach((line) => lines.push(line));
537
+ lines.push("");
538
+ lines.push(titleShort);
539
+ lines.push("");
540
+ envLines.forEach((line) => lines.push(line));
541
+ } else {
542
+ const gap = isNarrow ? " " : " ";
543
+ lines.push("");
544
+ const maxLines = Math.max(logoLines.length, envLines.length + 3);
545
+ for (let i = 0; i < maxLines; i++) {
546
+ const logoLine = logoLines[i] || "";
547
+ if (i === 0) {
548
+ const visibleWidth2 = logoLine.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "").length;
549
+ const padding2 = " ".repeat(Math.max(0, logo.width - visibleWidth2));
550
+ lines.push(logoLine + padding2);
551
+ continue;
552
+ }
553
+ if (i === 1) {
554
+ const visibleWidth2 = logoLine.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "").length;
555
+ const padding2 = " ".repeat(Math.max(0, logo.width - visibleWidth2));
556
+ lines.push(logoLine + padding2 + gap + (isNarrow ? titleShort : title));
557
+ continue;
558
+ }
559
+ if (i === 2) {
560
+ const visibleWidth2 = logoLine.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "").length;
561
+ const padding2 = " ".repeat(Math.max(0, logo.width - visibleWidth2));
562
+ lines.push(logoLine + padding2);
563
+ continue;
564
+ }
565
+ const envLine = envLines[i - 3] || "";
566
+ const visibleWidth = logoLine.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "").length;
567
+ const padding = " ".repeat(Math.max(0, logo.width - visibleWidth));
568
+ lines.push(logoLine + padding + gap + envLine);
569
+ }
570
+ }
571
+ return lines.join("\n");
572
+ };
573
+ var renderUsageLine = (stats, loading) => {
574
+ if (loading) {
575
+ return theme.muted(" Usage: ") + getSpinnerFrame() + theme.dim(" Loading...");
576
+ }
577
+ if (!stats) {
578
+ return theme.muted(" Usage: ") + theme.dim("No data");
579
+ }
580
+ const termWidth = getTerminalWidth();
581
+ const isNarrow = termWidth < 70;
582
+ const todayTokens = getTotalTokens(stats.today);
583
+ const weekTokens = getTotalTokens(stats.week);
584
+ const totalTokens = getTotalTokens(stats.total);
585
+ if (isNarrow) {
586
+ return theme.muted(" Usage: ") + theme.text("Today ") + theme.primary(formatTokens(todayTokens)) + theme.dim(" | ") + theme.text("Week ") + theme.primary(formatTokens(weekTokens)) + theme.dim(" | ") + theme.text("Total ") + theme.primary(formatTokens(totalTokens));
587
+ }
588
+ return theme.muted(" Usage: ") + theme.text("Today ") + theme.primary(formatTokens(todayTokens).padStart(6)) + theme.dim(` (${formatCost(stats.today.cost)})`) + theme.dim(" | ") + theme.text("Week ") + theme.primary(formatTokens(weekTokens).padStart(6)) + theme.dim(` (${formatCost(stats.week.cost)})`) + theme.dim(" | ") + theme.text("Total ") + theme.primary(formatTokens(totalTokens).padStart(6)) + theme.dim(` (${formatCost(stats.total.cost)})`);
589
+ };
590
+ var renderCompactHeader = () => {
591
+ const width = process.stdout.columns || 80;
592
+ return theme.dim("\u2500".repeat(width));
593
+ };
594
+ var getMainMenuChoices = (defaultMode) => {
595
+ let startLabel = "Start Claude Code";
596
+ if (defaultMode && PERMISSION_PRESETS[defaultMode]) {
597
+ startLabel = `Start Claude Code ${theme.muted(`(${PERMISSION_PRESETS[defaultMode].name})`)}`;
598
+ }
599
+ return [
600
+ { name: theme.success(startLabel), value: "start", short: "Start" },
601
+ { name: theme.primary("Switch Environment"), value: "switch", short: "Switch" },
602
+ { name: theme.primary("Permission Mode"), value: "perm", short: "Permission" },
603
+ { name: theme.accent("View Usage"), value: "usage", short: "Usage" },
604
+ { name: theme.text("Set Default Mode"), value: "setDefault", short: "Default" },
605
+ { name: theme.muted("Exit"), value: "exit", short: "Exit" }
606
+ ];
607
+ };
608
+ var modeColors = {
609
+ yolo: theme.danger,
610
+ dev: theme.success,
611
+ readonly: theme.primary,
612
+ safe: theme.warning,
613
+ ci: theme.accent,
614
+ audit: theme.primary
615
+ };
616
+ var getPermModeChoices = (currentMode, showCurrent = false) => {
617
+ const modes = ["yolo", "dev", "readonly", "safe", "ci", "audit"];
618
+ const choices = modes.map((mode) => {
619
+ const preset = PERMISSION_PRESETS[mode];
620
+ const colorFn = modeColors[mode];
621
+ const isCurrent = showCurrent && mode === currentMode;
622
+ const tag = isCurrent ? theme.success(" *") : "";
623
+ return {
624
+ name: colorFn(preset.name.padEnd(12)) + theme.muted(preset.description) + tag,
625
+ value: mode,
626
+ short: preset.name
627
+ };
628
+ });
629
+ if (showCurrent) {
630
+ choices.push({
631
+ name: theme.muted("Clear default"),
632
+ value: "clear",
633
+ short: "Clear"
634
+ });
635
+ }
636
+ choices.push({
637
+ name: theme.muted("Back"),
638
+ value: "back",
639
+ short: "Back"
640
+ });
641
+ return choices;
642
+ };
643
+ var msg = {
644
+ success: (text) => console.log(theme.success("\u2713 ") + theme.text(text)),
645
+ error: (text) => console.log(theme.danger("\u2717 ") + theme.text(text)),
646
+ warning: (text) => console.log(theme.warning("! ") + theme.text(text)),
647
+ info: (text) => console.log(theme.primary("\u203A ") + theme.text(text))
648
+ };
649
+ var renderStarting = () => {
650
+ return theme.muted("Starting Claude Code...");
651
+ };
652
+ var renderCalendarHeatmap = (stats, months = 6) => {
653
+ const lines = [];
654
+ const now = /* @__PURE__ */ new Date();
655
+ const startDate = new Date(now);
656
+ startDate.setMonth(startDate.getMonth() - months);
657
+ const day = startDate.getDay();
658
+ const diff = startDate.getDate() - day + (day === 0 ? -6 : 1);
659
+ startDate.setDate(diff);
660
+ startDate.setHours(0, 0, 0, 0);
661
+ const dates = [];
662
+ const d = new Date(startDate);
663
+ while (d <= now || d.getDay() !== 1) {
664
+ dates.push(d.toISOString().split("T")[0]);
665
+ d.setDate(d.getDate() + 1);
666
+ if (d > now && d.getDay() === 1) break;
667
+ }
668
+ let maxTokens = 0;
669
+ for (const date of dates) {
670
+ const usage = stats.dailyHistory[date];
671
+ if (usage) {
672
+ const tokens = getTotalTokens(usage);
673
+ if (tokens > maxTokens) maxTokens = tokens;
674
+ }
675
+ }
676
+ let header = " ";
677
+ let currentMonth = -1;
678
+ const weeks = Math.ceil(dates.length / 7);
679
+ for (let w = 0; w < weeks; w++) {
680
+ const dateIndex = w * 7;
681
+ if (dateIndex < dates.length) {
682
+ const date = new Date(dates[dateIndex]);
683
+ const month = date.getMonth();
684
+ if (month !== currentMonth && w < weeks - 1) {
685
+ const monthName = date.toLocaleString("default", { month: "short" });
686
+ header += theme.muted(monthName);
687
+ currentMonth = month;
688
+ }
689
+ }
690
+ }
691
+ header = " ";
692
+ let lastMonth = -1;
693
+ for (let w = 0; w < weeks; w++) {
694
+ const dateIndex = w * 7;
695
+ if (dateIndex >= dates.length) break;
696
+ const date = new Date(dates[dateIndex]);
697
+ const month = date.getMonth();
698
+ if (month !== lastMonth) {
699
+ const monthName = date.toLocaleString("default", { month: "short" });
700
+ header += theme.muted(monthName.padEnd(4));
701
+ w++;
702
+ lastMonth = month;
703
+ } else {
704
+ header += " ";
705
+ }
706
+ }
707
+ lines.push(header);
708
+ const dayLabels = ["Mon", "", "Wed", "", "Fri", "", "Sun"];
709
+ const levels = [" ", "\u2591", "\u2592", "\u2593", "\u2588"];
710
+ for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
711
+ let row = theme.muted((dayLabels[dayOfWeek] || "").padEnd(4) + " ");
712
+ for (let w = 0; w < weeks; w++) {
713
+ const dateIndex = w * 7 + dayOfWeek;
714
+ if (dateIndex < dates.length) {
715
+ const dateKey = dates[dateIndex];
716
+ if (new Date(dateKey) > now) {
717
+ row += " ";
718
+ } else {
719
+ const usage = stats.dailyHistory[dateKey];
720
+ const tokens = usage ? getTotalTokens(usage) : 0;
721
+ let level = 0;
722
+ if (tokens > 0) {
723
+ if (maxTokens === 0) level = 0;
724
+ else {
725
+ level = Math.ceil(tokens / maxTokens * 4);
726
+ }
727
+ }
728
+ row += (level === 0 ? theme.dim("\xB7") : theme.primary(levels[level])) + " ";
729
+ }
730
+ }
731
+ }
732
+ lines.push(row);
733
+ }
734
+ lines.push("");
735
+ lines.push(" " + theme.dim("Less ") + theme.dim("\xB7") + " " + theme.primary(levels[1]) + " " + theme.primary(levels[2]) + " " + theme.primary(levels[3]) + " " + theme.primary(levels[4]) + " " + theme.dim(" More"));
736
+ return lines.join("\n");
737
+ };
738
+ var renderUsageDetail = (stats) => {
739
+ const lines = [];
740
+ lines.push("");
741
+ lines.push(theme.primary(" Token Usage Statistics"));
742
+ lines.push(theme.dim("\u2500".repeat(60)));
743
+ lines.push("");
744
+ lines.push(renderCalendarHeatmap(stats));
745
+ lines.push("");
746
+ lines.push(theme.dim("\u2500".repeat(60)));
747
+ const periodTable = new Table({
748
+ head: [
749
+ theme.muted("Period"),
750
+ theme.muted("Input"),
751
+ theme.muted("Output"),
752
+ theme.muted("Cache Read"),
753
+ theme.muted("Cost")
754
+ ],
755
+ style: { head: [], border: [] },
756
+ chars: {
757
+ "top": "",
758
+ "top-mid": "",
759
+ "top-left": "",
760
+ "top-right": "",
761
+ "bottom": "",
762
+ "bottom-mid": "",
763
+ "bottom-left": "",
764
+ "bottom-right": "",
765
+ "left": " ",
766
+ "left-mid": "",
767
+ "mid": "",
768
+ "mid-mid": "",
769
+ "right": "",
770
+ "right-mid": "",
771
+ "middle": " "
772
+ }
773
+ });
774
+ const formatRow = (label, usage) => [
775
+ theme.text(label),
776
+ theme.primary(formatTokens(usage.inputTokens)),
777
+ theme.primary(formatTokens(usage.outputTokens)),
778
+ theme.primary(formatTokens(usage.cacheReadTokens)),
779
+ theme.success(formatCost(usage.cost))
780
+ ];
781
+ periodTable.push(formatRow("Today", stats.today));
782
+ periodTable.push(formatRow("This Week", stats.week));
783
+ periodTable.push(formatRow("All Time", stats.total));
784
+ lines.push(periodTable.toString());
785
+ const modelEntries = Object.entries(stats.byModel).sort((a, b) => b[1].cost - a[1].cost);
786
+ if (modelEntries.length > 0) {
787
+ lines.push("");
788
+ lines.push(theme.dim("\u2500".repeat(60)));
789
+ lines.push(theme.muted(" By Model"));
790
+ lines.push("");
791
+ const modelTable = new Table({
792
+ head: [
793
+ theme.muted("Model"),
794
+ theme.muted("Tokens"),
795
+ theme.muted("Cost")
796
+ ],
797
+ style: { head: [], border: [] },
798
+ chars: {
799
+ "top": "",
800
+ "top-mid": "",
801
+ "top-left": "",
802
+ "top-right": "",
803
+ "bottom": "",
804
+ "bottom-mid": "",
805
+ "bottom-left": "",
806
+ "bottom-right": "",
807
+ "left": " ",
808
+ "left-mid": "",
809
+ "mid": "",
810
+ "mid-mid": "",
811
+ "right": "",
812
+ "right-mid": "",
813
+ "middle": " "
814
+ }
815
+ });
816
+ for (const [model, usage] of modelEntries) {
817
+ const totalTokens = getTotalTokens(usage);
818
+ modelTable.push([
819
+ theme.text(model),
820
+ theme.primary(formatTokens(totalTokens)),
821
+ theme.success(formatCost(usage.cost))
822
+ ]);
823
+ }
824
+ lines.push(modelTable.toString());
825
+ }
826
+ lines.push("");
827
+ lines.push(theme.dim("\u2500".repeat(60)));
828
+ lines.push(theme.muted(` Last updated: ${new Date(stats.lastUpdated).toLocaleString()}`));
829
+ return lines.join("\n");
830
+ };
831
+ var renderFooterHints = (hints) => {
832
+ const termWidth = process.stdout.columns || 80;
833
+ const full = hints.map((h) => theme.primary(h.key) + theme.dim(h.label)).join(" ");
834
+ const medium = hints.map((h) => theme.primary(h.key) + theme.dim(h.short || h.label)).join(" ");
835
+ const minimal = hints.map((h) => theme.primary(h.key)).join(" ");
836
+ const stripAnsi = (s) => s.replace(/\x1B\[[0-9;]*m/g, "");
837
+ if (stripAnsi(full).length <= termWidth) return full;
838
+ if (stripAnsi(medium).length <= termWidth) return medium;
839
+ return minimal;
840
+ };
841
+ var selectEnvWithKeys = (registries, current) => {
842
+ return new Promise((resolve2) => {
843
+ const envNames = Object.keys(registries);
844
+ let selectedIndex = envNames.indexOf(current);
845
+ if (selectedIndex === -1) selectedIndex = 0;
846
+ const stdin = process.stdin;
847
+ const wasRaw = stdin.isRaw;
848
+ stdin.setRawMode(true);
849
+ stdin.resume();
850
+ const footerHints = [
851
+ { key: "[\u2191\u2193]", label: " navigate", short: " nav" },
852
+ { key: "[Enter]", label: " select", short: " sel" },
853
+ { key: "[e]", label: "dit", short: "" },
854
+ { key: "[r]", label: "ename", short: "en" },
855
+ { key: "[c]", label: "opy", short: "py" },
856
+ { key: "[d]", label: "elete", short: "el" }
857
+ ];
858
+ const totalLines = envNames.length + 1;
859
+ const renderItem = (index) => {
860
+ const name = envNames[index];
861
+ const isCurrent = name === current;
862
+ const isSelected = index === selectedIndex;
863
+ const prefix = isSelected ? theme.primary("\u276F ") : " ";
864
+ const tag = isCurrent ? theme.success(" *") : "";
865
+ const nameText = isSelected ? theme.primary(name) : isCurrent ? theme.primary(name) : theme.text(name);
866
+ return prefix + nameText + tag;
867
+ };
868
+ const updateLine = (lineIndex, content) => {
869
+ const moveUp = totalLines - lineIndex;
870
+ process.stdout.write(`\x1B[${moveUp}A`);
871
+ process.stdout.write("\x1B[2K");
872
+ process.stdout.write("\x1B[G");
873
+ process.stdout.write(content);
874
+ process.stdout.write(`\x1B[${moveUp}B`);
875
+ process.stdout.write("\x1B[G");
876
+ };
877
+ const initialRender = () => {
878
+ process.stdout.write("\x1B[?25l");
879
+ envNames.forEach((_, i) => {
880
+ console.log(renderItem(i));
881
+ });
882
+ console.log(renderFooterHints(footerHints));
883
+ };
884
+ const handleSelectionChange = (oldIndex, newIndex) => {
885
+ if (oldIndex === newIndex) return;
886
+ selectedIndex = newIndex;
887
+ updateLine(oldIndex, renderItem(oldIndex));
888
+ updateLine(newIndex, renderItem(newIndex));
889
+ };
890
+ initialRender();
891
+ const cleanup = () => {
892
+ stdin.setRawMode(wasRaw ?? false);
893
+ stdin.removeListener("data", onKeypress);
894
+ process.stdout.write("\x1B[?25h");
895
+ };
896
+ const onKeypress = (key) => {
897
+ const char = key.toString();
898
+ if (char === "") {
899
+ cleanup();
900
+ process.exit(0);
901
+ }
902
+ if (char === "\x1B" && key.length === 1) {
903
+ cleanup();
904
+ resolve2({ action: "cancel" });
905
+ return;
906
+ }
907
+ if (char === "\x1B[A" || char === "k") {
908
+ const oldIndex = selectedIndex;
909
+ const newIndex = Math.max(0, selectedIndex - 1);
910
+ handleSelectionChange(oldIndex, newIndex);
911
+ return;
912
+ }
913
+ if (char === "\x1B[B" || char === "j") {
914
+ const oldIndex = selectedIndex;
915
+ const newIndex = Math.min(envNames.length - 1, selectedIndex + 1);
916
+ handleSelectionChange(oldIndex, newIndex);
917
+ return;
918
+ }
919
+ if (char === "\r" || char === "\n") {
920
+ cleanup();
921
+ resolve2({ action: "select", name: envNames[selectedIndex] });
922
+ return;
923
+ }
924
+ if (char === "e" || char === "E") {
925
+ cleanup();
926
+ resolve2({ action: "edit", name: envNames[selectedIndex] });
927
+ return;
928
+ }
929
+ if (char === "r" || char === "R") {
930
+ cleanup();
931
+ resolve2({ action: "rename", name: envNames[selectedIndex] });
932
+ return;
933
+ }
934
+ if (char === "c" || char === "C") {
935
+ cleanup();
936
+ resolve2({ action: "copy", name: envNames[selectedIndex] });
937
+ return;
938
+ }
939
+ if (char === "d" || char === "D") {
940
+ cleanup();
941
+ resolve2({ action: "delete", name: envNames[selectedIndex] });
942
+ return;
943
+ }
944
+ };
945
+ stdin.on("data", onKeypress);
946
+ });
947
+ };
948
+
949
+ // src/permissions.ts
950
+ import fs4 from "fs";
951
+ import chalk3 from "chalk";
952
+ import Table2 from "cli-table3";
953
+ import { PERMISSION_PRESETS as PERMISSION_PRESETS3, getPermissionModeNames } from "@ccem/core";
954
+
955
+ // src/utils.ts
956
+ import crypto from "crypto";
957
+ import fs2 from "fs";
958
+ import path2 from "path";
959
+ var SECRET_KEY = crypto.scryptSync("claude-code-env-manager-secret", "salt", 32);
960
+ var findProjectRoot = () => {
961
+ let currentDir = process.cwd();
962
+ const root = path2.parse(currentDir).root;
963
+ while (currentDir !== root) {
964
+ if (fs2.existsSync(path2.join(currentDir, ".git")) || fs2.existsSync(path2.join(currentDir, "package.json"))) {
965
+ return currentDir;
966
+ }
967
+ currentDir = path2.dirname(currentDir);
968
+ }
969
+ return process.cwd();
970
+ };
971
+ var getSettingsPath = (useLocal = true) => {
972
+ const projectRoot = findProjectRoot();
973
+ const claudeDir = path2.join(projectRoot, ".claude");
974
+ const filename = useLocal ? "settings.local.json" : "settings.json";
975
+ return path2.join(claudeDir, filename);
976
+ };
977
+ var ensureClaudeDir = () => {
978
+ const projectRoot = findProjectRoot();
979
+ const claudeDir = path2.join(projectRoot, ".claude");
980
+ if (!fs2.existsSync(claudeDir)) {
981
+ fs2.mkdirSync(claudeDir, { recursive: true });
982
+ }
983
+ return claudeDir;
984
+ };
985
+ var getHomeDir = () => {
986
+ return process.env.HOME || process.env.USERPROFILE || "";
987
+ };
988
+ var getGlobalClaudeConfigPath = () => {
989
+ return path2.join(getHomeDir(), ".claude.json");
990
+ };
991
+ var getGlobalClaudeSettingsPath = () => {
992
+ return path2.join(getHomeDir(), ".claude", "settings.json");
993
+ };
994
+ var ensureGlobalClaudeDir = () => {
995
+ const claudeDir = path2.join(getHomeDir(), ".claude");
996
+ if (!fs2.existsSync(claudeDir)) {
997
+ fs2.mkdirSync(claudeDir, { recursive: true });
998
+ }
999
+ return claudeDir;
1000
+ };
1001
+
1002
+ // src/launcher.ts
1003
+ import { spawn } from "child_process";
1004
+ import * as fs3 from "fs";
1005
+ import * as path3 from "path";
1006
+ import chalk2 from "chalk";
1007
+ import { decrypt, PERMISSION_PRESETS as PERMISSION_PRESETS2, ensureCcemDir as ensureCcemDir2 } from "@ccem/core";
1008
+ function buildEnvVars(envConfig) {
1009
+ const vars = {};
1010
+ if (envConfig.ANTHROPIC_BASE_URL) vars.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
1011
+ if (envConfig.ANTHROPIC_API_KEY) vars.ANTHROPIC_API_KEY = decrypt(envConfig.ANTHROPIC_API_KEY);
1012
+ if (envConfig.ANTHROPIC_MODEL) vars.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
1013
+ if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) vars.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
1014
+ return vars;
1015
+ }
1016
+ function buildPermArgs(modeName) {
1017
+ const preset = PERMISSION_PRESETS2[modeName];
1018
+ if (!preset) return [];
1019
+ const args = ["--permission-mode", preset.permissionMode];
1020
+ if (preset.permissions.allow.length > 0) {
1021
+ const quoted = preset.permissions.allow.map((t) => `"${t}"`).join(" ");
1022
+ args.push("--allowedTools", quoted);
1023
+ }
1024
+ if (preset.permissions.deny.length > 0) {
1025
+ const quoted = preset.permissions.deny.map((t) => `"${t}"`).join(" ");
1026
+ args.push("--disallowedTools", quoted);
1027
+ }
1028
+ return args;
1029
+ }
1030
+ function ensureSessionsDir() {
1031
+ const dir = path3.join(ensureCcemDir2(), "sessions");
1032
+ if (!fs3.existsSync(dir)) {
1033
+ fs3.mkdirSync(dir, { recursive: true });
1034
+ }
1035
+ return dir;
1036
+ }
1037
+ async function launchClaude(options) {
1038
+ const { envConfig, permMode, workingDir, sessionId, resumeSessionId, silent } = options;
1039
+ const env = { ...process.env };
1040
+ if (envConfig) {
1041
+ Object.assign(env, buildEnvVars(envConfig));
1042
+ }
1043
+ delete env.CLAUDECODE;
1044
+ const args = [];
1045
+ if (permMode) {
1046
+ const preset = PERMISSION_PRESETS2[permMode];
1047
+ if (preset) {
1048
+ if (!silent) {
1049
+ console.log(chalk2.green(`\u5DF2\u5E94\u7528 ${preset.name}\uFF08\u4E34\u65F6\uFF09`));
1050
+ console.log(chalk2.gray(`\u8BF4\u660E: ${preset.description}`));
1051
+ console.log("");
1052
+ }
1053
+ args.push(...buildPermArgs(permMode));
1054
+ }
1055
+ }
1056
+ if (resumeSessionId) {
1057
+ args.push("--resume", resumeSessionId);
1058
+ }
1059
+ if (workingDir) {
1060
+ process.chdir(workingDir);
1061
+ }
1062
+ if (!silent && !permMode) {
1063
+ console.log(renderStarting());
1064
+ }
1065
+ const sessionsDir = ensureSessionsDir();
1066
+ return new Promise((resolve2) => {
1067
+ const child = spawn("claude", args, {
1068
+ stdio: "inherit",
1069
+ shell: true,
1070
+ env
1071
+ });
1072
+ child.on("exit", (code) => {
1073
+ if (sessionId) {
1074
+ try {
1075
+ fs3.writeFileSync(
1076
+ path3.join(sessionsDir, `${sessionId}.exit`),
1077
+ String(code ?? 0)
1078
+ );
1079
+ } catch {
1080
+ }
1081
+ }
1082
+ process.exit(code ?? 0);
1083
+ });
1084
+ child.on("error", (err) => {
1085
+ console.error(chalk2.red(`\u542F\u52A8 Claude Code \u5931\u8D25: ${err.message}`));
1086
+ process.exit(1);
1087
+ });
1088
+ });
1089
+ }
1090
+
1091
+ // src/permissions.ts
1092
+ var readSettings = (settingsPath) => {
1093
+ if (fs4.existsSync(settingsPath)) {
1094
+ try {
1095
+ const content = fs4.readFileSync(settingsPath, "utf-8");
1096
+ return JSON.parse(content);
1097
+ } catch {
1098
+ console.warn(chalk3.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${settingsPath}\uFF0C\u5C06\u521B\u5EFA\u5907\u4EFD`));
1099
+ const backupPath = settingsPath + ".error." + Date.now();
1100
+ fs4.copyFileSync(settingsPath, backupPath);
1101
+ console.log(chalk3.gray(`\u5907\u4EFD\u5DF2\u4FDD\u5B58\u5230: ${backupPath}`));
1102
+ return {};
1103
+ }
1104
+ }
1105
+ return {};
1106
+ };
1107
+ var writeSettings = (settingsPath, config3) => {
1108
+ ensureClaudeDir();
1109
+ fs4.writeFileSync(settingsPath, JSON.stringify(config3, null, 2) + "\n");
1110
+ };
1111
+ var mergePermissions = (existing, preset) => {
1112
+ const existingAllow = existing.permissions?.allow || [];
1113
+ const existingDeny = existing.permissions?.deny || [];
1114
+ const mergedAllow = [.../* @__PURE__ */ new Set([...preset.allow, ...existingAllow])];
1115
+ const mergedDeny = [.../* @__PURE__ */ new Set([...preset.deny, ...existingDeny])];
1116
+ return {
1117
+ ...existing,
1118
+ permissions: {
1119
+ allow: mergedAllow,
1120
+ deny: mergedDeny
1121
+ }
1122
+ };
1123
+ };
1124
+ var applyPermissionMode = (modeName) => {
1125
+ const preset = PERMISSION_PRESETS3[modeName];
1126
+ if (!preset) {
1127
+ console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
1128
+ console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
1129
+ process.exit(1);
1130
+ }
1131
+ const settingsPath = getSettingsPath(true);
1132
+ const existing = readSettings(settingsPath);
1133
+ const merged = mergePermissions(existing, preset.permissions);
1134
+ writeSettings(settingsPath, merged);
1135
+ console.log(chalk3.green(`\u5DF2\u5E94\u7528 ${preset.name}`));
1136
+ console.log(chalk3.gray(`\u914D\u7F6E\u5DF2\u5199\u5165: ${settingsPath}`));
1137
+ console.log(chalk3.gray(`\u8BF4\u660E: ${preset.description}`));
1138
+ };
1139
+ var resetPermissions = () => {
1140
+ const settingsPath = getSettingsPath(true);
1141
+ if (!fs4.existsSync(settingsPath)) {
1142
+ console.log(chalk3.yellow("\u6CA1\u6709\u81EA\u5B9A\u4E49\u6743\u9650\u914D\u7F6E\u9700\u8981\u91CD\u7F6E"));
1143
+ return;
1144
+ }
1145
+ const config3 = readSettings(settingsPath);
1146
+ delete config3.permissions;
1147
+ if (Object.keys(config3).length === 0) {
1148
+ fs4.unlinkSync(settingsPath);
1149
+ console.log(chalk3.green("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF08\u6587\u4EF6\u4E3A\u7A7A\uFF09"));
1150
+ } else {
1151
+ writeSettings(settingsPath, config3);
1152
+ console.log(chalk3.green("\u6743\u9650\u914D\u7F6E\u5DF2\u91CD\u7F6E"));
1153
+ }
1154
+ console.log(chalk3.gray(`\u6587\u4EF6: ${settingsPath}`));
1155
+ };
1156
+ var showCurrentMode = () => {
1157
+ const settingsPath = getSettingsPath(true);
1158
+ if (!fs4.existsSync(settingsPath)) {
1159
+ console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
1160
+ console.log(chalk3.gray(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${settingsPath}`));
1161
+ return;
1162
+ }
1163
+ const config3 = readSettings(settingsPath);
1164
+ if (!config3.permissions) {
1165
+ console.log(chalk3.yellow("\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49\u6743\u9650"));
1166
+ return;
1167
+ }
1168
+ const matchedPreset = Object.entries(PERMISSION_PRESETS3).find(([_, preset]) => {
1169
+ const configAllow = new Set(config3.permissions?.allow || []);
1170
+ const configDeny = new Set(config3.permissions?.deny || []);
1171
+ const presetAllow = new Set(preset.permissions.allow);
1172
+ const presetDeny = new Set(preset.permissions.deny);
1173
+ const allowMatch = preset.permissions.allow.every((p) => configAllow.has(p));
1174
+ const denyMatch = preset.permissions.deny.every((p) => configDeny.has(p));
1175
+ return allowMatch && denyMatch;
1176
+ });
1177
+ if (matchedPreset) {
1178
+ console.log(chalk3.green(`\u5F53\u524D\u6A21\u5F0F: ${matchedPreset[0]} (${matchedPreset[1].name})`));
1179
+ } else {
1180
+ console.log(chalk3.yellow("\u5F53\u524D\u6A21\u5F0F: \u81EA\u5B9A\u4E49"));
1181
+ }
1182
+ console.log(chalk3.gray(`\u914D\u7F6E\u6587\u4EF6: ${settingsPath}`));
1183
+ const table = new Table2({
1184
+ head: ["\u7C7B\u578B", "\u89C4\u5219"],
1185
+ style: { head: ["cyan"] },
1186
+ colWidths: [10, 70]
1187
+ });
1188
+ const allowRules = config3.permissions.allow || [];
1189
+ const denyRules = config3.permissions.deny || [];
1190
+ table.push(["Allow", allowRules.length > 0 ? allowRules.join("\n") : "(\u65E0)"]);
1191
+ table.push(["Deny", denyRules.length > 0 ? denyRules.join("\n") : "(\u65E0)"]);
1192
+ console.log(table.toString());
1193
+ };
1194
+ var listAvailableModes = () => {
1195
+ const table = new Table2({
1196
+ head: ["\u6A21\u5F0F", "\u6807\u5FD7", "\u8BF4\u660E"],
1197
+ style: { head: ["cyan"] },
1198
+ colWidths: [15, 15, 50]
1199
+ });
1200
+ Object.entries(PERMISSION_PRESETS3).forEach(([key, preset]) => {
1201
+ table.push([preset.name, `--${key}`, preset.description]);
1202
+ });
1203
+ console.log(chalk3.bold("\u53EF\u7528\u6743\u9650\u6A21\u5F0F:\n"));
1204
+ console.log(table.toString());
1205
+ console.log(chalk3.gray("\n\u4E34\u65F6\u6A21\u5F0F: ccem <mode>"));
1206
+ console.log(chalk3.gray("\u6C38\u4E45\u6A21\u5F0F: ccem setup perms --<mode>"));
1207
+ };
1208
+ var runWithTempPermissions = async (modeName, envConfig) => {
1209
+ const preset = PERMISSION_PRESETS3[modeName];
1210
+ if (!preset) {
1211
+ console.error(chalk3.red(`\u672A\u77E5\u7684\u6743\u9650\u6A21\u5F0F: ${modeName}`));
1212
+ console.log(chalk3.yellow("\u53EF\u7528\u6A21\u5F0F: " + getPermissionModeNames().join(", ")));
1213
+ process.exit(1);
1214
+ }
1215
+ await launchClaude({ envConfig, permMode: modeName });
1216
+ };
1217
+
1218
+ // src/setup.ts
1219
+ import fs5 from "fs";
1220
+ import chalk4 from "chalk";
1221
+ import { spawn as spawn2 } from "child_process";
1222
+ var readJsonFile = (filePath) => {
1223
+ if (fs5.existsSync(filePath)) {
1224
+ try {
1225
+ const content = fs5.readFileSync(filePath, "utf-8");
1226
+ return JSON.parse(content);
1227
+ } catch {
1228
+ console.warn(chalk4.yellow(`\u8B66\u544A: \u65E0\u6CD5\u89E3\u6790 ${filePath}`));
1229
+ return {};
1230
+ }
1231
+ }
1232
+ return {};
1233
+ };
1234
+ var writeJsonFile = (filePath, data) => {
1235
+ fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
1236
+ };
1237
+ var setupOnboarding = () => {
1238
+ const configPath = getGlobalClaudeConfigPath();
1239
+ try {
1240
+ const config3 = readJsonFile(configPath);
1241
+ if (config3.hasCompletedOnboarding === true) {
1242
+ console.log(chalk4.gray(" \u2713 hasCompletedOnboarding \u5DF2\u8BBE\u7F6E"));
1243
+ return true;
1244
+ }
1245
+ config3.hasCompletedOnboarding = true;
1246
+ writeJsonFile(configPath, config3);
1247
+ console.log(chalk4.green(" \u2713 \u5DF2\u8BBE\u7F6E hasCompletedOnboarding: true"));
1248
+ return true;
1249
+ } catch (err) {
1250
+ console.error(chalk4.red(` \u2717 \u8BBE\u7F6E hasCompletedOnboarding \u5931\u8D25: ${err}`));
1251
+ return false;
1252
+ }
1253
+ };
1254
+ var setupEnvSettings = () => {
1255
+ const settingsPath = getGlobalClaudeSettingsPath();
1256
+ try {
1257
+ ensureGlobalClaudeDir();
1258
+ const settings = readJsonFile(settingsPath);
1259
+ if (!settings.env || typeof settings.env !== "object") {
1260
+ settings.env = {};
1261
+ }
1262
+ const env = settings.env;
1263
+ const envVars = {
1264
+ "DISABLE_BUG_COMMAND": "1",
1265
+ "DISABLE_ERROR_REPORTING": "1",
1266
+ "DISABLE_TELEMETRY": "1"
1267
+ };
1268
+ let changed = false;
1269
+ for (const [key, value] of Object.entries(envVars)) {
1270
+ if (env[key] !== value) {
1271
+ env[key] = value;
1272
+ changed = true;
1273
+ }
1274
+ }
1275
+ if (!changed) {
1276
+ console.log(chalk4.gray(" \u2713 \u73AF\u5883\u53D8\u91CF\u5DF2\u914D\u7F6E"));
1277
+ return true;
1278
+ }
1279
+ writeJsonFile(settingsPath, settings);
1280
+ console.log(chalk4.green(" \u2713 \u5DF2\u914D\u7F6E\u73AF\u5883\u53D8\u91CF:"));
1281
+ console.log(chalk4.gray(" DISABLE_BUG_COMMAND=1"));
1282
+ console.log(chalk4.gray(" DISABLE_ERROR_REPORTING=1"));
1283
+ console.log(chalk4.gray(" DISABLE_TELEMETRY=1"));
1284
+ return true;
1285
+ } catch (err) {
1286
+ console.error(chalk4.red(` \u2717 \u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u5931\u8D25: ${err}`));
1287
+ return false;
1288
+ }
1289
+ };
1290
+ var setupMcpTool = () => {
1291
+ return new Promise((resolve2) => {
1292
+ console.log(chalk4.cyan(" \u2192 \u6B63\u5728\u6DFB\u52A0 chrome-devtools MCP \u5DE5\u5177..."));
1293
+ const child = spawn2("claude", [
1294
+ "mcp",
1295
+ "add",
1296
+ "chrome-devtools",
1297
+ "npx",
1298
+ "chrome-devtools-mcp@latest",
1299
+ "--scope",
1300
+ "user"
1301
+ ], {
1302
+ stdio: "pipe",
1303
+ shell: true
1304
+ });
1305
+ let stdout = "";
1306
+ let stderr = "";
1307
+ child.stdout?.on("data", (data) => {
1308
+ stdout += data.toString();
1309
+ });
1310
+ child.stderr?.on("data", (data) => {
1311
+ stderr += data.toString();
1312
+ });
1313
+ child.on("exit", (code) => {
1314
+ if (code === 0) {
1315
+ console.log(chalk4.green(" \u2713 \u5DF2\u6DFB\u52A0 chrome-devtools MCP \u5DE5\u5177"));
1316
+ resolve2(true);
1317
+ } else {
1318
+ if (stderr.includes("already exists") || stdout.includes("already exists")) {
1319
+ console.log(chalk4.gray(" \u2713 chrome-devtools MCP \u5DE5\u5177\u5DF2\u5B58\u5728"));
1320
+ resolve2(true);
1321
+ } else {
1322
+ console.error(chalk4.red(` \u2717 \u6DFB\u52A0 MCP \u5DE5\u5177\u5931\u8D25 (code: ${code})`));
1323
+ if (stderr) console.error(chalk4.gray(` ${stderr.trim()}`));
1324
+ resolve2(false);
1325
+ }
1326
+ }
1327
+ });
1328
+ child.on("error", (err) => {
1329
+ console.error(chalk4.red(` \u2717 \u6267\u884C claude \u547D\u4EE4\u5931\u8D25: ${err.message}`));
1330
+ console.log(chalk4.yellow(" \u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Claude Code CLI"));
1331
+ resolve2(false);
1332
+ });
1333
+ });
1334
+ };
1335
+ var runSetupInit = async () => {
1336
+ console.log(chalk4.bold("\n\u{1F527} Claude Code \u521D\u59CB\u5316\u8BBE\u7F6E\n"));
1337
+ console.log(chalk4.cyan("1. \u8BBE\u7F6E onboarding \u72B6\u6001"));
1338
+ const step1 = setupOnboarding();
1339
+ console.log(chalk4.cyan("\n2. \u914D\u7F6E\u9690\u79C1\u8BBE\u7F6E"));
1340
+ const step2 = setupEnvSettings();
1341
+ console.log(chalk4.cyan("\n3. \u5B89\u88C5 MCP \u5DE5\u5177"));
1342
+ const step3 = await setupMcpTool();
1343
+ console.log("");
1344
+ if (step1 && step2 && step3) {
1345
+ console.log(chalk4.green.bold("\u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
1346
+ } else {
1347
+ console.log(chalk4.yellow.bold("\u26A0\uFE0F \u90E8\u5206\u6B65\u9AA4\u672A\u5B8C\u6210\uFF0C\u8BF7\u68C0\u67E5\u4E0A\u8FF0\u9519\u8BEF"));
1348
+ }
1349
+ console.log(chalk4.gray("\n\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E:"));
1350
+ console.log(chalk4.gray(` - ${getGlobalClaudeConfigPath()}`));
1351
+ console.log(chalk4.gray(` - ${getGlobalClaudeSettingsPath()}`));
1352
+ console.log("");
1353
+ };
1354
+
1355
+ // src/skills.ts
1356
+ import { execSync } from "child_process";
1357
+ import * as fs6 from "fs";
1358
+ import * as path4 from "path";
1359
+ import chalk5 from "chalk";
1360
+ var SKILL_GROUPS = {
1361
+ official: { label: "\u5B98\u65B9", icon: "\u{1F3E2}" },
1362
+ featured: { label: "\u7CBE\u9009", icon: "\u2B50" },
1363
+ others: { label: "\u5176\u4ED6", icon: "\u{1F4E6}" }
1364
+ };
1365
+ var SKILL_PRESETS = [
1366
+ // ===== 官方 (Official) =====
1367
+ {
1368
+ name: "frontend-design",
1369
+ description: "\u521B\u5EFA\u9AD8\u8D28\u91CF\u524D\u7AEF\u754C\u9762\u8BBE\u8BA1",
1370
+ group: "official",
1371
+ install: { type: "preset", name: "frontend-design" }
1372
+ },
1373
+ {
1374
+ name: "skill-creator",
1375
+ description: "\u521B\u5EFA\u65B0\u7684 Claude Code skills",
1376
+ group: "official",
1377
+ install: { type: "preset", name: "skill-creator" }
1378
+ },
1379
+ {
1380
+ name: "web-artifacts-builder",
1381
+ description: "\u6784\u5EFA\u53EF\u4EA4\u4E92\u7684 Web \u7EC4\u4EF6",
1382
+ group: "official",
1383
+ install: { type: "preset", name: "web-artifacts-builder" }
1384
+ },
1385
+ {
1386
+ name: "canvas-design",
1387
+ description: "Canvas \u7ED8\u56FE\u8BBE\u8BA1",
1388
+ group: "official",
1389
+ install: { type: "preset", name: "canvas-design" }
1390
+ },
1391
+ {
1392
+ name: "algorithmic-art",
1393
+ description: "\u7B97\u6CD5\u827A\u672F\u751F\u6210",
1394
+ group: "official",
1395
+ install: { type: "preset", name: "algorithmic-art" }
1396
+ },
1397
+ {
1398
+ name: "theme-factory",
1399
+ description: "\u4E3B\u9898\u5DE5\u5382 - \u521B\u5EFA UI \u4E3B\u9898",
1400
+ group: "official",
1401
+ install: { type: "preset", name: "theme-factory" }
1402
+ },
1403
+ {
1404
+ name: "mcp-builder",
1405
+ description: "\u6784\u5EFA MCP \u670D\u52A1\u5668",
1406
+ group: "official",
1407
+ install: { type: "preset", name: "mcp-builder" }
1408
+ },
1409
+ {
1410
+ name: "webapp-testing",
1411
+ description: "Web \u5E94\u7528\u6D4B\u8BD5",
1412
+ group: "official",
1413
+ install: { type: "preset", name: "webapp-testing" }
1414
+ },
1415
+ {
1416
+ name: "pdf",
1417
+ description: "PDF \u6587\u6863\u5904\u7406",
1418
+ group: "official",
1419
+ install: { type: "preset", name: "pdf" }
1420
+ },
1421
+ {
1422
+ name: "docx",
1423
+ description: "Word \u6587\u6863\u5904\u7406",
1424
+ group: "official",
1425
+ install: { type: "preset", name: "docx" }
1426
+ },
1427
+ {
1428
+ name: "pptx",
1429
+ description: "PowerPoint \u6F14\u793A\u6587\u7A3F\u5904\u7406",
1430
+ group: "official",
1431
+ install: { type: "preset", name: "pptx" }
1432
+ },
1433
+ {
1434
+ name: "xlsx",
1435
+ description: "Excel \u8868\u683C\u5904\u7406",
1436
+ group: "official",
1437
+ install: { type: "preset", name: "xlsx" }
1438
+ },
1439
+ {
1440
+ name: "brand-guidelines",
1441
+ description: "\u54C1\u724C\u6307\u5357\u751F\u6210",
1442
+ group: "official",
1443
+ install: { type: "preset", name: "brand-guidelines" }
1444
+ },
1445
+ {
1446
+ name: "doc-coauthoring",
1447
+ description: "\u6587\u6863\u534F\u4F5C\u7F16\u5199",
1448
+ group: "official",
1449
+ install: { type: "preset", name: "doc-coauthoring" }
1450
+ },
1451
+ {
1452
+ name: "internal-comms",
1453
+ description: "\u5185\u90E8\u901A\u4FE1\u6587\u6863",
1454
+ group: "official",
1455
+ install: { type: "preset", name: "internal-comms" }
1456
+ },
1457
+ {
1458
+ name: "slack-gif-creator",
1459
+ description: "Slack GIF \u521B\u5EFA\u5668",
1460
+ group: "official",
1461
+ install: { type: "preset", name: "slack-gif-creator" }
1462
+ },
1463
+ // ===== 精选 (Featured) =====
1464
+ {
1465
+ name: "superpowers",
1466
+ description: "Claude Code Plan\u6A21\u5F0F\u5347\u7EA7\u7248\uFF0C\u8FDE\u7EED\u8FFD\u95EE\u8BA8\u8BBA\u786E\u5B9A\u5F00\u53D1\u65B9\u6848",
1467
+ group: "featured",
1468
+ install: {
1469
+ type: "plugin",
1470
+ marketplace: "obra/superpowers-marketplace",
1471
+ package: "superpowers@superpowers-marketplace"
1472
+ }
1473
+ },
1474
+ {
1475
+ name: "ui-ux-pro-max",
1476
+ description: "\u4E13\u4E1A UI/UX \u8BBE\u8BA1",
1477
+ group: "featured",
1478
+ install: {
1479
+ type: "github",
1480
+ url: "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/tree/main/.claude/skills/ui-ux-pro-max"
1481
+ }
1482
+ },
1483
+ {
1484
+ name: "Humanizer-zh",
1485
+ description: "\u53BB\u9664\u6587\u672C\u4E2D AI \u751F\u6210\u75D5\u8FF9\uFF0C\u6539\u5199\u5F97\u66F4\u81EA\u7136\u3001\u66F4\u50CF\u4EBA\u7C7B\u4E66\u5199",
1486
+ group: "featured",
1487
+ install: {
1488
+ type: "github",
1489
+ url: "https://github.com/op7418/Humanizer-zh"
1490
+ }
1491
+ },
1492
+ // ===== 其他 (Others) =====
1493
+ {
1494
+ name: "skill-writer",
1495
+ description: "\u6307\u5BFC\u7528\u6237\u4E3A Claude Code \u521B\u5EFA\u4EE3\u7406\u6280\u80FD",
1496
+ group: "others",
1497
+ install: {
1498
+ type: "github",
1499
+ url: "https://github.com/pytorch/pytorch/tree/main/.claude/skills/skill-writer"
1500
+ }
1501
+ }
1502
+ ];
1503
+ function getSkillsByGroup(group) {
1504
+ return SKILL_PRESETS.filter((p) => p.group === group);
1505
+ }
1506
+ function getGroupOrder() {
1507
+ return ["official", "featured", "others"];
1508
+ }
1509
+ function parseGitHubUrl(url) {
1510
+ if (/^[\w-]+\/[\w-]+$/.test(url)) {
1511
+ const [owner2, repo2] = url.split("/");
1512
+ return { owner: owner2, repo: repo2, branch: "main", path: "" };
1513
+ }
1514
+ const match = url.match(
1515
+ /github\.com\/([^/]+)\/([^/]+)(?:\/tree\/([^/]+)(?:\/(.*))?)?/
1516
+ );
1517
+ if (!match) return null;
1518
+ const [, owner, repo, branch = "main", repoPath = ""] = match;
1519
+ return {
1520
+ owner,
1521
+ repo: repo.replace(/\.git$/, ""),
1522
+ branch,
1523
+ path: repoPath
1524
+ };
1525
+ }
1526
+ function getSkillsDir() {
1527
+ return path4.join(process.cwd(), ".claude", "skills");
1528
+ }
1529
+ function ensureSkillsDir() {
1530
+ const skillsDir = getSkillsDir();
1531
+ if (!fs6.existsSync(skillsDir)) {
1532
+ fs6.mkdirSync(skillsDir, { recursive: true });
1533
+ } else {
1534
+ cleanupTempDirs(skillsDir);
1535
+ }
1536
+ return skillsDir;
1537
+ }
1538
+ function cleanupTempDirs(skillsDir) {
1539
+ try {
1540
+ const entries = fs6.readdirSync(skillsDir, { withFileTypes: true });
1541
+ for (const entry of entries) {
1542
+ if (entry.isDirectory() && entry.name.startsWith(".tmp-")) {
1543
+ const tmpPath = path4.join(skillsDir, entry.name);
1544
+ fs6.rmSync(tmpPath, { recursive: true });
1545
+ }
1546
+ }
1547
+ } catch {
1548
+ }
1549
+ }
1550
+ function downloadSkillWithGit(owner, repo, branch, repoPath, targetName) {
1551
+ const skillsDir = ensureSkillsDir();
1552
+ const targetDir = path4.join(skillsDir, targetName);
1553
+ if (fs6.existsSync(targetDir)) {
1554
+ console.log(chalk5.yellow(`Skill "${targetName}" already exists. Updating...`));
1555
+ fs6.rmSync(targetDir, { recursive: true });
1556
+ }
1557
+ const repoUrl = `https://github.com/${owner}/${repo}.git`;
1558
+ const tempDir = path4.join(skillsDir, `.tmp-${Date.now()}`);
1559
+ try {
1560
+ fs6.mkdirSync(tempDir, { recursive: true });
1561
+ execSync(`git init`, { cwd: tempDir, stdio: "pipe" });
1562
+ execSync(`git remote add origin ${repoUrl}`, { cwd: tempDir, stdio: "pipe" });
1563
+ execSync(`git config core.sparseCheckout true`, { cwd: tempDir, stdio: "pipe" });
1564
+ const sparseFile = path4.join(tempDir, ".git", "info", "sparse-checkout");
1565
+ fs6.writeFileSync(sparseFile, repoPath ? `${repoPath}/
1566
+ ` : "*\n");
1567
+ execSync(`git pull --depth=1 origin ${branch}`, { cwd: tempDir, stdio: "pipe" });
1568
+ const sourceDir = repoPath ? path4.join(tempDir, repoPath) : tempDir;
1569
+ if (!fs6.existsSync(sourceDir)) {
1570
+ throw new Error(`Path "${repoPath}" not found in repository`);
1571
+ }
1572
+ copyDir(sourceDir, targetDir);
1573
+ console.log(chalk5.green(`Successfully installed skill "${targetName}"`));
1574
+ return true;
1575
+ } catch (error) {
1576
+ const errMsg = error instanceof Error ? error.message : String(error);
1577
+ console.error(chalk5.red(`Failed to download skill: ${errMsg}`));
1578
+ return false;
1579
+ } finally {
1580
+ if (fs6.existsSync(tempDir)) {
1581
+ fs6.rmSync(tempDir, { recursive: true });
1582
+ }
1583
+ }
1584
+ }
1585
+ function copyDir(src, dest) {
1586
+ fs6.mkdirSync(dest, { recursive: true });
1587
+ const entries = fs6.readdirSync(src, { withFileTypes: true });
1588
+ for (const entry of entries) {
1589
+ if (entry.name === ".git") continue;
1590
+ const srcPath = path4.join(src, entry.name);
1591
+ const destPath = path4.join(dest, entry.name);
1592
+ if (entry.isDirectory()) {
1593
+ copyDir(srcPath, destPath);
1594
+ } else {
1595
+ fs6.copyFileSync(srcPath, destPath);
1596
+ }
1597
+ }
1598
+ }
1599
+ function addSkillFromGitHub(urlOrPreset) {
1600
+ const preset = SKILL_PRESETS.find((p) => p.name === urlOrPreset);
1601
+ if (preset) {
1602
+ if (preset.install.type === "preset") {
1603
+ return downloadSkillWithGit(
1604
+ "anthropics",
1605
+ "skills",
1606
+ "main",
1607
+ `skills/${preset.install.name}`,
1608
+ preset.name
1609
+ );
1610
+ } else if (preset.install.type === "github") {
1611
+ const parsed2 = parseGitHubUrl(preset.install.url);
1612
+ if (!parsed2) {
1613
+ console.error(chalk5.red(`Invalid GitHub URL in preset: ${preset.install.url}`));
1614
+ return false;
1615
+ }
1616
+ return downloadSkillWithGit(
1617
+ parsed2.owner,
1618
+ parsed2.repo,
1619
+ parsed2.branch,
1620
+ parsed2.path,
1621
+ preset.name
1622
+ );
1623
+ } else if (preset.install.type === "plugin") {
1624
+ console.error(chalk5.yellow(`Plugin installation not yet supported for "${preset.name}"`));
1625
+ console.log(chalk5.gray(`Marketplace: ${preset.install.marketplace}`));
1626
+ console.log(chalk5.gray(`Package: ${preset.install.package}`));
1627
+ return false;
1628
+ }
1629
+ }
1630
+ const parsed = parseGitHubUrl(urlOrPreset);
1631
+ if (!parsed) {
1632
+ console.error(chalk5.red("Invalid GitHub URL or preset name"));
1633
+ console.log(chalk5.gray("Examples:"));
1634
+ console.log(chalk5.gray(" ccem skill add frontend-design"));
1635
+ console.log(chalk5.gray(" ccem skill add https://github.com/owner/repo"));
1636
+ console.log(chalk5.gray(" ccem skill add https://github.com/owner/repo/tree/main/path"));
1637
+ return false;
1638
+ }
1639
+ let skillName;
1640
+ if (parsed.path) {
1641
+ skillName = path4.basename(parsed.path);
1642
+ } else {
1643
+ skillName = parsed.repo;
1644
+ }
1645
+ return downloadSkillWithGit(
1646
+ parsed.owner,
1647
+ parsed.repo,
1648
+ parsed.branch,
1649
+ parsed.path,
1650
+ skillName
1651
+ );
1652
+ }
1653
+ function listInstalledSkills() {
1654
+ const skillsDir = getSkillsDir();
1655
+ if (!fs6.existsSync(skillsDir)) {
1656
+ return [];
1657
+ }
1658
+ const entries = fs6.readdirSync(skillsDir, { withFileTypes: true });
1659
+ return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => ({
1660
+ name: entry.name,
1661
+ path: path4.join(skillsDir, entry.name)
1662
+ }));
1663
+ }
1664
+ function removeSkill(name) {
1665
+ const skillsDir = getSkillsDir();
1666
+ const targetDir = path4.join(skillsDir, name);
1667
+ if (!fs6.existsSync(targetDir)) {
1668
+ console.error(chalk5.red(`Skill "${name}" not found`));
1669
+ return false;
1670
+ }
1671
+ fs6.rmSync(targetDir, { recursive: true });
1672
+ console.log(chalk5.green(`Removed skill "${name}"`));
1673
+ return true;
1674
+ }
1675
+ function installFromPluginMarketplace(marketplace, packageName) {
1676
+ try {
1677
+ console.log(chalk5.cyan(`Adding marketplace: ${marketplace}...`));
1678
+ execSync(`claude plugin marketplace add ${marketplace}`, { stdio: "inherit" });
1679
+ console.log(chalk5.cyan(`Installing package: ${packageName}...`));
1680
+ execSync(`claude plugin install ${packageName}`, { stdio: "inherit" });
1681
+ console.log(chalk5.green(`Successfully installed ${packageName}`));
1682
+ return true;
1683
+ } catch (error) {
1684
+ const errMsg = error instanceof Error ? error.message : String(error);
1685
+ console.error(chalk5.red(`Failed to install from marketplace: ${errMsg}`));
1686
+ return false;
1687
+ }
1688
+ }
1689
+ function installSkill(preset) {
1690
+ console.log(chalk5.cyan(`Installing ${preset.name}...`));
1691
+ switch (preset.install.type) {
1692
+ case "preset":
1693
+ const officialPreset = {
1694
+ repo: "anthropics/skills",
1695
+ path: `skills/${preset.install.name}`,
1696
+ branch: "main"
1697
+ };
1698
+ const [owner, repo] = officialPreset.repo.split("/");
1699
+ return downloadSkillWithGit(
1700
+ owner,
1701
+ repo,
1702
+ officialPreset.branch,
1703
+ officialPreset.path,
1704
+ preset.name
1705
+ );
1706
+ case "github":
1707
+ return addSkillFromGitHub(preset.install.url);
1708
+ case "plugin":
1709
+ return installFromPluginMarketplace(
1710
+ preset.install.marketplace,
1711
+ preset.install.package
1712
+ );
1713
+ }
1714
+ }
1715
+
1716
+ // src/components/index.tsx
1717
+ import React2 from "react";
1718
+ import { render } from "ink";
1719
+
1720
+ // src/components/SkillSelector.tsx
1721
+ import React, { useState, useEffect } from "react";
1722
+ import { Box, Text, useInput, useApp } from "ink";
1723
+ function SkillSelector({ onSelect, onCancel }) {
1724
+ const { exit } = useApp();
1725
+ const groups = getGroupOrder();
1726
+ const [activeGroupIndex, setActiveGroupIndex] = useState(0);
1727
+ const [selectedIndex, setSelectedIndex] = useState(0);
1728
+ const currentGroup = groups[activeGroupIndex];
1729
+ const skills = getSkillsByGroup(currentGroup);
1730
+ const items = [...skills, null];
1731
+ const maxIndex = items.length - 1;
1732
+ useEffect(() => {
1733
+ setSelectedIndex(0);
1734
+ }, [activeGroupIndex]);
1735
+ useInput((input, key) => {
1736
+ if (key.tab && !key.shift) {
1737
+ setActiveGroupIndex((prev) => (prev + 1) % groups.length);
1738
+ return;
1739
+ }
1740
+ if (key.tab && key.shift) {
1741
+ setActiveGroupIndex((prev) => (prev - 1 + groups.length) % groups.length);
1742
+ return;
1743
+ }
1744
+ if (key.upArrow) {
1745
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
1746
+ return;
1747
+ }
1748
+ if (key.downArrow) {
1749
+ setSelectedIndex((prev) => Math.min(maxIndex, prev + 1));
1750
+ return;
1751
+ }
1752
+ if (key.return) {
1753
+ const selected = items[selectedIndex];
1754
+ if (selected === null) {
1755
+ onSelect("custom");
1756
+ } else {
1757
+ onSelect(selected);
1758
+ }
1759
+ return;
1760
+ }
1761
+ if (key.escape || input === "q") {
1762
+ onCancel();
1763
+ exit();
1764
+ return;
1765
+ }
1766
+ });
1767
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, groups.map((group, index) => {
1768
+ const meta = SKILL_GROUPS[group];
1769
+ const isActive = index === activeGroupIndex;
1770
+ return /* @__PURE__ */ React.createElement(Box, { key: group, marginRight: 2 }, /* @__PURE__ */ React.createElement(
1771
+ Text,
1772
+ {
1773
+ bold: isActive,
1774
+ color: isActive ? "cyan" : "gray",
1775
+ inverse: isActive
1776
+ },
1777
+ " ",
1778
+ meta.icon,
1779
+ " ",
1780
+ meta.label,
1781
+ " "
1782
+ ));
1783
+ })), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "\u2500".repeat(50))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, items.map((item, index) => {
1784
+ const isSelected = index === selectedIndex;
1785
+ const prefix = isSelected ? "\u276F " : " ";
1786
+ if (item === null) {
1787
+ return /* @__PURE__ */ React.createElement(Box, { key: "custom" }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "yellow" : "gray" }, prefix, "\u8F93\u5165\u81EA\u5B9A\u4E49 GitHub URL"));
1788
+ }
1789
+ return /* @__PURE__ */ React.createElement(Box, { key: item.name }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "cyan" : void 0 }, prefix, /* @__PURE__ */ React.createElement(Text, { bold: isSelected }, item.name), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, " - ", item.description)));
1790
+ })), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Tab \u5207\u6362\u5206\u7EC4 | \u2191\u2193 \u9009\u62E9 | Enter \u786E\u8BA4 | Esc \u53D6\u6D88")));
1791
+ }
1792
+
1793
+ // src/components/index.tsx
1794
+ async function runSkillSelector() {
1795
+ return new Promise((resolve2) => {
1796
+ let resolved = false;
1797
+ const { unmount, waitUntilExit } = render(
1798
+ /* @__PURE__ */ React2.createElement(
1799
+ SkillSelector,
1800
+ {
1801
+ onSelect: (result) => {
1802
+ if (resolved) return;
1803
+ resolved = true;
1804
+ unmount();
1805
+ if (result === "custom") {
1806
+ resolve2({ type: "custom" });
1807
+ } else {
1808
+ resolve2({ type: "skill", skill: result });
1809
+ }
1810
+ },
1811
+ onCancel: () => {
1812
+ if (resolved) return;
1813
+ resolved = true;
1814
+ unmount();
1815
+ resolve2({ type: "cancelled" });
1816
+ }
1817
+ }
1818
+ )
1819
+ );
1820
+ waitUntilExit().then(() => {
1821
+ if (!resolved) {
1822
+ resolve2({ type: "cancelled" });
1823
+ }
1824
+ });
1825
+ });
1826
+ }
1827
+
1828
+ // src/remote.ts
1829
+ import crypto2 from "crypto";
1830
+ import chalk6 from "chalk";
1831
+ import Conf from "conf";
1832
+ import { encrypt } from "@ccem/core";
1833
+ var config = new Conf({
1834
+ projectName: "claude-code-env-manager"
1835
+ });
1836
+ var decryptWithSecret = (encryptedBase64, secret) => {
1837
+ const key = crypto2.scryptSync(secret, "ccem-salt", 32);
1838
+ const combined = Buffer.from(encryptedBase64, "base64");
1839
+ const iv = combined.subarray(0, 16);
1840
+ const encryptedHex = combined.subarray(16).toString("hex");
1841
+ const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
1842
+ let decrypted = decipher.update(encryptedHex, "hex", "utf8");
1843
+ decrypted += decipher.final("utf8");
1844
+ return decrypted;
1845
+ };
1846
+ var getUniqueName = (baseName, existingNames) => {
1847
+ if (!existingNames.has(baseName)) {
1848
+ return baseName;
1849
+ }
1850
+ let suffix = 1;
1851
+ let newName = `${baseName}-remote`;
1852
+ while (existingNames.has(newName)) {
1853
+ suffix++;
1854
+ newName = `${baseName}-remote-${suffix}`;
1855
+ }
1856
+ return newName;
1857
+ };
1858
+ var loadFromRemote = async (url, secret) => {
1859
+ console.log(chalk6.gray("Fetching from remote..."));
1860
+ let response;
1861
+ try {
1862
+ response = await fetch(url);
1863
+ } catch (err) {
1864
+ console.error(chalk6.red("Error: Failed to connect to server"));
1865
+ console.error(chalk6.gray(err.message));
1866
+ process.exit(1);
1867
+ }
1868
+ if (response.status === 401) {
1869
+ console.error(chalk6.red("Error: Invalid key (HTTP 401)"));
1870
+ process.exit(1);
1871
+ }
1872
+ if (response.status === 429) {
1873
+ console.error(chalk6.red("Error: Too many requests, please try again later"));
1874
+ process.exit(1);
1875
+ }
1876
+ if (!response.ok) {
1877
+ console.error(chalk6.red(`Error: Server returned HTTP ${response.status}`));
1878
+ process.exit(1);
1879
+ }
1880
+ let data;
1881
+ try {
1882
+ data = await response.json();
1883
+ } catch {
1884
+ console.error(chalk6.red("Error: Invalid response format from server"));
1885
+ process.exit(1);
1886
+ }
1887
+ if (!data.encrypted) {
1888
+ console.error(chalk6.red("Error: Invalid response format from server"));
1889
+ process.exit(1);
1890
+ }
1891
+ let decrypted;
1892
+ try {
1893
+ const jsonStr = decryptWithSecret(data.encrypted, secret);
1894
+ decrypted = JSON.parse(jsonStr);
1895
+ } catch {
1896
+ console.error(chalk6.red("Error: Decryption failed, check your --secret"));
1897
+ process.exit(1);
1898
+ }
1899
+ if (!decrypted.environments || typeof decrypted.environments !== "object") {
1900
+ console.error(chalk6.red("Error: Invalid response format from server"));
1901
+ process.exit(1);
1902
+ }
1903
+ const registries = config.get("registries");
1904
+ const existingNames = new Set(Object.keys(registries));
1905
+ const results = [];
1906
+ for (const [name, envConfig] of Object.entries(decrypted.environments)) {
1907
+ const uniqueName = getUniqueName(name, existingNames);
1908
+ const renamed = uniqueName !== name;
1909
+ const configToSave = { ...envConfig };
1910
+ if (configToSave.ANTHROPIC_API_KEY) {
1911
+ configToSave.ANTHROPIC_API_KEY = encrypt(configToSave.ANTHROPIC_API_KEY);
1912
+ }
1913
+ registries[uniqueName] = configToSave;
1914
+ existingNames.add(uniqueName);
1915
+ results.push({
1916
+ name: uniqueName,
1917
+ originalName: name,
1918
+ renamed
1919
+ });
1920
+ }
1921
+ config.set("registries", registries);
1922
+ console.log(chalk6.green(`
1923
+ Loaded ${results.length} environment(s) from remote:`));
1924
+ for (const result of results) {
1925
+ if (result.renamed) {
1926
+ console.log(chalk6.yellow(` + ${result.originalName} \u2192 ${result.name} (renamed, local exists)`));
1927
+ } else {
1928
+ console.log(chalk6.green(` + ${result.name} (new)`));
1929
+ }
1930
+ }
1931
+ console.log(chalk6.gray("\nRun 'ccem ls' to see all environments."));
1932
+ };
1933
+
1934
+ // src/cron-skill.ts
1935
+ var CCEM_CRON_SKILL_CONTENT = `# ccem-cron
1936
+
1937
+ Manage scheduled tasks for Claude Code via \\\`~/.ccem/cron-tasks.json\\\`. Supports creating, listing, and deleting cron tasks through conversational interaction.
1938
+
1939
+ ## Storage
1940
+
1941
+ All tasks are stored in \\\`~/.ccem/cron-tasks.json\\\` as a JSON array. Each task follows this schema:
1942
+
1943
+ \\\`\\\`\\\`json
1944
+ {
1945
+ "id": "uuid-v4",
1946
+ "name": "task name",
1947
+ "cronExpression": "0 9 * * *",
1948
+ "prompt": "Claude prompt to execute",
1949
+ "workingDir": "/absolute/path",
1950
+ "envName": null,
1951
+ "enabled": true,
1952
+ "timeoutSecs": 300,
1953
+ "templateId": null,
1954
+ "triggerType": "schedule",
1955
+ "parentTaskId": null,
1956
+ "createdAt": "ISO-8601",
1957
+ "updatedAt": "ISO-8601"
1958
+ }
1959
+ \\\`\\\`\\\`
1960
+
1961
+ ## Instructions
1962
+
1963
+ Determine the user's intent from their message:
1964
+ - **List/view**: user says "list", "show", "view", "\u67E5\u770B", "\u5217\u51FA"
1965
+ - **Delete/remove**: user says "delete", "remove", "\u5220\u9664", "\u79FB\u9664"
1966
+ - **Create**: default for anything else
1967
+
1968
+ ### Creating a Task
1969
+
1970
+ 1. Ask the user for:
1971
+ - Task name (short descriptive label)
1972
+ - What they want Claude to do (natural language; derive the \\\`prompt\\\` field from this)
1973
+ - When to run it (derive the \\\`cronExpression\\\`; show common examples below)
1974
+ - Working directory (default: current directory via \\\`pwd\\\`)
1975
+ - Timeout in seconds (default: 300)
1976
+
1977
+ 2. Show common cron patterns to help the user choose:
1978
+
1979
+ \\\`\\\`\\\`
1980
+ Every minute: * * * * *
1981
+ Every 30 minutes: */30 * * * *
1982
+ Every hour: 0 * * * *
1983
+ Every day at 9am: 0 9 * * *
1984
+ Every day at midnight: 0 0 * * *
1985
+ Weekdays at 9am: 0 9 * * 1-5
1986
+ Every Monday 8am: 0 8 * * 1
1987
+ Every 1st of month: 0 0 1 * *
1988
+ \\\`\\\`\\\`
1989
+
1990
+ 3. Generate the task and write it:
1991
+
1992
+ \\\`\\\`\\\`bash
1993
+ # Generate UUID and timestamp
1994
+ TASK_ID=\\$(uuidgen | tr '[:upper:]' '[:lower:]')
1995
+ NOW=\\$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
1996
+ WORK_DIR=\\$(pwd)
1997
+
1998
+ # Read existing file or initialize empty array
1999
+ if [ -f ~/.ccem/cron-tasks.json ]; then
2000
+ EXISTING=\\$(cat ~/.ccem/cron-tasks.json)
2001
+ else
2002
+ mkdir -p ~/.ccem
2003
+ EXISTING="[]"
2004
+ fi
2005
+
2006
+ # Build new task JSON and append using python3 for safe JSON manipulation
2007
+ python3 -c "
2008
+ import json, sys
2009
+ tasks = json.loads('''\\$EXISTING''')
2010
+ tasks.append({
2011
+ 'id': '\\$TASK_ID',
2012
+ 'name': 'TASK_NAME_HERE',
2013
+ 'cronExpression': 'CRON_EXPR_HERE',
2014
+ 'prompt': 'PROMPT_HERE',
2015
+ 'workingDir': '\\$WORK_DIR',
2016
+ 'envName': None,
2017
+ 'enabled': True,
2018
+ 'timeoutSecs': TIMEOUT_HERE,
2019
+ 'templateId': None,
2020
+ 'triggerType': 'schedule',
2021
+ 'parentTaskId': None,
2022
+ 'createdAt': '\\$NOW',
2023
+ 'updatedAt': '\\$NOW'
2024
+ })
2025
+ print(json.dumps(tasks, indent=2, ensure_ascii=False))
2026
+ " > ~/.ccem/cron-tasks.json
2027
+ \\\`\\\`\\\`
2028
+
2029
+ Replace \\\`TASK_NAME_HERE\\\`, \\\`CRON_EXPR_HERE\\\`, \\\`PROMPT_HERE\\\`, and \\\`TIMEOUT_HERE\\\` with actual values. Escape any quotes or special characters in the prompt string properly for Python.
2030
+
2031
+ 4. Confirm creation by reading back the file and showing the new task.
2032
+
2033
+ ### Listing Tasks
2034
+
2035
+ \\\`\\\`\\\`bash
2036
+ if [ -f ~/.ccem/cron-tasks.json ]; then
2037
+ cat ~/.ccem/cron-tasks.json
2038
+ else
2039
+ echo "No tasks found. File ~/.ccem/cron-tasks.json does not exist."
2040
+ fi
2041
+ \\\`\\\`\\\`
2042
+
2043
+ Format the output as a readable table with columns: name, cron expression, enabled status, working directory, and creation date. If the list is empty, tell the user.
2044
+
2045
+ ### Deleting a Task
2046
+
2047
+ 1. First list all tasks so the user can identify which to delete.
2048
+ 2. Ask the user to confirm by name or ID.
2049
+ 3. Remove the matching task:
2050
+
2051
+ \\\`\\\`\\\`bash
2052
+ python3 -c "
2053
+ import json
2054
+ with open('\\$HOME/.ccem/cron-tasks.json') as f:
2055
+ tasks = json.load(f)
2056
+ tasks = [t for t in tasks if t['id'] != 'TARGET_ID' and t['name'] != 'TARGET_NAME']
2057
+ with open('\\$HOME/.ccem/cron-tasks.json', 'w') as f:
2058
+ json.dump(tasks, f, indent=2, ensure_ascii=False)
2059
+ print(json.dumps(tasks, indent=2, ensure_ascii=False))
2060
+ "
2061
+ \\\`\\\`\\\`
2062
+
2063
+ Replace \\\`TARGET_ID\\\` or \\\`TARGET_NAME\\\` with the user's selection.
2064
+
2065
+ 4. Confirm deletion by showing the remaining tasks.
2066
+
2067
+ ## Safety Rules
2068
+
2069
+ - Always read the existing file before writing to avoid data loss.
2070
+ - Use \\\`python3\\\` for JSON manipulation to ensure valid output (never hand-construct JSON with echo/cat).
2071
+ - Create \\\`~/.ccem/\\\` directory if it does not exist.
2072
+ - When the file is missing or empty, start with an empty array \\\`[]\\\`.
2073
+ - Always show the user what will be written before confirming.
2074
+ `;
2075
+
2076
+ // src/index.ts
2077
+ var __filename = fileURLToPath(import.meta.url);
2078
+ var __dirname2 = path5.dirname(__filename);
2079
+ var pkgPath = path5.resolve(__dirname2, "..", "package.json");
2080
+ var pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
2081
+ var program = new Command();
2082
+ ensureCcemDir3();
2083
+ var config2 = new Conf2({
2084
+ projectName: "claude-code-env-manager",
2085
+ cwd: getCcemConfigDir(),
2086
+ // 使用新路径
2087
+ defaults: {
2088
+ registries: {
2089
+ "official": {
2090
+ ANTHROPIC_BASE_URL: "https://api.anthropic.com",
2091
+ ANTHROPIC_MODEL: "claude-sonnet-4-5-20250929",
2092
+ ANTHROPIC_SMALL_FAST_MODEL: "claude-haiku-4-5-20251001"
2093
+ }
2094
+ },
2095
+ current: "official",
2096
+ defaultMode: null
2097
+ }
2098
+ });
2099
+ var PERMISSION_MODES = ["yolo", "dev", "readonly", "safe", "ci", "audit"];
2100
+ var usageStats = null;
2101
+ var usageLoading = true;
2102
+ var usageAbortController = null;
2103
+ var initUsageStats = (onUpdate) => {
2104
+ const cachedStats = getUsageStatsFromCache();
2105
+ if (cachedStats) {
2106
+ usageStats = cachedStats;
2107
+ usageLoading = false;
2108
+ } else {
2109
+ if (onUpdate) {
2110
+ startSpinner(onUpdate);
2111
+ }
2112
+ }
2113
+ if (usageAbortController) {
2114
+ usageAbortController.abort();
2115
+ }
2116
+ usageAbortController = new AbortController();
2117
+ const signal = usageAbortController.signal;
2118
+ getUsageStats(signal).then((stats) => {
2119
+ if (signal.aborted) return;
2120
+ const needRefresh = usageLoading || usageStats && stats && usageStats.lastUpdated !== stats.lastUpdated;
2121
+ usageStats = stats;
2122
+ usageLoading = false;
2123
+ stopSpinner();
2124
+ if (needRefresh && onUpdate) {
2125
+ onUpdate();
2126
+ }
2127
+ }).catch((err) => {
2128
+ if (err.message === "Aborted") return;
2129
+ usageLoading = false;
2130
+ stopSpinner();
2131
+ });
2132
+ };
2133
+ program.name("ccem").description("Claude Code Environment Manager - \u7BA1\u7406 Claude Code \u73AF\u5883\u53D8\u91CF\u548C\u6743\u9650").version(pkg.version).option("--mode", "\u67E5\u770B\u5F53\u524D\u6743\u9650\u6A21\u5F0F").option("--list-modes", "\u5217\u51FA\u6240\u6709\u53EF\u7528\u6743\u9650\u6A21\u5F0F");
2134
+ PERMISSION_MODES.forEach((mode) => {
2135
+ const preset = PERMISSION_PRESETS4[mode];
2136
+ program.command(mode).description(`\u4E34\u65F6\u5E94\u7528 ${preset.name}\uFF0C\u9000\u51FA\u540E\u8FD8\u539F`).action(async () => {
2137
+ const registries = config2.get("registries");
2138
+ const current = config2.get("current");
2139
+ const envConfig = registries[current];
2140
+ await runWithTempPermissions(mode, envConfig);
2141
+ });
2142
+ });
2143
+ var showCurrentEnv = (usageStats2, usageLoading2) => {
2144
+ if (!process.stdout.isTTY) return;
2145
+ const current = config2.get("current");
2146
+ const registries = config2.get("registries");
2147
+ const env = registries[current];
2148
+ const defaultMode = config2.get("defaultMode");
2149
+ if (!env) return;
2150
+ console.log(renderLogoWithEnvPanel(current, {
2151
+ ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
2152
+ ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY ? decrypt2(env.ANTHROPIC_API_KEY) : void 0,
2153
+ ANTHROPIC_MODEL: env.ANTHROPIC_MODEL,
2154
+ ANTHROPIC_SMALL_FAST_MODEL: env.ANTHROPIC_SMALL_FAST_MODEL
2155
+ }, defaultMode));
2156
+ console.log("");
2157
+ console.log(renderUsageLine(usageStats2, usageLoading2));
2158
+ console.log(renderCompactHeader());
2159
+ console.log("");
2160
+ };
2161
+ var switchEnvironment = async (name) => {
2162
+ const registries = config2.get("registries");
2163
+ if (!registries[name]) {
2164
+ console.error(chalk7.red(`Environment '${name}' not found.`));
2165
+ return;
2166
+ }
2167
+ config2.set("current", name);
2168
+ if (process.stdout.isTTY) {
2169
+ console.log(chalk7.green(`Switched to environment '${name}'`));
2170
+ } else {
2171
+ console.error(chalk7.green(`Switched to environment '${name}'`));
2172
+ }
2173
+ showCurrentEnv(null, false);
2174
+ const env = registries[name];
2175
+ const exportCmds = [];
2176
+ if (env.ANTHROPIC_BASE_URL) exportCmds.push(`export ANTHROPIC_BASE_URL="${env.ANTHROPIC_BASE_URL}"`);
2177
+ if (env.ANTHROPIC_API_KEY) exportCmds.push(`export ANTHROPIC_API_KEY="${decrypt2(env.ANTHROPIC_API_KEY)}"`);
2178
+ if (env.ANTHROPIC_MODEL) exportCmds.push(`export ANTHROPIC_MODEL="${env.ANTHROPIC_MODEL}"`);
2179
+ if (env.ANTHROPIC_SMALL_FAST_MODEL) exportCmds.push(`export ANTHROPIC_SMALL_FAST_MODEL="${env.ANTHROPIC_SMALL_FAST_MODEL}"`);
2180
+ if (process.stdout.isTTY) {
2181
+ console.log(chalk7.yellow("\nTo apply to current shell immediately, run:"));
2182
+ console.log(chalk7.cyan("eval $(ccem env)"));
2183
+ console.log(chalk7.yellow("\nOr manually export:"));
2184
+ exportCmds.forEach((cmd) => console.log(cmd));
2185
+ } else {
2186
+ exportCmds.forEach((cmd) => console.log(cmd));
2187
+ }
2188
+ };
2189
+ program.command("ls").description("List all configured environments").action(() => {
2190
+ const registries = config2.get("registries");
2191
+ const current = config2.get("current");
2192
+ const table = new Table3({
2193
+ head: ["Name", "Base URL", "Model"],
2194
+ style: { head: ["cyan"] }
2195
+ });
2196
+ Object.keys(registries).forEach((name) => {
2197
+ const reg = registries[name];
2198
+ const prefix = name === current ? chalk7.green("* ") : " ";
2199
+ table.push([
2200
+ prefix + name,
2201
+ reg.ANTHROPIC_BASE_URL || "-",
2202
+ reg.ANTHROPIC_MODEL || "-"
2203
+ ]);
2204
+ });
2205
+ console.log(table.toString());
2206
+ });
2207
+ program.command("use <name>").description("Switch to a specific environment").action(async (name) => {
2208
+ await switchEnvironment(name);
2209
+ });
2210
+ program.command("add <name>").description("Add a new environment configuration").action(async (name) => {
2211
+ const registries = config2.get("registries");
2212
+ if (registries[name]) {
2213
+ console.log(chalk7.red(`Environment '${name}' already exists.`));
2214
+ return;
2215
+ }
2216
+ const { usePreset } = await inquirer.prompt([
2217
+ {
2218
+ type: "confirm",
2219
+ name: "usePreset",
2220
+ message: "Do you want to use a preset configuration?",
2221
+ default: true
2222
+ }
2223
+ ]);
2224
+ let presetConfig = {};
2225
+ if (usePreset) {
2226
+ const { presetName } = await inquirer.prompt([
2227
+ {
2228
+ type: "list",
2229
+ name: "presetName",
2230
+ message: "Select a preset:",
2231
+ choices: Object.keys(ENV_PRESETS)
2232
+ }
2233
+ ]);
2234
+ presetConfig = ENV_PRESETS[presetName];
2235
+ }
2236
+ const answers = await inquirer.prompt([
2237
+ {
2238
+ type: "input",
2239
+ name: "ANTHROPIC_BASE_URL",
2240
+ message: "Enter ANTHROPIC_BASE_URL:",
2241
+ default: presetConfig.ANTHROPIC_BASE_URL || "https://api.anthropic.com"
2242
+ },
2243
+ {
2244
+ type: "password",
2245
+ name: "ANTHROPIC_API_KEY",
2246
+ message: "Enter ANTHROPIC_API_KEY:"
2247
+ },
2248
+ {
2249
+ type: "input",
2250
+ name: "ANTHROPIC_MODEL",
2251
+ message: "Enter ANTHROPIC_MODEL:",
2252
+ default: presetConfig.ANTHROPIC_MODEL || "claude-sonnet-4-5-20250929"
2253
+ },
2254
+ {
2255
+ type: "input",
2256
+ name: "ANTHROPIC_SMALL_FAST_MODEL",
2257
+ message: "Enter ANTHROPIC_SMALL_FAST_MODEL:",
2258
+ default: presetConfig.ANTHROPIC_SMALL_FAST_MODEL || "claude-haiku-4-5-20251001"
2259
+ }
2260
+ ]);
2261
+ if (answers.ANTHROPIC_API_KEY) {
2262
+ answers.ANTHROPIC_API_KEY = encrypt2(answers.ANTHROPIC_API_KEY);
2263
+ }
2264
+ registries[name] = answers;
2265
+ config2.set("registries", registries);
2266
+ console.log(chalk7.green(`Environment '${name}' added successfully.`));
2267
+ });
2268
+ program.command("del <name>").description("Delete an environment configuration").action((name) => {
2269
+ const registries = config2.get("registries");
2270
+ if (!registries[name]) {
2271
+ console.log(chalk7.red(`Environment '${name}' not found.`));
2272
+ return;
2273
+ }
2274
+ if (name === "official") {
2275
+ console.log(chalk7.red(`Cannot delete default 'official' environment.`));
2276
+ return;
2277
+ }
2278
+ delete registries[name];
2279
+ config2.set("registries", registries);
2280
+ const current = config2.get("current");
2281
+ if (current === name) {
2282
+ config2.set("current", "official");
2283
+ console.log(chalk7.yellow(`Deleted current environment. Switched back to 'official'.`));
2284
+ }
2285
+ console.log(chalk7.green(`Environment '${name}' deleted.`));
2286
+ });
2287
+ program.command("rename <old> <new>").description("Rename an environment configuration").action((oldName, newName) => {
2288
+ const registries = config2.get("registries");
2289
+ if (!registries[oldName]) {
2290
+ console.log(chalk7.red(`Environment '${oldName}' not found.`));
2291
+ return;
2292
+ }
2293
+ if (registries[newName]) {
2294
+ console.log(chalk7.red(`Environment '${newName}' already exists.`));
2295
+ return;
2296
+ }
2297
+ if (oldName === "official") {
2298
+ console.log(chalk7.red(`Cannot rename default 'official' environment.`));
2299
+ return;
2300
+ }
2301
+ registries[newName] = registries[oldName];
2302
+ delete registries[oldName];
2303
+ config2.set("registries", registries);
2304
+ const current = config2.get("current");
2305
+ if (current === oldName) {
2306
+ config2.set("current", newName);
2307
+ }
2308
+ console.log(chalk7.green(`Environment '${oldName}' renamed to '${newName}'.`));
2309
+ });
2310
+ program.command("cp <source> <target>").description("Copy an environment configuration").action(async (source, target) => {
2311
+ const registries = config2.get("registries");
2312
+ if (!registries[source]) {
2313
+ console.log(chalk7.red(`Environment '${source}' not found.`));
2314
+ return;
2315
+ }
2316
+ if (registries[target]) {
2317
+ console.log(chalk7.red(`Environment '${target}' already exists.`));
2318
+ return;
2319
+ }
2320
+ registries[target] = { ...registries[source] };
2321
+ config2.set("registries", registries);
2322
+ console.log(chalk7.green(`Environment '${source}' copied to '${target}'.`));
2323
+ const { modify } = await inquirer.prompt([
2324
+ {
2325
+ type: "confirm",
2326
+ name: "modify",
2327
+ message: "Do you want to modify the configuration?",
2328
+ default: false
2329
+ }
2330
+ ]);
2331
+ if (modify) {
2332
+ const current = registries[target];
2333
+ const answers = await inquirer.prompt([
2334
+ {
2335
+ type: "input",
2336
+ name: "ANTHROPIC_BASE_URL",
2337
+ message: "ANTHROPIC_BASE_URL:",
2338
+ default: current.ANTHROPIC_BASE_URL
2339
+ },
2340
+ {
2341
+ type: "password",
2342
+ name: "ANTHROPIC_API_KEY",
2343
+ message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2344
+ },
2345
+ {
2346
+ type: "input",
2347
+ name: "ANTHROPIC_MODEL",
2348
+ message: "ANTHROPIC_MODEL:",
2349
+ default: current.ANTHROPIC_MODEL
2350
+ },
2351
+ {
2352
+ type: "input",
2353
+ name: "ANTHROPIC_SMALL_FAST_MODEL",
2354
+ message: "ANTHROPIC_SMALL_FAST_MODEL:",
2355
+ default: current.ANTHROPIC_SMALL_FAST_MODEL
2356
+ }
2357
+ ]);
2358
+ if (answers.ANTHROPIC_BASE_URL) current.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
2359
+ if (answers.ANTHROPIC_API_KEY) current.ANTHROPIC_API_KEY = encrypt2(answers.ANTHROPIC_API_KEY);
2360
+ if (answers.ANTHROPIC_MODEL) current.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
2361
+ if (answers.ANTHROPIC_SMALL_FAST_MODEL) current.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
2362
+ registries[target] = current;
2363
+ config2.set("registries", registries);
2364
+ console.log(chalk7.green(`Environment '${target}' updated.`));
2365
+ }
2366
+ });
2367
+ program.command("current").description("Show current environment name").action(() => {
2368
+ const current = config2.get("current");
2369
+ console.log(chalk7.green(current));
2370
+ });
2371
+ program.command("env").description("Output environment variables for shell eval").option("--json", "Output as JSON").action((options) => {
2372
+ const registries = config2.get("registries");
2373
+ const current = config2.get("current");
2374
+ const env = registries[current];
2375
+ if (!env) return;
2376
+ const outputEnv = { ...env };
2377
+ if (outputEnv.ANTHROPIC_API_KEY) {
2378
+ outputEnv.ANTHROPIC_API_KEY = decrypt2(outputEnv.ANTHROPIC_API_KEY);
2379
+ }
2380
+ if (options.json) {
2381
+ console.log(JSON.stringify(outputEnv, null, 2));
2382
+ } else {
2383
+ if (outputEnv.ANTHROPIC_BASE_URL) console.log(`export ANTHROPIC_BASE_URL="${outputEnv.ANTHROPIC_BASE_URL}"`);
2384
+ if (outputEnv.ANTHROPIC_API_KEY) console.log(`export ANTHROPIC_API_KEY="${outputEnv.ANTHROPIC_API_KEY}"`);
2385
+ if (outputEnv.ANTHROPIC_MODEL) console.log(`export ANTHROPIC_MODEL="${outputEnv.ANTHROPIC_MODEL}"`);
2386
+ if (outputEnv.ANTHROPIC_SMALL_FAST_MODEL) console.log(`export ANTHROPIC_SMALL_FAST_MODEL="${outputEnv.ANTHROPIC_SMALL_FAST_MODEL}"`);
2387
+ }
2388
+ });
2389
+ program.command("run <command...>").description("Run a command with the current environment variables").action((command) => {
2390
+ const registries = config2.get("registries");
2391
+ const current = config2.get("current");
2392
+ const envConfig = registries[current];
2393
+ if (!envConfig) {
2394
+ console.error(chalk7.red("No environment configuration found."));
2395
+ process.exit(1);
2396
+ }
2397
+ const env = { ...process.env };
2398
+ if (envConfig.ANTHROPIC_BASE_URL) env.ANTHROPIC_BASE_URL = envConfig.ANTHROPIC_BASE_URL;
2399
+ if (envConfig.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = decrypt2(envConfig.ANTHROPIC_API_KEY || "");
2400
+ if (envConfig.ANTHROPIC_MODEL) env.ANTHROPIC_MODEL = envConfig.ANTHROPIC_MODEL;
2401
+ if (envConfig.ANTHROPIC_SMALL_FAST_MODEL) env.ANTHROPIC_SMALL_FAST_MODEL = envConfig.ANTHROPIC_SMALL_FAST_MODEL;
2402
+ const [cmd, ...args] = command;
2403
+ const child = spawn3(cmd, args, {
2404
+ env,
2405
+ stdio: "inherit",
2406
+ shell: true
2407
+ });
2408
+ child.on("exit", (code) => {
2409
+ process.exit(code ?? 0);
2410
+ });
2411
+ });
2412
+ var setupCmd = program.command("setup").description("Setup commands for permanent configurations");
2413
+ setupCmd.command("perms").description("\u6C38\u4E45\u914D\u7F6E\u6743\u9650\u6A21\u5F0F").option("--yolo", "\u5E94\u7528 YOLO \u6A21\u5F0F\uFF08\u5168\u90E8\u653E\u5F00\uFF09").option("--dev", "\u5E94\u7528\u5F00\u53D1\u6A21\u5F0F").option("--readonly", "\u5E94\u7528\u53EA\u8BFB\u6A21\u5F0F").option("--safe", "\u5E94\u7528\u5B89\u5168\u6A21\u5F0F").option("--ci", "\u5E94\u7528 CI/CD \u6A21\u5F0F").option("--audit", "\u5E94\u7528\u5BA1\u8BA1\u6A21\u5F0F").option("--reset", "\u91CD\u7F6E\u6743\u9650\u914D\u7F6E").action(function() {
2414
+ const options = this.opts();
2415
+ if (options.reset) {
2416
+ resetPermissions();
2417
+ return;
2418
+ }
2419
+ for (const mode of PERMISSION_MODES) {
2420
+ if (options[mode]) {
2421
+ applyPermissionMode(mode);
2422
+ return;
2423
+ }
2424
+ }
2425
+ console.log(chalk7.yellow("\u8BF7\u6307\u5B9A\u4E00\u4E2A\u6743\u9650\u6A21\u5F0F\uFF0C\u4F8B\u5982: ccem setup perms --dev"));
2426
+ console.log(chalk7.gray("\u53EF\u7528\u6A21\u5F0F: " + PERMISSION_MODES.join(", ")));
2427
+ console.log(chalk7.gray("\u91CD\u7F6E\u6743\u9650: ccem setup perms --reset"));
2428
+ });
2429
+ setupCmd.command("default-mode").description("\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F").option("--yolo", "YOLO \u6A21\u5F0F").option("--dev", "\u5F00\u53D1\u6A21\u5F0F").option("--readonly", "\u53EA\u8BFB\u6A21\u5F0F").option("--safe", "\u5B89\u5168\u6A21\u5F0F").option("--ci", "CI/CD \u6A21\u5F0F").option("--audit", "\u5BA1\u8BA1\u6A21\u5F0F").option("--reset", "\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F").action(function() {
2430
+ const options = this.opts();
2431
+ if (options.reset) {
2432
+ config2.set("defaultMode", null);
2433
+ console.log(chalk7.green("\u5DF2\u6E05\u9664\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F"));
2434
+ return;
2435
+ }
2436
+ for (const mode of PERMISSION_MODES) {
2437
+ if (options[mode]) {
2438
+ config2.set("defaultMode", mode);
2439
+ console.log(chalk7.green(`\u5DF2\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F: ${PERMISSION_PRESETS4[mode].name}`));
2440
+ console.log(chalk7.gray(`\u4E0B\u6B21\u542F\u52A8 ccem \u65F6\u5C06\u9ED8\u8BA4\u4F7F\u7528\u6B64\u6A21\u5F0F`));
2441
+ return;
2442
+ }
2443
+ }
2444
+ const currentDefault = config2.get("defaultMode");
2445
+ if (currentDefault && PERMISSION_PRESETS4[currentDefault]) {
2446
+ console.log(chalk7.green(`\u5F53\u524D\u9ED8\u8BA4\u6A21\u5F0F: ${PERMISSION_PRESETS4[currentDefault].name}`));
2447
+ } else {
2448
+ console.log(chalk7.yellow("\u672A\u8BBE\u7F6E\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F"));
2449
+ }
2450
+ console.log(chalk7.gray("\n\u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --dev"));
2451
+ console.log(chalk7.gray("\u6E05\u9664\u9ED8\u8BA4\u6A21\u5F0F: ccem setup default-mode --reset"));
2452
+ console.log(chalk7.gray("\u53EF\u7528\u6A21\u5F0F: " + PERMISSION_MODES.join(", ")));
2453
+ });
2454
+ setupCmd.command("init").description("\u521D\u59CB\u5316 Claude Code \u5168\u5C40\u914D\u7F6E\uFF08\u8DF3\u8FC7 onboarding\u3001\u7981\u7528\u9065\u6D4B\u3001\u5B89\u88C5 MCP \u5DE5\u5177\uFF09").action(async () => {
2455
+ await runSetupInit();
2456
+ });
2457
+ setupCmd.command("migrate").description("\u8FC1\u79FB\u65E7\u7248\u914D\u7F6E\u5230 ~/.ccem/").option("--clean", "\u8FC1\u79FB\u540E\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6").option("--force", "\u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB\uFF08\u8986\u76D6\u73B0\u6709\u914D\u7F6E\uFF09").action(async function() {
2458
+ const options = this.opts();
2459
+ const newConfigPath = getCcemConfigPath();
2460
+ const legacyConfigPath = getLegacyConfigPath();
2461
+ console.log(chalk7.cyan("\n\u{1F504} \u914D\u7F6E\u8FC1\u79FB\n"));
2462
+ if (!fs7.existsSync(legacyConfigPath)) {
2463
+ console.log(chalk7.yellow("\u672A\u627E\u5230\u65E7\u7248\u914D\u7F6E\u6587\u4EF6"));
2464
+ console.log(chalk7.gray(` \u65E7\u8DEF\u5F84: ${legacyConfigPath}`));
2465
+ return;
2466
+ }
2467
+ if (fs7.existsSync(newConfigPath) && !options.force) {
2468
+ console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u5728\u65B0\u8DEF\u5F84"));
2469
+ console.log(chalk7.gray(` \u8DEF\u5F84: ${newConfigPath}`));
2470
+ console.log(chalk7.gray("\n\u4F7F\u7528 --force \u5F3A\u5236\u91CD\u65B0\u8FC1\u79FB"));
2471
+ return;
2472
+ }
2473
+ try {
2474
+ ensureCcemDir3();
2475
+ fs7.copyFileSync(legacyConfigPath, newConfigPath);
2476
+ console.log(chalk7.green("\u2713 \u914D\u7F6E\u5DF2\u8FC1\u79FB"));
2477
+ console.log(chalk7.gray(` \u4ECE: ${legacyConfigPath}`));
2478
+ console.log(chalk7.gray(` \u5230: ${newConfigPath}`));
2479
+ if (options.clean) {
2480
+ fs7.unlinkSync(legacyConfigPath);
2481
+ const legacyDir = path5.dirname(legacyConfigPath);
2482
+ try {
2483
+ fs7.rmdirSync(legacyDir);
2484
+ } catch {
2485
+ }
2486
+ console.log(chalk7.green("\u2713 \u5DF2\u5220\u9664\u65E7\u914D\u7F6E\u6587\u4EF6"));
2487
+ }
2488
+ } catch (err) {
2489
+ console.error(chalk7.red(`\u2717 \u8FC1\u79FB\u5931\u8D25: ${err}`));
2490
+ }
2491
+ });
2492
+ setupCmd.command("cron").description("\u5B89\u88C5 ccem-cron skill \u5230 Claude Code\uFF08~/.claude/skills/\uFF09").option("--force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u6709\u6587\u4EF6").action(async function() {
2493
+ const options = this.opts();
2494
+ const skillDir = path5.join(process.env.HOME || "~", ".claude", "skills");
2495
+ const targetPath = path5.join(skillDir, "ccem-cron.md");
2496
+ if (!fs7.existsSync(skillDir)) {
2497
+ fs7.mkdirSync(skillDir, { recursive: true });
2498
+ console.log(chalk7.gray(`\u521B\u5EFA\u76EE\u5F55: ${skillDir}`));
2499
+ }
2500
+ if (fs7.existsSync(targetPath) && !options.force) {
2501
+ const { overwrite } = await inquirer.prompt([
2502
+ {
2503
+ type: "confirm",
2504
+ name: "overwrite",
2505
+ message: `${targetPath} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
2506
+ default: false
2507
+ }
2508
+ ]);
2509
+ if (!overwrite) {
2510
+ console.log(chalk7.yellow("\u5DF2\u53D6\u6D88"));
2511
+ return;
2512
+ }
2513
+ }
2514
+ fs7.writeFileSync(targetPath, CCEM_CRON_SKILL_CONTENT, "utf-8");
2515
+ console.log(chalk7.green(`\u2713 \u5DF2\u5B89\u88C5 ccem-cron skill`));
2516
+ console.log(chalk7.gray(` \u8DEF\u5F84: ${targetPath}`));
2517
+ console.log(chalk7.cyan(`
2518
+ \u5728 Claude Code \u4E2D\u4F7F\u7528 /ccem-cron \u5373\u53EF\u8C03\u7528\u6B64 skill`));
2519
+ });
2520
+ var skillCmd = program.command("skill").description("\u7BA1\u7406 Claude Code skills");
2521
+ skillCmd.command("add [url]").description("\u6DFB\u52A0 skill\uFF08\u4ECE\u5B98\u65B9\u9884\u8BBE\u6216 GitHub URL\uFF09").action(async (url) => {
2522
+ if (url) {
2523
+ addSkillFromGitHub(url);
2524
+ } else {
2525
+ const result = await runSkillSelector();
2526
+ if (result.type === "cancelled") {
2527
+ console.log(chalk7.yellow("\u5DF2\u53D6\u6D88"));
2528
+ return;
2529
+ }
2530
+ if (result.type === "custom") {
2531
+ const { customUrl } = await inquirer.prompt([
2532
+ {
2533
+ type: "input",
2534
+ name: "customUrl",
2535
+ message: "\u8F93\u5165 GitHub URL:",
2536
+ validate: (input) => {
2537
+ if (!input.trim()) return "\u8BF7\u8F93\u5165\u6709\u6548\u7684 URL";
2538
+ if (!input.includes("github.com") && !/^[\w-]+\/[\w-]+$/.test(input)) {
2539
+ return "\u8BF7\u8F93\u5165\u6709\u6548\u7684 GitHub URL \u6216 owner/repo \u683C\u5F0F";
2540
+ }
2541
+ return true;
2542
+ }
2543
+ }
2544
+ ]);
2545
+ addSkillFromGitHub(customUrl);
2546
+ } else if (result.skill) {
2547
+ installSkill(result.skill);
2548
+ }
2549
+ }
2550
+ });
2551
+ skillCmd.command("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 skills").action(() => {
2552
+ const skills = listInstalledSkills();
2553
+ if (skills.length === 0) {
2554
+ console.log(chalk7.yellow("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709\u5B89\u88C5\u4EFB\u4F55 skill"));
2555
+ console.log(chalk7.gray("\u4F7F\u7528 ccem skill add \u6DFB\u52A0 skills"));
2556
+ return;
2557
+ }
2558
+ const table = new Table3({
2559
+ head: ["Name", "Path"],
2560
+ style: { head: ["cyan"] }
2561
+ });
2562
+ skills.forEach((skill) => {
2563
+ table.push([chalk7.green(skill.name), chalk7.gray(skill.path)]);
2564
+ });
2565
+ console.log(table.toString());
2566
+ });
2567
+ skillCmd.command("rm <name>").description("\u5220\u9664\u5DF2\u5B89\u88C5\u7684 skill").action((name) => {
2568
+ removeSkill(name);
2569
+ });
2570
+ program.command("load <url>").description("\u4ECE\u8FDC\u7A0B\u670D\u52A1\u5668\u52A0\u8F7D\u73AF\u5883\u914D\u7F6E").requiredOption("--secret <secret>", "\u89E3\u5BC6\u5BC6\u94A5").action(async (url, options) => {
2571
+ await loadFromRemote(url, options.secret);
2572
+ });
2573
+ program.command("launch").description(false).option("--env <name>", "\u73AF\u5883\u540D\u79F0").option("--perm <mode>", "\u6743\u9650\u6A21\u5F0F").option("--session-id <id>", "\u4F1A\u8BDD ID").option("--resume-session <id>", "\u6062\u590D\u4F1A\u8BDD ID").option("--working-dir <path>", "\u5DE5\u4F5C\u76EE\u5F55").action(async function() {
2574
+ const opts = this.opts();
2575
+ const envName = opts.env || config2.get("current");
2576
+ const registries = config2.get("registries");
2577
+ const envConfig = registries[envName];
2578
+ if (!envConfig) {
2579
+ console.error(chalk7.red(`Environment '${envName}' not found.`));
2580
+ process.exit(1);
2581
+ }
2582
+ await launchClaude({
2583
+ envConfig,
2584
+ permMode: opts.perm,
2585
+ workingDir: opts.workingDir,
2586
+ sessionId: opts.sessionId,
2587
+ resumeSessionId: opts.resumeSession,
2588
+ silent: true
2589
+ });
2590
+ });
2591
+ program.command("usage").description("\u663E\u793A\u4F7F\u7528\u7EDF\u8BA1\u5E76\u5237\u65B0\u7F13\u5B58").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action(async function() {
2592
+ const opts = this.opts();
2593
+ const stats = await getUsageStats();
2594
+ if (opts.json) {
2595
+ console.log(JSON.stringify(stats, null, 2));
2596
+ } else {
2597
+ if (stats) {
2598
+ console.log(renderUsageDetail(stats));
2599
+ } else {
2600
+ console.log(chalk7.yellow("No usage data available"));
2601
+ }
2602
+ }
2603
+ });
2604
+ program.action(async (options) => {
2605
+ if (options.mode) {
2606
+ showCurrentMode();
2607
+ return;
2608
+ }
2609
+ if (options.listModes) {
2610
+ listAvailableModes();
2611
+ return;
2612
+ }
2613
+ initUsageStats();
2614
+ while (true) {
2615
+ console.clear();
2616
+ showCurrentEnv(usageStats, usageLoading);
2617
+ console.log("");
2618
+ const defaultMode = config2.get("defaultMode");
2619
+ const registries = config2.get("registries");
2620
+ const current = config2.get("current");
2621
+ const envConfig = registries[current];
2622
+ const { action } = await inquirer.prompt([
2623
+ {
2624
+ type: "list",
2625
+ name: "action",
2626
+ message: chalk7.gray("Select action"),
2627
+ choices: getMainMenuChoices(defaultMode),
2628
+ prefix: chalk7.gray("?")
2629
+ }
2630
+ ]);
2631
+ if (action === "start") {
2632
+ if (usageAbortController) {
2633
+ usageAbortController.abort();
2634
+ usageAbortController = null;
2635
+ }
2636
+ stopSpinner();
2637
+ if (!envConfig) {
2638
+ msg.error("No environment configuration found.");
2639
+ process.exit(1);
2640
+ }
2641
+ await launchClaude({ envConfig, permMode: defaultMode || void 0 });
2642
+ return;
2643
+ } else if (action === "usage") {
2644
+ console.clear();
2645
+ if (usageStats) {
2646
+ console.log(renderUsageDetail(usageStats));
2647
+ } else {
2648
+ msg.warning("No usage data available");
2649
+ }
2650
+ await inquirer.prompt([
2651
+ {
2652
+ type: "input",
2653
+ name: "continue",
2654
+ message: chalk7.gray("Press Enter to continue..."),
2655
+ prefix: ""
2656
+ }
2657
+ ]);
2658
+ } else if (action === "switch") {
2659
+ const result = await selectEnvWithKeys(registries, current);
2660
+ if (result.action === "select") {
2661
+ config2.set("current", result.name);
2662
+ } else if (result.action === "edit") {
2663
+ const envToEdit = registries[result.name];
2664
+ console.log(chalk7.yellow(`
2665
+ Editing environment '${result.name}'`));
2666
+ const answers = await inquirer.prompt([
2667
+ {
2668
+ type: "input",
2669
+ name: "ANTHROPIC_BASE_URL",
2670
+ message: "ANTHROPIC_BASE_URL:",
2671
+ default: envToEdit.ANTHROPIC_BASE_URL
2672
+ },
2673
+ {
2674
+ type: "password",
2675
+ name: "ANTHROPIC_API_KEY",
2676
+ message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2677
+ },
2678
+ {
2679
+ type: "input",
2680
+ name: "ANTHROPIC_MODEL",
2681
+ message: "ANTHROPIC_MODEL:",
2682
+ default: envToEdit.ANTHROPIC_MODEL
2683
+ },
2684
+ {
2685
+ type: "input",
2686
+ name: "ANTHROPIC_SMALL_FAST_MODEL",
2687
+ message: "ANTHROPIC_SMALL_FAST_MODEL:",
2688
+ default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
2689
+ }
2690
+ ]);
2691
+ if (answers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = answers.ANTHROPIC_BASE_URL;
2692
+ if (answers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt2(answers.ANTHROPIC_API_KEY);
2693
+ if (answers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = answers.ANTHROPIC_MODEL;
2694
+ if (answers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = answers.ANTHROPIC_SMALL_FAST_MODEL;
2695
+ registries[result.name] = envToEdit;
2696
+ config2.set("registries", registries);
2697
+ msg.success(`Environment '${result.name}' updated.`);
2698
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2699
+ } else if (result.action === "rename") {
2700
+ if (result.name === "official") {
2701
+ msg.error("Cannot rename default 'official' environment.");
2702
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2703
+ } else {
2704
+ const { newName } = await inquirer.prompt([
2705
+ {
2706
+ type: "input",
2707
+ name: "newName",
2708
+ message: `Rename '${result.name}' to:`,
2709
+ validate: (input) => {
2710
+ if (!input.trim()) return "Name cannot be empty";
2711
+ if (registries[input]) return `Environment '${input}' already exists`;
2712
+ return true;
2713
+ }
2714
+ }
2715
+ ]);
2716
+ registries[newName] = registries[result.name];
2717
+ delete registries[result.name];
2718
+ config2.set("registries", registries);
2719
+ if (current === result.name) {
2720
+ config2.set("current", newName);
2721
+ }
2722
+ msg.success(`Environment '${result.name}' renamed to '${newName}'.`);
2723
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2724
+ }
2725
+ } else if (result.action === "copy") {
2726
+ const { targetName } = await inquirer.prompt([
2727
+ {
2728
+ type: "input",
2729
+ name: "targetName",
2730
+ message: `Copy '${result.name}' to:`,
2731
+ validate: (input) => {
2732
+ if (!input.trim()) return "Name cannot be empty";
2733
+ if (registries[input]) return `Environment '${input}' already exists`;
2734
+ return true;
2735
+ }
2736
+ }
2737
+ ]);
2738
+ registries[targetName] = { ...registries[result.name] };
2739
+ config2.set("registries", registries);
2740
+ msg.success(`Environment '${result.name}' copied to '${targetName}'.`);
2741
+ const { modify } = await inquirer.prompt([
2742
+ {
2743
+ type: "confirm",
2744
+ name: "modify",
2745
+ message: "Do you want to modify the configuration?",
2746
+ default: false
2747
+ }
2748
+ ]);
2749
+ if (modify) {
2750
+ const envToEdit = registries[targetName];
2751
+ const editAnswers = await inquirer.prompt([
2752
+ {
2753
+ type: "input",
2754
+ name: "ANTHROPIC_BASE_URL",
2755
+ message: "ANTHROPIC_BASE_URL:",
2756
+ default: envToEdit.ANTHROPIC_BASE_URL
2757
+ },
2758
+ {
2759
+ type: "password",
2760
+ name: "ANTHROPIC_API_KEY",
2761
+ message: "ANTHROPIC_API_KEY (leave empty to keep current):"
2762
+ },
2763
+ {
2764
+ type: "input",
2765
+ name: "ANTHROPIC_MODEL",
2766
+ message: "ANTHROPIC_MODEL:",
2767
+ default: envToEdit.ANTHROPIC_MODEL
2768
+ },
2769
+ {
2770
+ type: "input",
2771
+ name: "ANTHROPIC_SMALL_FAST_MODEL",
2772
+ message: "ANTHROPIC_SMALL_FAST_MODEL:",
2773
+ default: envToEdit.ANTHROPIC_SMALL_FAST_MODEL
2774
+ }
2775
+ ]);
2776
+ if (editAnswers.ANTHROPIC_BASE_URL) envToEdit.ANTHROPIC_BASE_URL = editAnswers.ANTHROPIC_BASE_URL;
2777
+ if (editAnswers.ANTHROPIC_API_KEY) envToEdit.ANTHROPIC_API_KEY = encrypt2(editAnswers.ANTHROPIC_API_KEY);
2778
+ if (editAnswers.ANTHROPIC_MODEL) envToEdit.ANTHROPIC_MODEL = editAnswers.ANTHROPIC_MODEL;
2779
+ if (editAnswers.ANTHROPIC_SMALL_FAST_MODEL) envToEdit.ANTHROPIC_SMALL_FAST_MODEL = editAnswers.ANTHROPIC_SMALL_FAST_MODEL;
2780
+ registries[targetName] = envToEdit;
2781
+ config2.set("registries", registries);
2782
+ msg.success(`Environment '${targetName}' updated.`);
2783
+ }
2784
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2785
+ } else if (result.action === "delete") {
2786
+ if (result.name === "official") {
2787
+ msg.error("Cannot delete default 'official' environment.");
2788
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2789
+ } else {
2790
+ const { confirm } = await inquirer.prompt([
2791
+ {
2792
+ type: "confirm",
2793
+ name: "confirm",
2794
+ message: `Are you sure you want to delete '${result.name}'?`,
2795
+ default: false
2796
+ }
2797
+ ]);
2798
+ if (confirm) {
2799
+ delete registries[result.name];
2800
+ config2.set("registries", registries);
2801
+ if (current === result.name) {
2802
+ config2.set("current", "official");
2803
+ msg.warning(`Deleted current environment. Switched back to 'official'.`);
2804
+ } else {
2805
+ msg.success(`Environment '${result.name}' deleted.`);
2806
+ }
2807
+ }
2808
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2809
+ }
2810
+ }
2811
+ } else if (action === "perm") {
2812
+ const { permMode } = await inquirer.prompt([
2813
+ {
2814
+ type: "list",
2815
+ name: "permMode",
2816
+ message: chalk7.gray("Select permission mode"),
2817
+ choices: getPermModeChoices(null, false),
2818
+ prefix: chalk7.gray("?")
2819
+ }
2820
+ ]);
2821
+ if (permMode !== "back") {
2822
+ await runWithTempPermissions(permMode, envConfig);
2823
+ return;
2824
+ }
2825
+ } else if (action === "setDefault") {
2826
+ const currentDefault = config2.get("defaultMode");
2827
+ const { selectedMode } = await inquirer.prompt([
2828
+ {
2829
+ type: "list",
2830
+ name: "selectedMode",
2831
+ message: chalk7.gray("Set default permission mode"),
2832
+ choices: getPermModeChoices(currentDefault, true),
2833
+ prefix: chalk7.gray("?")
2834
+ }
2835
+ ]);
2836
+ if (selectedMode === "clear") {
2837
+ config2.set("defaultMode", null);
2838
+ msg.success("Default mode cleared");
2839
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2840
+ } else if (selectedMode !== "back") {
2841
+ config2.set("defaultMode", selectedMode);
2842
+ msg.success(`Default mode set: ${PERMISSION_PRESETS4[selectedMode].name}`);
2843
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
2844
+ }
2845
+ } else {
2846
+ process.exit(0);
2847
+ }
2848
+ }
2849
+ });
2850
+ program.parse(process.argv);