ccem 1.8.1 → 2.0.0-beta.10

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