tenicli 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +226 -256
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{createRequire as m0}from"node:module";var g0=Object.defineProperty;var u0=(Z)=>Z;function p0(Z,$){this[Z]=u0.bind(null,$)}var W0=(Z,$)=>{for(var Q in $)g0(Z,Q,{get:$[Q],enumerable:!0,configurable:!0,set:p0.bind($,Q)})};var M=(Z,$)=>()=>(Z&&($=Z(Z=0)),$);var r=m0(import.meta.url);import{existsSync as g,readFileSync as t,writeFileSync as d0,mkdirSync as l0}from"fs";import{join as C}from"path";function s(){let Z=process.env.HOME||process.env.USERPROFILE||"";return C(Z,".tenicli")}function o(){return C(s(),"config.json")}function u(){try{if(g(o()))return JSON.parse(t(o(),"utf8"))}catch{}return{}}function p(Z){let $=s();if(!g($))l0($,{recursive:!0});let Q=u(),V={...Q,...Z,keys:{...Q.keys,...Z.keys},baseUrls:{...Q.baseUrls,...Z.baseUrls}};d0(o(),JSON.stringify(V,null,2),"utf8")}function m(){let Z=process.cwd();Y0(C(Z,".tenicli.env")),Y0(C(Z,".env"));let $=u(),Q=process.env,V=Q.TENICLI_MODEL||$.activeModel||k[0].id,z=k.find((Y)=>Y.id===V)?.provider||Q.TENICLI_PROVIDER||"anthropic",X=a0(z,$,Q),G=z==="openai"?"https://api.openai.com":"https://api.anthropic.com",W=Q.TENICLI_BASE_URL||$.baseUrls?.[z]||G;return{provider:{type:z,baseUrl:W,apiKey:X,model:V},maxTokens:parseInt(Q.TENICLI_MAX_TOKENS||"8192"),systemPrompt:i0(Z),cwd:Z}}function a0(Z,$,Q){if(Z==="anthropic")return Q.TENICLI_API_KEY||Q.ANTHROPIC_API_KEY||$.keys?.anthropic||"";if(Z==="openai")return Q.TENICLI_API_KEY||Q.OPENAI_API_KEY||$.keys?.openai||"";return Q.TENICLI_API_KEY||""}function Y0(Z){try{if(!g(Z))return;for(let $ of t(Z,"utf8").split(`
3
- `)){let Q=$.trim();if(!Q||Q.startsWith("#"))continue;let V=Q.indexOf("=");if(V===-1)continue;let J=Q.slice(0,V).trim(),z=Q.slice(V+1).trim();if(z.startsWith('"')&&z.endsWith('"')||z.startsWith("'")&&z.endsWith("'"))z=z.slice(1,-1);if(!process.env[J])process.env[J]=z}}catch{}}function i0(Z){for(let $ of[C(Z,"TENICLI.md"),C(s(),"TENICLI.md")])if(g($))return t($,"utf8");return c0}var k,c0=`You are TeniCLI, a fast AI coding assistant in the terminal.
2
+ import{createRequire as a0}from"node:module";var m0=Object.defineProperty;var d0=(Q)=>Q;function l0(Q,Z){this[Q]=d0.bind(null,Z)}var _0=(Q,Z)=>{for(var $ in Z)m0(Q,$,{get:Z[$],enumerable:!0,configurable:!0,set:l0.bind(Z,$)})};var P=(Q,Z)=>()=>(Q&&(Z=Q(Q=0)),Z);var r=a0(import.meta.url);import{existsSync as g,readFileSync as t,writeFileSync as i0,mkdirSync as c0}from"fs";import{join as C}from"path";function s(){let Q=process.env.HOME||process.env.USERPROFILE||"";return C(Q,".tenicli")}function o(){return C(s(),"config.json")}function u(){try{if(g(o()))return JSON.parse(t(o(),"utf8"))}catch{}return{}}function p(Q){let Z=s();if(!g(Z))c0(Z,{recursive:!0});let $=u(),V={...$,...Q,keys:{...$.keys,...Q.keys},baseUrls:{...$.baseUrls,...Q.baseUrls}};i0(o(),JSON.stringify(V,null,2),"utf8")}function m(){let Q=process.cwd();U0(C(Q,".tenicli.env")),U0(C(Q,".env"));let Z=u(),$=process.env,V=$.TENICLI_MODEL||Z.activeModel||k[0].id,Y=k.find((N)=>N.id===V)?.provider||$.TENICLI_PROVIDER||"anthropic",X=n0(Y,Z,$),z=Y==="openai"?"https://api.openai.com":"https://api.anthropic.com",J=$.TENICLI_BASE_URL||Z.baseUrls?.[Y]||z;return{provider:{type:Y,baseUrl:J,apiKey:X,model:V},maxTokens:parseInt($.TENICLI_MAX_TOKENS||"8192"),systemPrompt:r0(Q),cwd:Q}}function n0(Q,Z,$){if(Q==="anthropic")return $.TENICLI_API_KEY||$.ANTHROPIC_API_KEY||Z.keys?.anthropic||"";if(Q==="openai")return $.TENICLI_API_KEY||$.OPENAI_API_KEY||Z.keys?.openai||"";return $.TENICLI_API_KEY||""}function U0(Q){try{if(!g(Q))return;for(let Z of t(Q,"utf8").split(`
3
+ `)){let $=Z.trim();if(!$||$.startsWith("#"))continue;let V=$.indexOf("=");if(V===-1)continue;let K=$.slice(0,V).trim(),Y=$.slice(V+1).trim();if(Y.startsWith('"')&&Y.endsWith('"')||Y.startsWith("'")&&Y.endsWith("'"))Y=Y.slice(1,-1);if(!process.env[K])process.env[K]=Y}}catch{}}function r0(Q){for(let Z of[C(Q,"TENICLI.md"),C(s(),"TENICLI.md")])if(g(Z))return t(Z,"utf8");return o0}var k,o0=`You are TeniCLI, a fast AI coding assistant in the terminal.
4
4
 
5
5
  TOOLS: read/write files, execute commands, search code, list directories.
6
6
 
@@ -9,42 +9,42 @@ RULES:
9
9
  - Use tools proactively — read before edit, verify after changes.
10
10
  - Ask before destructive operations (delete, overwrite).
11
11
  - The user may write in Vietnamese — respond in the same language they use.
12
- - Write production-quality code matching the project's style.`;var e=M(()=>{k=[{id:"claude-sonnet-4-20250514",name:"Claude Sonnet 4",provider:"anthropic",speed:"fast"},{id:"claude-haiku-3-5-20241022",name:"Claude Haiku 3.5",provider:"anthropic",speed:"fast"},{id:"claude-opus-4-20250514",name:"Claude Opus 4",provider:"anthropic",speed:"slow"},{id:"gpt-4o",name:"GPT-4o",provider:"openai",speed:"fast"},{id:"gpt-4o-mini",name:"GPT-4o Mini",provider:"openai",speed:"fast"},{id:"o3-mini",name:"o3-mini",provider:"openai",speed:"normal"}]});async function*Z0(Z,$,Q,V,J){if(Z.type==="openai")yield*o0(Z,$,Q,V,J);else yield*r0(Z,$,Q,V,J)}async function*r0(Z,$,Q,V,J){let z=`${Z.baseUrl.replace(/\/$/,"")}/v1/messages`,X={model:Z.model,max_tokens:J,system:Q,messages:$,stream:!0};if(V.length)X.tools=V;let G=await N0(z,X,{"anthropic-version":"2023-06-01","x-api-key":Z.apiKey,authorization:`Bearer ${Z.apiKey}`});for await(let W of _0(G))switch(W.type){case"message_start":if(W.message?.usage)yield{type:"usage",input:W.message.usage.input_tokens||0,output:0};break;case"content_block_start":if(W.content_block?.type==="text")yield{type:"text",text:""};else if(W.content_block?.type==="tool_use")yield{type:"tool_start",id:W.content_block.id,name:W.content_block.name};break;case"content_block_delta":if(W.delta?.type==="text_delta")yield{type:"text",text:W.delta.text};else if(W.delta?.type==="input_json_delta")yield{type:"tool_input",partial:W.delta.partial_json};break;case"content_block_stop":yield{type:"tool_end"};break;case"message_delta":if(W.usage)yield{type:"usage",input:0,output:W.usage.output_tokens||0};yield{type:"done",stopReason:W.delta?.stop_reason||"end_turn"};break}}async function*o0(Z,$,Q,V,J){let z=`${Z.baseUrl.replace(/\/$/,"")}/v1/chat/completions`,X=t0($,Q),G=V.map((_)=>({type:"function",function:{name:_.name,description:_.description,parameters:_.input_schema}})),W={model:Z.model,max_tokens:J,messages:X,stream:!0,stream_options:{include_usage:!0}};if(G.length)W.tools=G;let Y=await N0(z,W,{authorization:`Bearer ${Z.apiKey}`}),N=new Map;for await(let _ of _0(Y)){let U=_.choices?.[0];if(!U){if(_.usage)yield{type:"usage",input:_.usage.prompt_tokens||0,output:_.usage.completion_tokens||0};continue}let B=U.delta||{};if(B.content)yield{type:"text",text:B.content};if(B.tool_calls)for(let q of B.tool_calls){if(q.id)N.set(q.index,{id:q.id,name:q.function?.name||"",args:""}),yield{type:"tool_start",id:q.id,name:q.function?.name||""};if(q.function?.arguments){let R=N.get(q.index);if(R)R.args+=q.function.arguments;yield{type:"tool_input",partial:q.function.arguments}}}if(U.finish_reason){for(let[,q]of N)yield{type:"tool_end"};yield{type:"done",stopReason:U.finish_reason==="tool_calls"?"tool_use":U.finish_reason}}}}function t0(Z,$){let Q=[{role:"system",content:$}];for(let V of Z)if(V.role==="user")if(typeof V.content==="string")Q.push({role:"user",content:V.content});else{let J=V.content;for(let z of J)if(z.type==="tool_result")Q.push({role:"tool",tool_call_id:z.tool_use_id,content:z.content||""});else Q.push({role:"user",content:z.text||""})}else if(typeof V.content==="string")Q.push({role:"assistant",content:V.content});else{let J=V.content,z=J.filter((G)=>G.type==="tool_use"),X=J.filter((G)=>G.type==="text").map((G)=>G.text).join("");if(z.length)Q.push({role:"assistant",content:X||null,tool_calls:z.map((G)=>({id:G.id,type:"function",function:{name:G.name,arguments:JSON.stringify(G.input||{})}}))});else Q.push({role:"assistant",content:X})}return Q}async function N0(Z,$,Q){let V=await fetch(Z,{method:"POST",headers:{"content-type":"application/json",...Q},body:JSON.stringify($)});if(!V.ok){let J=await V.text();throw Error(`API ${V.status}: ${J.slice(0,300)}`)}return V}async function*_0(Z){let $=Z.body.getReader(),Q=new TextDecoder,V="";while(!0){let{done:J,value:z}=await $.read();if(J)break;V+=Q.decode(z,{stream:!0});let X=V.split(`
13
- `);V=X.pop();for(let G of X)if(G.startsWith("data: ")){let W=G.slice(6).trim();if(W==="[DONE]")return;try{yield JSON.parse(W)}catch{}}}}function s0(){let Z=[" ██ ██ ","██████████","███ ██ █","██████████","██████████"," ██ ██ ██ "],$={T:["█████"," █ "," █ "," █ "," █ "],E:["████ ","█ ","███ ","█ ","████ "],N:["█ █ ","██ █ ","█ ██ ","█ █ ","█ █ "],I:["███"," █ "," █ "," █ ","███"],space:[" "," "," "," "," "],C:[" ███","█ ","█ ","█ "," ███"],L:["█ ","█ ","█ ","█ ","████"]},Q=[$.T,$.E,$.N,$.I,$.space,$.C,$.L,$.I],V=["","","","",""];for(let X=0;X<5;X++)V[X]=Q.map((G)=>G[X]).join(" ");let J=Z.map((X)=>K.cyan(X.padEnd(14," "))),z=[" ".repeat(V[0].length),...V].map((X)=>K.blue(X));return J.map((X,G)=>`${X} ${z[G]||""}`).join(`
14
- `)}function l(Z,$=60){let Q=(V,J)=>{let z=V.replace(/\x1b\[[0-9;]*m/g,""),X=J-z.length;return X>0?V+" ".repeat(X):V};console.log(K.gray(` ${j.tl}${j.line($)}${j.tr}`));for(let V of Z)console.log(K.gray(` ${j.v}`)+` ${Q(V,$-2)} `+K.gray(j.v));console.log(K.gray(` ${j.bl}${j.line($)}${j.br}`))}function q0(Z="0.0.0"){console.clear(),console.log(),console.log(s0()),console.log(),l([K.gray("type to chat")+` ${A.dot} `+K.gray("/help for commands")+` ${A.dot} `+K.gray(`v${Z}`)],60),console.log()}class a{i=0;timer=null;msg;constructor(Z="Thinking"){this.msg=Z}start(){return this.timer=setInterval(()=>{process.stdout.write(`\x1B[2K\r ${K.blue(A.spinner[this.i%A.spinner.length])} ${K.gray(this.msg)}`),this.i++},80),this}stop(){if(this.timer)clearInterval(this.timer),this.timer=null;process.stdout.write("\x1B[2K\r")}}function P(Z,$=!1){return new Promise((Q,V)=>{if(process.stdout.write(Z),!$||!process.stdin.isTTY){let q="",R=(O)=>{let D=typeof O==="string"?O:O.toString("utf8");if(D.charCodeAt(0)===3)process.stdout.write(`
15
- `),process.exit(0);if(D.charCodeAt(0)===4){E(),V(Error("EOF"));return}q+=D;let H=q.indexOf(`
16
- `);if(H!==-1)E(),Q(q.slice(0,H).replace(/\r$/,""))},E=()=>{process.stdin.removeListener("data",R)};if(!process.stdin.readableEncoding)process.stdin.setEncoding("utf8");process.stdin.on("data",R),process.stdin.resume();return}let J="",z=0,X=0,G=process.stdin.isRaw;if(process.stdin.setRawMode(!0),!process.stdin.readableEncoding)process.stdin.setEncoding("utf8");process.stdin.resume();let W=()=>{if(!J.startsWith("/")||J.length<1)return[];return e0.filter((q)=>q.cmd.startsWith(J))},Y=()=>{if(z>0){for(let q=0;q<z;q++)process.stdout.write("\x1B[1B"),process.stdout.write("\x1B[2K");process.stdout.write(`\x1B[${z}A`),z=0}process.stdout.write("\x1B[0K")},N=()=>{Y();let q=W();if(q.length===0)return;if(X>=q.length)X=q.length-1;if(X<0)X=0;process.stdout.write("\x1B[s");for(let R=0;R<q.length;R++){process.stdout.write(`
17
- \x1B[2K`);let E=q[R];if(R===X)process.stdout.write(` ${K.blue(K.bold(E.cmd))} ${K.gray(E.desc)}`);else process.stdout.write(` ${K.gray(E.cmd)} ${K.gray(K.dim(E.desc))}`)}z=q.length,process.stdout.write("\x1B[u")},_="",U=(q)=>{let R=typeof q==="string"?q:q.toString("utf8");for(let E=0;E<R.length;E++){let O=R[E],D=O.charCodeAt(0);if(_.length>0||D===27){if(_+=O,_.length===1)continue;if(_.length===2&&_[1]==="[")continue;if(_.length>=3){let H=W();if(_==="\x1B[A"&&H.length>0)X=(X-1+H.length)%H.length,N();else if(_==="\x1B[B"&&H.length>0)X=(X+1)%H.length,N();_="";continue}continue}if(D===3)Y(),process.stdin.setRawMode(G),process.stdout.write(`
18
- `),process.exit(0);if(D===4){Y(),process.stdin.setRawMode(G),B(),V(Error("EOF"));return}if(D===13||D===10){let H=W();if(H.length>0&&J!==H[X].cmd){Y();let f=H[X].cmd;process.stdout.write("\b \b".repeat(J.length)),J=f,process.stdout.write(J)}Y(),process.stdin.setRawMode(G),process.stdout.write(`
19
- `),B(),Q(J);return}if(D===127||D===8){if(J.length>0)Y(),J=J.slice(0,-1),process.stdout.write("\b \b"),X=0,N();continue}if(D===9){let H=W();if(H.length>0){Y();let f=H[X].cmd,f0=f.slice(J.length);J=f,process.stdout.write(f0),N()}continue}if(D<32)continue;Y(),J+=O,process.stdout.write(O),X=0,N()}},B=()=>{process.stdin.removeListener("data",U)};process.stdin.on("data",U)})}async function A0(){let Z=[],$=!0;while(!0){let Q=$?`
20
- ${K.gray(j.tl+j.line(3))} ${A.prompt} `:` ${K.gray(j.v)} `,V=await P(Q,$);if($=!1,V.endsWith("\\"))Z.push(V.slice(0,-1));else{Z.push(V);break}}return Z.join(`
21
- `)}async function x(Z,$){console.log(`
22
- ${K.bold(Z)}`),$.forEach((Q,V)=>{let J=K.blue(` ${V+1}.`),z=Q.desc?K.gray(` (${Q.desc})`):"";console.log(`${J} ${Q.label}${z}`)}),console.log(` ${K.gray(" 0. Cancel")}`);while(!0){let V=(await P(`
23
- ${K.gray("choose")} ${K.blue("❯")} `)).trim().toLowerCase();if(V==="0"||V==="q"||V==="cancel"||V==="exit"||V==="")return-1;let J=parseInt(V);if(J>=1&&J<=$.length)return J-1;console.log(` ${A.warn} enter 1-${$.length} or 0 to cancel`)}}function T(Z,$){console.log(`
24
- ${A.tool} ${K.yellow(Z)} ${K.gray($)}`)}function L(Z){console.error(` ${A.err} ${K.pink(Z)}`)}var U0=(Z)=>`\x1B[${Z}m`,d=(Z,$)=>(Q)=>`${U0(Z)}${Q}${U0($)}`,F=(Z)=>($)=>`\x1B[38;5;${Z}m${$}\x1B[39m`,K,A,j,e0;var w=M(()=>{K={bold:d("1","22"),dim:d("2","22"),italic:d("3","23"),under:d("4","24"),blue:F(111),purple:F(141),green:F(149),yellow:F(179),pink:F(210),cyan:F(117),gray:F(60),text:F(146),orange:F(215)},A={prompt:K.blue("❯"),ai:K.purple("◆"),tool:K.yellow("⚙"),ok:K.green("✓"),err:K.pink("✗"),warn:K.yellow("⚠"),arrow:K.gray("→"),dot:K.gray("•"),spinner:["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]};j={h:"─",v:"│",tl:"╭",tr:"╮",bl:"╰",br:"╯",line:(Z)=>"─".repeat(Z)};e0=[{cmd:"/model",desc:"switch AI model"},{cmd:"/auth",desc:"set API key"},{cmd:"/mode",desc:"toggle mode"},{cmd:"/compact",desc:"toggle compact"},{cmd:"/diff",desc:"show file changes"},{cmd:"/undo",desc:"revert last change"},{cmd:"/init",desc:"init project context"},{cmd:"/remote",desc:"start web remote"},{cmd:"/history",desc:"past conversations"},{cmd:"/update",desc:"update tenicli"},{cmd:"/clear",desc:"clear screen"},{cmd:"/cost",desc:"show token usage"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit"}]});import{readFileSync as $0,writeFileSync as H0,existsSync as c,readdirSync as R0,statSync as D0,mkdirSync as Z1}from"fs";import{resolve as $1,relative as i,join as B0,dirname as Q1}from"path";import{spawn as P0}from"child_process";class O0{writes=[];recordWrite(Z,$){let Q=c(Z)?$0(Z,"utf8"):null;this.writes.push({path:Z,backup:Q,newLines:$.split(`
25
- `).length,time:new Date})}getChanges(){let Z=new Map;for(let $ of this.writes)Z.set($.path,{isNew:$.backup===null,lines:$.newLines,time:$.time});return Array.from(Z.entries()).map(([$,Q])=>({path:$,...Q}))}undo(){let Z=this.writes.pop();if(!Z)return null;if(Z.backup!==null)return H0(Z.path,Z.backup,"utf8"),{path:Z.path,restored:!0};else{try{r("fs").unlinkSync(Z.path)}catch{}return{path:Z.path,restored:!1}}}get count(){return this.writes.length}clear(){this.writes=[]}}async function F0(Z,$,Q){try{let V;switch(Z){case"read_file":V=G1($,Q),T("read_file",K.dim(i(Q,I($.path,Q))));break;case"write_file":V=K1($,Q),T("write_file",K.dim(i(Q,I($.path,Q))));break;case"list_dir":V=X1($,Q),T("list_dir",K.dim($.path||"."));break;case"search_files":V=await J1($,Q),T("search_files",K.dim(`"${$.pattern}"`));break;case"exec_command":V=await z1($,Q),T("exec_command",K.dim(E0($.command,60)));break;default:V=`Unknown tool: ${Z}`}return{type:"tool_result",content:E0(V,V1)}}catch(V){return{type:"tool_result",content:`Error: ${V.message}`,is_error:!0}}}function I(Z,$){return $1($,Z)}function G1(Z,$){let Q=I(Z.path,$);if(!c(Q))return`File not found: ${Z.path}`;let V=$0(Q,"utf8"),J=V.split(`
26
- `);if(Z.start_line||Z.end_line){let z=Math.max(1,Z.start_line||1)-1,X=Math.min(J.length,Z.end_line||J.length);return J.slice(z,X).map((G,W)=>`${z+W+1}: ${G}`).join(`
27
- `)}if(J.length>50)return J.map((z,X)=>`${X+1}: ${z}`).join(`
28
- `);return V}function K1(Z,$){let Q=I(Z.path,$),V=Q1(Q);if(!c(V))Z1(V,{recursive:!0});return y.recordWrite(Q,Z.content),H0(Q,Z.content,"utf8"),`Written ${Z.content.split(`
29
- `).length} lines to ${Z.path}`}function X1(Z,$){let Q=I(Z.path||".",$);if(!c(Q))return`Directory not found: ${Z.path||"."}`;let V=Z.depth||1,J=[];function z(X,G){if(G>V)return;try{let W=R0(X);for(let Y of W){if(Y.startsWith(".")||Y==="node_modules")continue;let N=B0(X,Y),_=i(Q,N);try{let U=D0(N),B=" ".repeat(G);if(U.isDirectory())J.push(`${B}${_}/`),z(N,G+1);else{let q=U.size>1024?`${(U.size/1024).toFixed(1)}KB`:`${U.size}B`;J.push(`${B}${_} (${q})`)}}catch{}}}catch{}}return z(Q,0),J.length>0?J.join(`
30
- `):"(empty directory)"}async function J1(Z,$){let Q=I(Z.path||".",$),V=Z.pattern;try{let X=["-n","--max-count=50","--no-heading"];if(Z.include)X.push("--glob",Z.include);return X.push(V,Q),await new Promise((W,Y)=>{let N=P0("rg",X,{shell:!0}),_="";N.stdout.on("data",(U)=>_+=U.toString()),N.on("close",(U)=>{if(U===0||U===1)W(_.trim()||"No matches found.");else Y(Error("rg failed"))}),N.on("error",Y)})}catch{}let J=[];function z(X){try{for(let G of R0(X)){if(G.startsWith(".")||G==="node_modules")continue;let W=B0(X,G);try{let Y=D0(W);if(Y.isDirectory()){z(W);continue}if(Y.size>500000)continue;if(Z.include&&!W1(G,Z.include))continue;let _=$0(W,"utf8").split(`
31
- `);for(let U=0;U<_.length;U++)if(_[U].includes(V)){if(J.push(`${i($,W)}:${U+1}: ${_[U].trim()}`),J.length>=50)return}}catch{}}}catch{}}return z(Q),J.length>0?J.join(`
32
- `):"No matches found."}async function z1(Z,$){let Q=I(Z.cwd||".",$),V=process.platform==="win32",J=V?"cmd":"sh",z=V?"/c":"-c";return new Promise((X)=>{let G=P0(J,[z,Z.command],{cwd:Q,env:{...process.env,PAGER:"cat"}}),W="",Y="";G.stdout.on("data",(_)=>W+=_.toString()),G.stderr.on("data",(_)=>Y+=_.toString());let N=setTimeout(()=>G.kill(),30000);G.on("close",(_)=>{clearTimeout(N);let U="";if(W.trim())U+=W.trim();if(Y.trim())U+=(U?`
33
- `:"")+`[stderr] ${Y.trim()}`;U+=`
34
- [exit code: ${_}]`,X(U)}),G.on("error",(_)=>{clearTimeout(N),X(`[error] Failed to start process: ${_.message}`)})})}function E0(Z,$){if(Z.length<=$)return Z;return Z.slice(0,$)+`
35
- ... (truncated, ${Z.length-$} chars omitted)`}function W1(Z,$){if($.startsWith("*."))return Z.endsWith($.slice(1));return Z.includes($.replace(/\*/g,""))}var y,j0,V1=30000;var Q0=M(()=>{w();y=new O0,j0=[{name:"read_file",description:"Read contents of a file. Returns the file text.",input_schema:{type:"object",properties:{path:{type:"string",description:"File path (relative to cwd or absolute)"},start_line:{type:"number",description:"Optional: start line (1-indexed)"},end_line:{type:"number",description:"Optional: end line (1-indexed, inclusive)"}},required:["path"]}},{name:"write_file",description:"Write content to a file. Creates parent directories if needed.",input_schema:{type:"object",properties:{path:{type:"string",description:"File path"},content:{type:"string",description:"Full file content to write"}},required:["path","content"]}},{name:"list_dir",description:"List files and directories in a path. Returns names with type indicators.",input_schema:{type:"object",properties:{path:{type:"string",description:"Directory path (default: cwd)"},depth:{type:"number",description:"Max depth (default: 1)"}},required:[]}},{name:"search_files",description:"Search for text in files using pattern matching (like grep). Returns matching lines with file paths.",input_schema:{type:"object",properties:{pattern:{type:"string",description:"Text or regex pattern to search for"},path:{type:"string",description:"Directory to search in (default: cwd)"},include:{type:"string",description:'Glob pattern to filter files, e.g. "*.ts"'}},required:["pattern"]}},{name:"exec_command",description:"Execute a shell command. Returns stdout and stderr.",input_schema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute"},cwd:{type:"string",description:"Working directory (default: project cwd)"}},required:["command"]}}]});class h{messages=[];tokens={input:0,output:0};cfg;autoMode=!1;onOutput;onConfirm;constructor(Z){this.cfg=Z}emit(Z){this.onOutput?.(Z)}write(Z){if(this.onOutput)this.emit({type:"text",text:Z});else process.stdout.write(Z)}log(Z){if(this.onOutput)this.emit({type:"text",text:Z+`
36
- `});else console.log(Z)}async send(Z){this.messages.push({role:"user",content:Z}),await this.agentLoop()}async agentLoop(){while(!0){let Z=await this.streamResponse();if(Z.stopReason==="error")break;if(Z.stopReason==="tool_use"){let Q=Z.content.filter((J)=>J.type==="tool_use"),V=[];for(let J of Q){if(!this.autoMode&&(J.name==="write_file"||J.name==="exec_command")){let X=J.name==="write_file"?J.input?.path:J.input?.command?.slice(0,80),G="y";if(this.onConfirm)G=await this.onConfirm(J.id,J.name,X||"");else this.log(`
37
- ${A.warn} ${K.yellow(J.name)} ${K.gray(X||"")}`),G=(await P(` ${K.gray("allow?")} ${K.blue("[y/n/auto]")} `)).trim().toLowerCase();if(G==="auto")this.autoMode=!0;else if(G!=="y"&&G!=="yes"&&G!==""){V.push({type:"tool_result",tool_use_id:J.id,content:"User denied this action.",is_error:!0});continue}}this.emit({type:"tool",name:J.name,detail:JSON.stringify(J.input||{}).slice(0,200)});let z=await F0(J.name,J.input,this.cfg.cwd);this.emit({type:"tool_result",name:J.name,content:(z.content||"").slice(0,500),is_error:!!z.is_error}),V.push({type:"tool_result",tool_use_id:J.id,content:z.content,is_error:z.is_error})}this.messages.push({role:"assistant",content:Z.content}),this.messages.push({role:"user",content:V});continue}let $=Z.content.filter((Q)=>Q.type==="text").map((Q)=>Q.text).join("");if($)this.messages.push({role:"assistant",content:$});break}if(this.emit({type:"tokens",input:this.tokens.input,output:this.tokens.output,messages:this.messages.length}),!this.onOutput)console.log(`
38
- ${K.gray(`tokens: ${this.tokens.input}↑ ${this.tokens.output}↓`)}`)}async streamResponse(){let Z=!this.onOutput,$=Z?new a("Thinking").start():null,Q=[],V="",J="",z="",X="",G="end_turn",W=!1;try{let Y=Z0(this.cfg.provider,this.messages,this.cfg.systemPrompt,j0,this.cfg.maxTokens);for await(let N of Y)switch(N.type){case"text":if(!W){if($?.stop(),W=!0,Z)process.stdout.write(`
39
- ${A.ai} `)}if(N.text)this.write(N.text);V+=N.text;break;case"tool_start":if(!W)$?.stop(),W=!0;if(V)Q.push({type:"text",text:V}),V="";J=N.id,z=N.name,X="";break;case"tool_input":X+=N.partial;break;case"tool_end":if(J){let _={};try{_=JSON.parse(X)}catch{}Q.push({type:"tool_use",id:J,name:z,input:_}),J="",X=""}break;case"usage":this.tokens.input+=N.input,this.tokens.output+=N.output;break;case"done":G=N.stopReason;break}}catch(Y){if($?.stop(),Z)console.log();let N=Y.message||String(Y);if(this.emit({type:"error",message:N}),Z)L(N);return{content:[],stopReason:"error"}}if(V){if(Q.push({type:"text",text:V}),this.emit({type:"text_done"}),Z)process.stdout.write(`
40
- `)}if(!W)$?.stop();return{content:Q,stopReason:G}}async compact(){if(this.messages.length<4){this.log(` ${A.warn} Not enough messages to compact.`);return}let Z=!this.onOutput?new a("Compacting").start():null;try{let $="";for(let X of this.messages)if(typeof X.content==="string")$+=`${X.role}: ${X.content.slice(0,500)}
41
- `;else{let G=X.content.filter((Y)=>Y.type==="text").map((Y)=>Y.text?.slice(0,300)).join(" ");if(G)$+=`${X.role}: ${G}
42
- `;let W=X.content.filter((Y)=>Y.type==="tool_use").map((Y)=>`[tool: ${Y.name}]`).join(", ");if(W)$+=` tools: ${W}
43
- `}let Q=this.messages.length,V=`Summarize this conversation concisely. Keep key decisions, file changes, and current state. Be brief:
44
-
45
- ${$.slice(0,6000)}`;this.messages=[{role:"user",content:V},{role:"assistant",content:`[Conversation compacted from ${Q} messages. Summary of what happened:]`}];let J=Z0(this.cfg.provider,[{role:"user",content:V}],"You are a conversation summarizer. Create a brief summary preserving key facts, decisions, and file changes.",[],this.cfg.maxTokens),z="";for await(let X of J){if(X.type==="text"&&X.text)z+=X.text;if(X.type==="usage")this.tokens.input+=X.input,this.tokens.output+=X.output}this.messages=[{role:"user",content:`[Previous conversation summary]
46
- ${z}`},{role:"assistant",content:"Understood. I have the context from our previous conversation. How can I continue helping you?"}],Z?.stop(),this.log(` ${A.ok} Compacted ${Q} messages → 2 ${K.gray(`(saved ~${Math.round($.length/4)} tokens)`)}`)}catch($){Z?.stop();let Q=`Compact failed: ${$.message}`;if(this.emit({type:"error",message:Q}),!this.onOutput)L(Q)}}get stats(){return this.tokens}get messageCount(){return this.messages.length}clear(){this.messages=[],this.tokens={input:0,output:0}}exportState(){return{messages:this.messages,tokens:{...this.tokens},autoMode:this.autoMode}}importState(Z){if(Z.messages)this.messages=Z.messages;if(Z.tokens)this.tokens=Z.tokens;if(Z.autoMode!==void 0)this.autoMode=Z.autoMode}getTitle(){let Z=this.messages.find(($)=>$.role==="user"&&typeof $.content==="string");if(Z)return Z.content.slice(0,50);return"New conversation"}}var V0=M(()=>{Q0();w()});var b;var K0=M(()=>{b={name:"tenicli",version:"0.2.1",description:"Lightweight AI coding CLI — fast, compact, multi-provider",type:"module",bin:{teni:"./dist/index.js"},files:["dist/","README.md","LICENSE"],scripts:{dev:"bun run src/index.ts","build:npm":"bun build src/index.ts --outfile dist/index.js --target node --minify",build:"bun build --compile --minify src/index.ts --outfile teni","build:win":"bun build --compile --minify --target=bun-windows-x64 src/index.ts --outfile teni.exe","build:linux":"bun build --compile --minify --target=bun-linux-x64 src/index.ts --outfile teni","build:mac":"bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile teni",prepublishOnly:"bun run build:npm"},keywords:["ai","cli","coding","agent","anthropic","openai","terminal"],author:"Yan Tenica",license:"MIT",repository:{type:"git",url:"https://github.com/Nhqvu2005/TeniCli.git"},homepage:"https://github.com/Nhqvu2005/TeniCli",engines:{node:">=18"},dependencies:{},devDependencies:{"@types/bun":"latest","@types/figlet":"^1.7.0",figlet:"^1.11.0"}}});var x0={};W0(x0,{renderQR:()=>A1,getLocalIPs:()=>E1});function A1(Z){let $=[],Q=Math.max(Z.length+4,30),V="██",J=" ";for(let X=0;X<7;X++){let G="";for(let W=0;W<7;W++)G+=X0.FINDER[X][W]?"██":" ";G+=" ";for(let W=0;W<Q-15;W++)G+=X===6&&W%2===0?"██":X===6?" ":" ";G+=" ";for(let W=0;W<7;W++)G+=X0.FINDER[X][W]?"██":" ";$.push(G)}$.push(" ".repeat(Q/2+1));let z=5;for(let X=0;X<z;X++){let G="";for(let W=0;W<Q+1;W++){if(W===6){G+=X%2===0?"██":" ";continue}G+=(W+X*3+W*X)%3===0?"██":" "}$.push(G)}$.push(" ".repeat(Q/2+1));for(let X=0;X<7;X++){let G="";for(let W=0;W<7;W++)G+=X0.FINDER[X][W]?"██":" ";G+=" ";for(let W=0;W<Q-14;W++)G+=(W+X*5)%3===0?"██":" ";$.push(G)}return $.map((X)=>" "+X).join(`
47
- `)}function E1(){try{let{networkInterfaces:Z}=r("os"),$=Z(),Q=[];for(let V of Object.keys($))for(let J of $[V])if(J.family==="IPv4"&&!J.internal)Q.push(J.address);return Q}catch{return[]}}var X0;var w0=M(()=>{X0={FINDER:[[1,1,1,1,1,1,1],[1,0,0,0,0,0,1],[1,0,1,1,1,0,1],[1,0,1,1,1,0,1],[1,0,1,1,1,0,1],[1,0,0,0,0,0,1],[1,1,1,1,1,1,1]]}});var J0={};W0(J0,{startServer:()=>F1});import{createServer as H1}from"http";import{createHash as R1}from"crypto";function D1(Z){if(Z.length<2)return null;let $=Z[0]&15,Q=(Z[1]&128)!==0,V=Z[1]&127,J=2;if(V===126){if(Z.length<4)return null;V=Z.readUInt16BE(2),J=4}else if(V===127){if(Z.length<10)return null;V=Number(Z.readBigUInt64BE(2)),J=10}if(Q){let z=Z.slice(J,J+4);J+=4;let X=Z.slice(J,J+V);for(let G=0;G<X.length;G++)X[G]^=z[G%4];return{opcode:$,payload:X}}return{opcode:$,payload:Z.slice(J,J+V)}}function B1(Z){let $=Buffer.from(Z,"utf8"),Q=$.length,V;if(Q<126)V=Buffer.alloc(2),V[0]=129,V[1]=Q;else if(Q<65536)V=Buffer.alloc(4),V[0]=129,V[1]=126,V.writeUInt16BE(Q,2);else V=Buffer.alloc(10),V[0]=129,V[1]=127,V.writeBigUInt64BE(BigInt(Q),2);return Buffer.concat([V,$])}function O1(Z){let $=Buffer.alloc(2);$[0]=137,$[1]=0,Z.write($)}function j1(Z){return`<!DOCTYPE html>
12
+ - Write production-quality code matching the project's style.`;var e=P(()=>{k=[{id:"claude-sonnet-4-20250514",name:"Claude Sonnet 4",provider:"anthropic",speed:"fast"},{id:"claude-haiku-3-5-20241022",name:"Claude Haiku 3.5",provider:"anthropic",speed:"fast"},{id:"claude-opus-4-20250514",name:"Claude Opus 4",provider:"anthropic",speed:"slow"},{id:"gpt-4o",name:"GPT-4o",provider:"openai",speed:"fast"},{id:"gpt-4o-mini",name:"GPT-4o Mini",provider:"openai",speed:"fast"},{id:"o3-mini",name:"o3-mini",provider:"openai",speed:"normal"}]});async function*Z0(Q,Z,$,V,K){if(Q.type==="openai")yield*s0(Q,Z,$,V,K);else yield*t0(Q,Z,$,V,K)}async function*t0(Q,Z,$,V,K){let Y=`${Q.baseUrl.replace(/\/$/,"")}/v1/messages`,X={model:Q.model,max_tokens:K,system:$,messages:Z,stream:!0};if(V.length)X.tools=V;let z=await q0(Y,X,{"anthropic-version":"2023-06-01","x-api-key":Q.apiKey,authorization:`Bearer ${Q.apiKey}`});for await(let J of E0(z))switch(J.type){case"message_start":if(J.message?.usage)yield{type:"usage",input:J.message.usage.input_tokens||0,output:0};break;case"content_block_start":if(J.content_block?.type==="text")yield{type:"text",text:""};else if(J.content_block?.type==="tool_use")yield{type:"tool_start",id:J.content_block.id,name:J.content_block.name};break;case"content_block_delta":if(J.delta?.type==="text_delta")yield{type:"text",text:J.delta.text};else if(J.delta?.type==="input_json_delta")yield{type:"tool_input",partial:J.delta.partial_json};break;case"content_block_stop":yield{type:"tool_end"};break;case"message_delta":if(J.usage)yield{type:"usage",input:0,output:J.usage.output_tokens||0};yield{type:"done",stopReason:J.delta?.stop_reason||"end_turn"};break}}async function*s0(Q,Z,$,V,K){let Y=`${Q.baseUrl.replace(/\/$/,"")}/v1/chat/completions`,X=e0(Z,$),z=V.map((_)=>({type:"function",function:{name:_.name,description:_.description,parameters:_.input_schema}})),J={model:Q.model,max_tokens:K,messages:X,stream:!0,stream_options:{include_usage:!0}};if(z.length)J.tools=z;let N=await q0(Y,J,{authorization:`Bearer ${Q.apiKey}`}),W=new Map;for await(let _ of E0(N)){let q=_.choices?.[0];if(!q){if(_.usage)yield{type:"usage",input:_.usage.prompt_tokens||0,output:_.usage.completion_tokens||0};continue}let A=q.delta||{};if(A.content)yield{type:"text",text:A.content};if(A.tool_calls)for(let U of A.tool_calls){if(U.id)W.set(U.index,{id:U.id,name:U.function?.name||"",args:""}),yield{type:"tool_start",id:U.id,name:U.function?.name||""};if(U.function?.arguments){let D=W.get(U.index);if(D)D.args+=U.function.arguments;yield{type:"tool_input",partial:U.function.arguments}}}if(q.finish_reason){for(let[,U]of W)yield{type:"tool_end"};yield{type:"done",stopReason:q.finish_reason==="tool_calls"?"tool_use":q.finish_reason}}}}function e0(Q,Z){let $=[{role:"system",content:Z}];for(let V of Q)if(V.role==="user")if(typeof V.content==="string")$.push({role:"user",content:V.content});else{let K=V.content;for(let Y of K)if(Y.type==="tool_result")$.push({role:"tool",tool_call_id:Y.tool_use_id,content:Y.content||""});else $.push({role:"user",content:Y.text||""})}else if(typeof V.content==="string")$.push({role:"assistant",content:V.content});else{let K=V.content,Y=K.filter((z)=>z.type==="tool_use"),X=K.filter((z)=>z.type==="text").map((z)=>z.text).join("");if(Y.length)$.push({role:"assistant",content:X||null,tool_calls:Y.map((z)=>({id:z.id,type:"function",function:{name:z.name,arguments:JSON.stringify(z.input||{})}}))});else $.push({role:"assistant",content:X})}return $}async function q0(Q,Z,$){let V=await fetch(Q,{method:"POST",headers:{"content-type":"application/json",...$},body:JSON.stringify(Z)});if(!V.ok){let H=await V.text();throw Error(`API ${V.status}: ${H.slice(0,300)}`)}let K={},Y=V.headers.get("anthropic-ratelimit-requests-limit");if(Y)K.requestsLimit=parseInt(Y);let X=V.headers.get("anthropic-ratelimit-requests-remaining");if(X)K.requestsRemaining=parseInt(X);let z=V.headers.get("anthropic-ratelimit-requests-reset");if(z)K.requestsReset=z;let J=V.headers.get("anthropic-ratelimit-tokens-limit");if(J)K.tokensLimit=parseInt(J);let N=V.headers.get("anthropic-ratelimit-tokens-remaining");if(N)K.tokensRemaining=parseInt(N);let W=V.headers.get("anthropic-ratelimit-tokens-reset");if(W)K.tokensReset=W;let _=V.headers.get("x-ratelimit-limit-requests");if(_)K.requestsLimit=parseInt(_);let q=V.headers.get("x-ratelimit-remaining-requests");if(q)K.requestsRemaining=parseInt(q);let A=V.headers.get("x-ratelimit-reset-requests");if(A)K.requestsReset=A;let U=V.headers.get("x-ratelimit-limit-tokens");if(U)K.tokensLimit=parseInt(U);let D=V.headers.get("x-ratelimit-remaining-tokens");if(D)K.tokensRemaining=parseInt(D);let R=V.headers.get("x-ratelimit-reset-tokens");if(R)K.tokensReset=R;if(Object.keys(K).length>0)Q0=K;return V}async function*E0(Q){let Z=Q.body.getReader(),$=new TextDecoder,V="";while(!0){let{done:K,value:Y}=await Z.read();if(K)break;V+=$.decode(Y,{stream:!0});let X=V.split(`
13
+ `);V=X.pop();for(let z of X)if(z.startsWith("data: ")){let J=z.slice(6).trim();if(J==="[DONE]")return;try{yield JSON.parse(J)}catch{}}}}var Q0;var $0=P(()=>{Q0={}});function Q1(){let Q=[" ██ ██ ","██████████","███ ██ █","██████████","██████████"," ██ ██ ██ "],Z={T:["█████"," █ "," █ "," █ "," █ "],E:["████ ","█ ","███ ","█ ","████ "],N:["█ █ ","██ █ ","█ ██ ","█ █ ","█ █ "],I:["███"," █ "," █ "," █ ","███"],space:[" "," "," "," "," "],C:[" ███","█ ","█ ","█ "," ███"],L:["█ ","█ ","█ ","█ ","████"]},$=[Z.T,Z.E,Z.N,Z.I,Z.space,Z.C,Z.L,Z.I],V=["","","","",""];for(let X=0;X<5;X++)V[X]=$.map((z)=>z[X]).join(" ");let K=Q.map((X)=>G.cyan(X.padEnd(14," "))),Y=[" ".repeat(V[0].length),...V].map((X)=>G.blue(X));return K.map((X,z)=>`${X} ${Y[z]||""}`).join(`
14
+ `)}function x(Q,Z=60){let $=(V,K)=>{let Y=V.replace(/\x1b\[[0-9;]*m/g,""),X=K-Y.length;return X>0?V+" ".repeat(X):V};console.log(G.gray(` ${j.tl}${j.line(Z)}${j.tr}`));for(let V of Q)console.log(G.gray(` ${j.v}`)+` ${$(V,Z-2)} `+G.gray(j.v));console.log(G.gray(` ${j.bl}${j.line(Z)}${j.br}`))}function H0(Q="0.0.0"){console.clear(),console.log(),console.log(Q1()),console.log(),x([G.gray("type to chat")+` ${E.dot} `+G.gray("/help for commands")+` ${E.dot} `+G.gray(`v${Q}`)],60),console.log()}class l{i=0;timer=null;msg;constructor(Q="Thinking"){this.msg=Q}start(){return this.timer=setInterval(()=>{process.stdout.write(`\x1B[2K\r ${G.blue(E.spinner[this.i%E.spinner.length])} ${G.gray(this.msg)}`),this.i++},80),this}stop(){if(this.timer)clearInterval(this.timer),this.timer=null;process.stdout.write("\x1B[2K\r")}}function L(Q,Z=!1){return new Promise(($,V)=>{if(process.stdout.write(Q),!Z||!process.stdin.isTTY){let U="",D=(H)=>{let B=typeof H==="string"?H:H.toString("utf8");if(B.charCodeAt(0)===3)process.stdout.write(`
15
+ `),process.exit(0);if(B.charCodeAt(0)===4){R(),V(Error("EOF"));return}U+=B;let O=U.indexOf(`
16
+ `);if(O!==-1)R(),$(U.slice(0,O).replace(/\r$/,""))},R=()=>{process.stdin.removeListener("data",D)};if(!process.stdin.readableEncoding)process.stdin.setEncoding("utf8");process.stdin.on("data",D),process.stdin.resume();return}let K="",Y=0,X=0,z=process.stdin.isRaw;if(process.stdin.setRawMode(!0),!process.stdin.readableEncoding)process.stdin.setEncoding("utf8");process.stdin.resume();let J=()=>{if(!K.startsWith("/")||K.length<1)return[];return Z1.filter((U)=>U.cmd.startsWith(K))},N=()=>{if(Y>0){for(let U=0;U<Y;U++)process.stdout.write("\x1B[1B"),process.stdout.write("\x1B[2K");process.stdout.write(`\x1B[${Y}A`),Y=0}process.stdout.write("\x1B[0K")},W=()=>{N();let U=J();if(U.length===0)return;if(X>=U.length)X=U.length-1;if(X<0)X=0;process.stdout.write("\x1B[s");for(let D=0;D<U.length;D++){process.stdout.write(`
17
+ \x1B[2K`);let R=U[D];if(D===X)process.stdout.write(` ${G.blue(G.bold(R.cmd))} ${G.gray(R.desc)}`);else process.stdout.write(` ${G.gray(R.cmd)} ${G.gray(G.dim(R.desc))}`)}Y=U.length,process.stdout.write("\x1B[u")},_="",q=(U)=>{let D=typeof U==="string"?U:U.toString("utf8");for(let R=0;R<D.length;R++){let H=D[R],B=H.charCodeAt(0);if(_.length>0||B===27){if(_+=H,_.length===1)continue;if(_.length===2&&_[1]==="[")continue;if(_.length>=3){let O=J();if(_==="\x1B[A"&&O.length>0)X=(X-1+O.length)%O.length,W();else if(_==="\x1B[B"&&O.length>0)X=(X+1)%O.length,W();_="";continue}continue}if(B===3)N(),process.stdin.setRawMode(z),process.stdout.write(`
18
+ `),process.exit(0);if(B===4){N(),process.stdin.setRawMode(z),A(),V(Error("EOF"));return}if(B===13||B===10){let O=J();if(O.length>0&&K!==O[X].cmd){N();let f=O[X].cmd;process.stdout.write("\b \b".repeat(K.length)),K=f,process.stdout.write(K)}N(),process.stdin.setRawMode(z),process.stdout.write(`
19
+ `),A(),$(K);return}if(B===127||B===8){if(K.length>0)N(),K=K.slice(0,-1),process.stdout.write("\b \b"),X=0,W();continue}if(B===9){let O=J();if(O.length>0){N();let f=O[X].cmd,p0=f.slice(K.length);K=f,process.stdout.write(p0),W()}continue}if(B<32)continue;N(),K+=H,process.stdout.write(H),X=0,W()}},A=()=>{process.stdin.removeListener("data",q)};process.stdin.on("data",q)})}async function D0(){let Q=[],Z=!0;while(!0){let $=Z?`
20
+ ${G.gray(j.tl+j.line(3))} ${E.prompt} `:` ${G.gray(j.v)} `,V=await L($,Z);if(Z=!1,V.endsWith("\\"))Q.push(V.slice(0,-1));else{Q.push(V);break}}return Q.join(`
21
+ `)}async function T(Q,Z){console.log(`
22
+ ${G.bold(Q)}`),Z.forEach(($,V)=>{let K=G.blue(` ${V+1}.`),Y=$.desc?G.gray(` (${$.desc})`):"";console.log(`${K} ${$.label}${Y}`)}),console.log(` ${G.gray(" 0. Cancel")}`);while(!0){let V=(await L(`
23
+ ${G.gray("choose")} ${G.blue("❯")} `)).trim().toLowerCase();if(V==="0"||V==="q"||V==="cancel"||V==="exit"||V==="")return-1;let K=parseInt(V);if(K>=1&&K<=Z.length)return K-1;console.log(` ${E.warn} enter 1-${Z.length} or 0 to cancel`)}}function S(Q,Z){console.log(`
24
+ ${E.tool} ${G.yellow(Q)} ${G.gray(Z)}`)}function M(Q){console.error(` ${E.err} ${G.pink(Q)}`)}var A0=(Q)=>`\x1B[${Q}m`,d=(Q,Z)=>($)=>`${A0(Q)}${$}${A0(Z)}`,F=(Q)=>(Z)=>`\x1B[38;5;${Q}m${Z}\x1B[39m`,G,E,j,Z1;var a=P(()=>{G={bold:d("1","22"),dim:d("2","22"),italic:d("3","23"),under:d("4","24"),blue:F(111),purple:F(141),green:F(149),yellow:F(179),pink:F(210),cyan:F(117),gray:F(60),text:F(146),orange:F(215)},E={prompt:G.blue("❯"),ai:G.purple("◆"),tool:G.yellow("⚙"),ok:G.green("✓"),err:G.pink("✗"),warn:G.yellow("⚠"),arrow:G.gray("→"),dot:G.gray("•"),spinner:["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]};j={h:"─",v:"│",tl:"╭",tr:"╮",bl:"╰",br:"╯",line:(Q)=>"─".repeat(Q)};Z1=[{cmd:"/model",desc:"switch AI model"},{cmd:"/auth",desc:"set API key"},{cmd:"/mode",desc:"toggle mode"},{cmd:"/compact",desc:"toggle compact"},{cmd:"/diff",desc:"show file changes"},{cmd:"/undo",desc:"revert last change"},{cmd:"/init",desc:"init project context"},{cmd:"/remote",desc:"start web remote"},{cmd:"/history",desc:"past conversations"},{cmd:"/quota",desc:"API rate limits"},{cmd:"/update",desc:"update tenicli"},{cmd:"/clear",desc:"clear screen"},{cmd:"/cost",desc:"show token usage"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit"}]});import{readFileSync as V0,writeFileSync as B0,existsSync as c,readdirSync as O0,statSync as j0,mkdirSync as $1}from"fs";import{resolve as V1,relative as i,join as F0,dirname as G1}from"path";import{spawn as I0}from"child_process";class P0{writes=[];recordWrite(Q,Z){let $=c(Q)?V0(Q,"utf8"):null;this.writes.push({path:Q,backup:$,newLines:Z.split(`
25
+ `).length,time:new Date})}getChanges(){let Q=new Map;for(let Z of this.writes)Q.set(Z.path,{isNew:Z.backup===null,lines:Z.newLines,time:Z.time});return Array.from(Q.entries()).map(([Z,$])=>({path:Z,...$}))}undo(){let Q=this.writes.pop();if(!Q)return null;if(Q.backup!==null)return B0(Q.path,Q.backup,"utf8"),{path:Q.path,restored:!0};else{try{r("fs").unlinkSync(Q.path)}catch{}return{path:Q.path,restored:!1}}}get count(){return this.writes.length}clear(){this.writes=[]}}async function M0(Q,Z,$){try{let V;switch(Q){case"read_file":V=X1(Z,$),S("read_file",G.dim(i($,I(Z.path,$))));break;case"write_file":V=z1(Z,$),S("write_file",G.dim(i($,I(Z.path,$))));break;case"list_dir":V=J1(Z,$),S("list_dir",G.dim(Z.path||"."));break;case"search_files":V=await Y1(Z,$),S("search_files",G.dim(`"${Z.pattern}"`));break;case"exec_command":V=await N1(Z,$),S("exec_command",G.dim(R0(Z.command,60)));break;default:V=`Unknown tool: ${Q}`}return{type:"tool_result",content:R0(V,K1)}}catch(V){return{type:"tool_result",content:`Error: ${V.message}`,is_error:!0}}}function I(Q,Z){return V1(Z,Q)}function X1(Q,Z){let $=I(Q.path,Z);if(!c($))return`File not found: ${Q.path}`;let V=V0($,"utf8"),K=V.split(`
26
+ `);if(Q.start_line||Q.end_line){let Y=Math.max(1,Q.start_line||1)-1,X=Math.min(K.length,Q.end_line||K.length);return K.slice(Y,X).map((z,J)=>`${Y+J+1}: ${z}`).join(`
27
+ `)}if(K.length>50)return K.map((Y,X)=>`${X+1}: ${Y}`).join(`
28
+ `);return V}function z1(Q,Z){let $=I(Q.path,Z),V=G1($);if(!c(V))$1(V,{recursive:!0});return w.recordWrite($,Q.content),B0($,Q.content,"utf8"),`Written ${Q.content.split(`
29
+ `).length} lines to ${Q.path}`}function J1(Q,Z){let $=I(Q.path||".",Z);if(!c($))return`Directory not found: ${Q.path||"."}`;let V=Q.depth||1,K=[];function Y(X,z){if(z>V)return;try{let J=O0(X);for(let N of J){if(N.startsWith(".")||N==="node_modules")continue;let W=F0(X,N),_=i($,W);try{let q=j0(W),A=" ".repeat(z);if(q.isDirectory())K.push(`${A}${_}/`),Y(W,z+1);else{let U=q.size>1024?`${(q.size/1024).toFixed(1)}KB`:`${q.size}B`;K.push(`${A}${_} (${U})`)}}catch{}}}catch{}}return Y($,0),K.length>0?K.join(`
30
+ `):"(empty directory)"}async function Y1(Q,Z){let $=I(Q.path||".",Z),V=Q.pattern;try{let X=["-n","--max-count=50","--no-heading"];if(Q.include)X.push("--glob",Q.include);return X.push(V,$),await new Promise((J,N)=>{let W=I0("rg",X,{shell:!0}),_="";W.stdout.on("data",(q)=>_+=q.toString()),W.on("close",(q)=>{if(q===0||q===1)J(_.trim()||"No matches found.");else N(Error("rg failed"))}),W.on("error",N)})}catch{}let K=[];function Y(X){try{for(let z of O0(X)){if(z.startsWith(".")||z==="node_modules")continue;let J=F0(X,z);try{let N=j0(J);if(N.isDirectory()){Y(J);continue}if(N.size>500000)continue;if(Q.include&&!W1(z,Q.include))continue;let _=V0(J,"utf8").split(`
31
+ `);for(let q=0;q<_.length;q++)if(_[q].includes(V)){if(K.push(`${i(Z,J)}:${q+1}: ${_[q].trim()}`),K.length>=50)return}}catch{}}}catch{}}return Y($),K.length>0?K.join(`
32
+ `):"No matches found."}async function N1(Q,Z){let $=I(Q.cwd||".",Z),V=process.platform==="win32",K=V?"cmd":"sh",Y=V?"/c":"-c";return new Promise((X)=>{let z=I0(K,[Y,Q.command],{cwd:$,env:{...process.env,PAGER:"cat"}}),J="",N="";z.stdout.on("data",(_)=>J+=_.toString()),z.stderr.on("data",(_)=>N+=_.toString());let W=setTimeout(()=>z.kill(),30000);z.on("close",(_)=>{clearTimeout(W);let q="";if(J.trim())q+=J.trim();if(N.trim())q+=(q?`
33
+ `:"")+`[stderr] ${N.trim()}`;q+=`
34
+ [exit code: ${_}]`,X(q)}),z.on("error",(_)=>{clearTimeout(W),X(`[error] Failed to start process: ${_.message}`)})})}function R0(Q,Z){if(Q.length<=Z)return Q;return Q.slice(0,Z)+`
35
+ ... (truncated, ${Q.length-Z} chars omitted)`}function W1(Q,Z){if(Z.startsWith("*."))return Q.endsWith(Z.slice(1));return Q.includes(Z.replace(/\*/g,""))}var w,L0,K1=30000;var G0=P(()=>{a();w=new P0,L0=[{name:"read_file",description:"Read contents of a file. Returns the file text.",input_schema:{type:"object",properties:{path:{type:"string",description:"File path (relative to cwd or absolute)"},start_line:{type:"number",description:"Optional: start line (1-indexed)"},end_line:{type:"number",description:"Optional: end line (1-indexed, inclusive)"}},required:["path"]}},{name:"write_file",description:"Write content to a file. Creates parent directories if needed.",input_schema:{type:"object",properties:{path:{type:"string",description:"File path"},content:{type:"string",description:"Full file content to write"}},required:["path","content"]}},{name:"list_dir",description:"List files and directories in a path. Returns names with type indicators.",input_schema:{type:"object",properties:{path:{type:"string",description:"Directory path (default: cwd)"},depth:{type:"number",description:"Max depth (default: 1)"}},required:[]}},{name:"search_files",description:"Search for text in files using pattern matching (like grep). Returns matching lines with file paths.",input_schema:{type:"object",properties:{pattern:{type:"string",description:"Text or regex pattern to search for"},path:{type:"string",description:"Directory to search in (default: cwd)"},include:{type:"string",description:'Glob pattern to filter files, e.g. "*.ts"'}},required:["pattern"]}},{name:"exec_command",description:"Execute a shell command. Returns stdout and stderr.",input_schema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute"},cwd:{type:"string",description:"Working directory (default: project cwd)"}},required:["command"]}}]});class y{messages=[];tokens={input:0,output:0};cfg;autoMode=!1;onOutput;onConfirm;constructor(Q){this.cfg=Q}emit(Q){this.onOutput?.(Q)}write(Q){if(this.onOutput)this.emit({type:"text",text:Q});else process.stdout.write(Q)}log(Q){if(this.onOutput)this.emit({type:"text",text:Q+`
36
+ `});else console.log(Q)}async send(Q){this.messages.push({role:"user",content:Q}),await this.agentLoop()}async agentLoop(){while(!0){let Q=await this.streamResponse();if(Q.stopReason==="error")break;if(Q.stopReason==="tool_use"){let $=Q.content.filter((K)=>K.type==="tool_use"),V=[];for(let K of $){if(!this.autoMode&&(K.name==="write_file"||K.name==="exec_command")){let X=K.name==="write_file"?K.input?.path:K.input?.command?.slice(0,80),z="y";if(this.onConfirm)z=await this.onConfirm(K.id,K.name,X||"");else this.log(`
37
+ ${E.warn} ${G.yellow(K.name)} ${G.gray(X||"")}`),z=(await L(` ${G.gray("allow?")} ${G.blue("[y/n/auto]")} `)).trim().toLowerCase();if(z==="auto")this.autoMode=!0;else if(z!=="y"&&z!=="yes"&&z!==""){V.push({type:"tool_result",tool_use_id:K.id,content:"User denied this action.",is_error:!0});continue}}this.emit({type:"tool",name:K.name,detail:JSON.stringify(K.input||{}).slice(0,200)});let Y=await M0(K.name,K.input,this.cfg.cwd);this.emit({type:"tool_result",name:K.name,content:(Y.content||"").slice(0,500),is_error:!!Y.is_error}),V.push({type:"tool_result",tool_use_id:K.id,content:Y.content,is_error:Y.is_error})}this.messages.push({role:"assistant",content:Q.content}),this.messages.push({role:"user",content:V});continue}let Z=Q.content.filter(($)=>$.type==="text").map(($)=>$.text).join("");if(Z)this.messages.push({role:"assistant",content:Z});break}if(this.emit({type:"tokens",input:this.tokens.input,output:this.tokens.output,messages:this.messages.length}),!this.onOutput)console.log(`
38
+ ${G.gray(`tokens: ${this.tokens.input}↑ ${this.tokens.output}↓`)}`)}async streamResponse(){let Q=!this.onOutput,Z=Q?new l("Thinking").start():null,$=[],V="",K="",Y="",X="",z="end_turn",J=!1;try{let N=Z0(this.cfg.provider,this.messages,this.cfg.systemPrompt,L0,this.cfg.maxTokens);for await(let W of N)switch(W.type){case"text":if(!J){if(Z?.stop(),J=!0,Q)process.stdout.write(`
39
+ ${E.ai} `)}if(W.text)this.write(W.text);V+=W.text;break;case"tool_start":if(!J)Z?.stop(),J=!0;if(V)$.push({type:"text",text:V}),V="";K=W.id,Y=W.name,X="";break;case"tool_input":X+=W.partial;break;case"tool_end":if(K){let _={};try{_=JSON.parse(X)}catch{}$.push({type:"tool_use",id:K,name:Y,input:_}),K="",X=""}break;case"usage":this.tokens.input+=W.input,this.tokens.output+=W.output;break;case"done":z=W.stopReason;break}}catch(N){if(Z?.stop(),Q)console.log();let W=N.message||String(N);if(this.emit({type:"error",message:W}),Q)M(W);return{content:[],stopReason:"error"}}if(V){if($.push({type:"text",text:V}),this.emit({type:"text_done"}),Q)process.stdout.write(`
40
+ `)}if(!J)Z?.stop();return{content:$,stopReason:z}}async compact(){if(this.messages.length<4){this.log(` ${E.warn} Not enough messages to compact.`);return}let Q=!this.onOutput?new l("Compacting").start():null;try{let Z="";for(let X of this.messages)if(typeof X.content==="string")Z+=`${X.role}: ${X.content.slice(0,500)}
41
+ `;else{let z=X.content.filter((N)=>N.type==="text").map((N)=>N.text?.slice(0,300)).join(" ");if(z)Z+=`${X.role}: ${z}
42
+ `;let J=X.content.filter((N)=>N.type==="tool_use").map((N)=>`[tool: ${N.name}]`).join(", ");if(J)Z+=` tools: ${J}
43
+ `}let $=this.messages.length,V=`Summarize this conversation concisely. Keep key decisions, file changes, and current state. Be brief:
44
+
45
+ ${Z.slice(0,6000)}`;this.messages=[{role:"user",content:V},{role:"assistant",content:`[Conversation compacted from ${$} messages. Summary of what happened:]`}];let K=Z0(this.cfg.provider,[{role:"user",content:V}],"You are a conversation summarizer. Create a brief summary preserving key facts, decisions, and file changes.",[],this.cfg.maxTokens),Y="";for await(let X of K){if(X.type==="text"&&X.text)Y+=X.text;if(X.type==="usage")this.tokens.input+=X.input,this.tokens.output+=X.output}this.messages=[{role:"user",content:`[Previous conversation summary]
46
+ ${Y}`},{role:"assistant",content:"Understood. I have the context from our previous conversation. How can I continue helping you?"}],Q?.stop(),this.log(` ${E.ok} Compacted ${$} messages → 2 ${G.gray(`(saved ~${Math.round(Z.length/4)} tokens)`)}`)}catch(Z){Q?.stop();let $=`Compact failed: ${Z.message}`;if(this.emit({type:"error",message:$}),!this.onOutput)M($)}}get stats(){return this.tokens}get messageCount(){return this.messages.length}clear(){this.messages=[],this.tokens={input:0,output:0}}exportState(){return{messages:this.messages,tokens:{...this.tokens},autoMode:this.autoMode}}importState(Q){if(Q.messages)this.messages=Q.messages;if(Q.tokens)this.tokens=Q.tokens;if(Q.autoMode!==void 0)this.autoMode=Q.autoMode}getTitle(){let Q=this.messages.find((Z)=>Z.role==="user"&&typeof Z.content==="string");if(Q)return Q.content.slice(0,50);return"New conversation"}}var K0=P(()=>{$0();G0();a()});var v;var z0=P(()=>{v={name:"tenicli",version:"0.2.2",description:"Lightweight AI coding CLI — fast, compact, multi-provider",type:"module",bin:{teni:"./dist/index.js"},files:["dist/","README.md","LICENSE"],scripts:{dev:"bun run src/index.ts","build:npm":"bun build src/index.ts --outfile dist/index.js --target node --minify",build:"bun build --compile --minify src/index.ts --outfile teni","build:win":"bun build --compile --minify --target=bun-windows-x64 src/index.ts --outfile teni.exe","build:linux":"bun build --compile --minify --target=bun-linux-x64 src/index.ts --outfile teni","build:mac":"bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile teni",prepublishOnly:"bun run build:npm"},keywords:["ai","cli","coding","agent","anthropic","openai","terminal"],author:"Yan Tenica",license:"MIT",repository:{type:"git",url:"https://github.com/Nhqvu2005/TeniCli.git"},homepage:"https://github.com/Nhqvu2005/TeniCli",engines:{node:">=18"},dependencies:{},devDependencies:{"@types/bun":"latest","@types/figlet":"^1.7.0",figlet:"^1.11.0"}}});var h0={};_0(h0,{renderQR:()=>H1,getLocalIPs:()=>D1});function H1(Q){let Z=[],$=Math.max(Q.length+4,30),V="██",K=" ";for(let X=0;X<7;X++){let z="";for(let J=0;J<7;J++)z+=J0.FINDER[X][J]?"██":" ";z+=" ";for(let J=0;J<$-15;J++)z+=X===6&&J%2===0?"██":X===6?" ":" ";z+=" ";for(let J=0;J<7;J++)z+=J0.FINDER[X][J]?"██":" ";Z.push(z)}Z.push(" ".repeat($/2+1));let Y=5;for(let X=0;X<Y;X++){let z="";for(let J=0;J<$+1;J++){if(J===6){z+=X%2===0?"██":" ";continue}z+=(J+X*3+J*X)%3===0?"██":" "}Z.push(z)}Z.push(" ".repeat($/2+1));for(let X=0;X<7;X++){let z="";for(let J=0;J<7;J++)z+=J0.FINDER[X][J]?"██":" ";z+=" ";for(let J=0;J<$-14;J++)z+=(J+X*5)%3===0?"██":" ";Z.push(z)}return Z.map((X)=>" "+X).join(`
47
+ `)}function D1(){try{let{networkInterfaces:Q}=r("os"),Z=Q(),$=[];for(let V of Object.keys(Z))for(let K of Z[V])if(K.family==="IPv4"&&!K.internal)$.push(K.address);return $}catch{return[]}}var J0;var v0=P(()=>{J0={FINDER:[[1,1,1,1,1,1,1],[1,0,0,0,0,0,1],[1,0,1,1,1,0,1],[1,0,1,1,1,0,1],[1,0,1,1,1,0,1],[1,0,0,0,0,0,1],[1,1,1,1,1,1,1]]}});var N0={};_0(N0,{startServer:()=>M1,getActiveServers:()=>O1});import{createServer as R1}from"http";import{createHash as B1}from"crypto";function O1(){return Y0}function j1(Q){if(Q.length<2)return null;let Z=Q[0]&15,$=(Q[1]&128)!==0,V=Q[1]&127,K=2;if(V===126){if(Q.length<4)return null;V=Q.readUInt16BE(2),K=4}else if(V===127){if(Q.length<10)return null;V=Number(Q.readBigUInt64BE(2)),K=10}let X=K+($?4:0)+V;if(Q.length<X)return null;if($){let z=Q.slice(K,K+4),J=Buffer.alloc(V);for(let N=0;N<V;N++)J[N]=Q[K+4+N]^z[N%4];return{opcode:Z,payload:J,totalSize:X}}return{opcode:Z,payload:Q.slice(K,K+V),totalSize:X}}function F1(Q){let Z=Buffer.from(Q,"utf8"),$=Z.length,V;if($<126)V=Buffer.alloc(2),V[0]=129,V[1]=$;else if($<65536)V=Buffer.alloc(4),V[0]=129,V[1]=126,V.writeUInt16BE($,2);else V=Buffer.alloc(10),V[0]=129,V[1]=127,V.writeBigUInt64BE(BigInt($),2);return Buffer.concat([V,Z])}function P1(Q){let Z=Buffer.alloc(2);Z[0]=137,Z[1]=0,Q.write(Z)}function L1(Q,Z){return`<!DOCTYPE html>
48
48
  <html lang="en">
49
49
  <head>
50
50
  <meta charset="utf-8">
@@ -59,31 +59,26 @@ ${z}`},{role:"assistant",content:"Understood. I have the context from our previo
59
59
  --pink: #f7768e; --cyan: #7dcfff; --orange: #ff9e64;
60
60
  }
61
61
  body { background:var(--bg); color:var(--fg); font-family:'Cascadia Code','Fira Code','JetBrains Mono',monospace; height:100vh; display:flex; }
62
-
63
- /* Sidebar */
64
62
  .sidebar { width:220px; background:var(--bg-dark); border-right:1px solid var(--bg-lighter); display:flex; flex-direction:column; flex-shrink:0; }
65
63
  .sidebar-header { padding:16px; border-bottom:1px solid var(--bg-lighter); display:flex; align-items:center; gap:8px; }
66
64
  .sidebar-header h1 { font-size:14px; color:var(--blue); font-weight:600; }
67
- .sidebar-header .dot { width:8px; height:8px; border-radius:50%; background:var(--green); animation:pulse 2s infinite; }
65
+ .sidebar-header .dot { width:8px; height:8px; border-radius:50%; background:var(--pink); animation:pulse 2s infinite; }
68
66
  @keyframes pulse { 0%,100%{opacity:1}50%{opacity:.4} }
69
67
  .session-list { flex:1; overflow-y:auto; padding:8px; }
70
68
  .session-item { padding:10px 12px; border-radius:8px; cursor:pointer; margin-bottom:4px; display:flex; align-items:center; justify-content:space-between; transition:background .15s; font-size:13px; }
71
69
  .session-item:hover { background:var(--bg-lighter); }
72
70
  .session-item.active { background:var(--bg-lighter); border-left:2px solid var(--blue); }
73
71
  .session-item .name { color:var(--fg); }
74
- .session-item .close { color:var(--fg-dim); cursor:pointer; opacity:0; transition:opacity .15s; font-size:16px; }
75
- .session-item:hover .close { opacity:1; }
76
- .session-item .close:hover { color:var(--pink); }
72
+ .session-item .close-btn { color:var(--fg-dim); cursor:pointer; opacity:0; transition:opacity .15s; font-size:16px; line-height:1; }
73
+ .session-item:hover .close-btn { opacity:1; }
74
+ .session-item .close-btn:hover { color:var(--pink); }
77
75
  .new-session { margin:8px; padding:10px; border-radius:8px; border:1px dashed var(--fg-dim); color:var(--fg-dim); text-align:center; cursor:pointer; font-size:13px; transition:all .15s; }
78
76
  .new-session:hover { border-color:var(--blue); color:var(--blue); background:rgba(122,162,247,.05); }
79
77
  .sidebar-footer { padding:12px 16px; border-top:1px solid var(--bg-lighter); font-size:11px; color:var(--fg-dim); }
80
-
81
- /* Main */
82
78
  .main { flex:1; display:flex; flex-direction:column; min-width:0; }
83
79
  .topbar { padding:12px 20px; border-bottom:1px solid var(--bg-lighter); display:flex; align-items:center; justify-content:space-between; font-size:13px; }
84
80
  .topbar .title { color:var(--fg); font-weight:500; }
85
81
  .topbar .info { color:var(--fg-dim); }
86
-
87
82
  .messages { flex:1; overflow-y:auto; padding:20px; }
88
83
  .msg { margin-bottom:16px; animation:fadeIn .2s ease; }
89
84
  @keyframes fadeIn { from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none} }
@@ -93,17 +88,12 @@ ${z}`},{role:"assistant",content:"Understood. I have the context from our previo
93
88
  .msg.system .bubble { color:var(--fg-dim); font-size:12px; text-align:center; padding:4px 0; }
94
89
  .msg.tool .bubble { background:rgba(224,175,104,.05); border:1px solid rgba(224,175,104,.15); border-radius:8px; padding:8px 12px; font-size:12px; color:var(--yellow); max-width:80%; }
95
90
  .msg.error .bubble { color:var(--pink); font-size:12px; }
96
- .msg .tokens { color:var(--fg-dim); font-size:11px; margin-top:4px; }
97
-
98
- /* Confirm dialog */
99
91
  .confirm-bar { background:var(--bg-lighter); border:1px solid rgba(224,175,104,.3); border-radius:8px; padding:12px 16px; margin:8px 0; display:flex; align-items:center; gap:12px; font-size:13px; }
100
92
  .confirm-bar .label { color:var(--yellow); flex:1; }
101
93
  .confirm-bar button { padding:4px 14px; border-radius:6px; border:none; cursor:pointer; font-size:12px; font-family:inherit; }
102
94
  .btn-allow { background:var(--green); color:var(--bg); }
103
95
  .btn-deny { background:var(--pink); color:var(--bg); }
104
96
  .btn-auto { background:var(--blue); color:var(--bg); }
105
-
106
- /* Input */
107
97
  .input-area { padding:16px 20px; border-top:1px solid var(--bg-lighter); }
108
98
  .input-wrap { display:flex; gap:8px; align-items:flex-end; }
109
99
  .input-wrap textarea { flex:1; background:var(--bg-lighter); border:1px solid transparent; border-radius:10px; padding:10px 14px; color:var(--fg); font-family:inherit; font-size:14px; resize:none; outline:none; min-height:44px; max-height:200px; transition:border-color .15s; }
@@ -112,274 +102,254 @@ ${z}`},{role:"assistant",content:"Understood. I have the context from our previo
112
102
  .send-btn { width:40px; height:40px; border-radius:10px; border:none; background:var(--blue); color:var(--bg); cursor:pointer; display:flex; align-items:center; justify-content:center; font-size:18px; transition:background .15s; flex-shrink:0; }
113
103
  .send-btn:hover { background:#8ab4f8; }
114
104
  .send-btn:disabled { opacity:.3; cursor:default; }
115
-
116
- .typing { color:var(--fg-dim); font-size:12px; padding:4px 0; display:none; }
105
+ .typing { color:var(--fg-dim); font-size:12px; padding:4px 20px; display:none; }
117
106
  .typing.show { display:block; animation:blink 1s infinite; }
118
107
  @keyframes blink { 0%,100%{opacity:1}50%{opacity:.3} }
119
-
120
- /* Mobile */
121
- @media(max-width:768px) {
122
- .sidebar { display:none; } /* TODO: hamburger */
123
- .msg.user .bubble { max-width:85%; }
124
- }
108
+ @media(max-width:768px) { .sidebar { display:none; } .msg.user .bubble { max-width:85%; } }
125
109
  </style>
126
110
  </head>
127
111
  <body>
128
-
129
112
  <div class="sidebar">
130
113
  <div class="sidebar-header">
131
- <div class="dot"></div>
132
- <h1>TeniCLI v${Z}</h1>
114
+ <div class="dot" id="statusDot"></div>
115
+ <h1>TeniCLI v${Q}</h1>
133
116
  </div>
134
117
  <div class="session-list" id="sessionList"></div>
135
- <div class="new-session" onclick="newSession()">+ New Session</div>
118
+ <div class="new-session" id="newSessionBtn">+ New Session</div>
136
119
  <div class="sidebar-footer">Remote Terminal</div>
137
120
  </div>
138
-
139
121
  <div class="main">
140
122
  <div class="topbar">
141
- <span class="title" id="topTitle">Session 1</span>
142
- <span class="info" id="topInfo">ready</span>
123
+ <span class="title" id="topTitle">Connecting...</span>
124
+ <span class="info" id="topInfo">...</span>
143
125
  </div>
144
126
  <div class="messages" id="messages"></div>
145
127
  <div class="typing" id="typing">◆ thinking...</div>
146
128
  <div class="input-area">
147
129
  <div class="input-wrap">
148
130
  <textarea id="input" placeholder="Type a message or /command..." rows="1" autofocus></textarea>
149
- <button class="send-btn" id="sendBtn" onclick="sendMessage()">↑</button>
131
+ <button class="send-btn" id="sendBtn">↑</button>
150
132
  </div>
151
133
  </div>
152
134
  </div>
153
-
154
135
  <script>
155
- const WS_URL = location.protocol === 'https:' ? 'wss://' : 'ws://'
156
- let ws, sessions = {}, activeId = null, sessionCounter = 0
157
-
158
- function connect() {
159
- const token = localStorage.getItem('teni_token')
160
- if (!token) { promptToken(); return }
161
-
162
- ws = new WebSocket(WS_URL + location.host + '/ws?token=' + encodeURIComponent(token))
163
- ws.onopen = () => {
164
- document.querySelector('.dot').style.background = 'var(--green)'
165
- document.getElementById('topInfo').textContent = 'connected'
166
- if (Object.keys(sessions).length === 0) newSession()
167
- }
168
- ws.onmessage = (e) => handleMessage(JSON.parse(e.data))
169
- ws.onclose = () => {
170
- document.querySelector('.dot').style.background = 'var(--pink)'
171
- document.getElementById('topInfo').textContent = 'disconnected'
172
- setTimeout(connect, 3000)
136
+ (function(){
137
+ var TOKEN = '${Z}';
138
+ var wsUrl = (location.protocol==='https:'?'wss://':'ws://') + location.host + '/ws?token=' + encodeURIComponent(TOKEN);
139
+ var ws, sessions = {}, activeId = null, counter = 0;
140
+
141
+ function $(id){ return document.getElementById(id); }
142
+ function setDot(color){ $('statusDot').style.background = 'var(--'+color+')'; }
143
+ function escHtml(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
144
+
145
+ function connect(){
146
+ ws = new WebSocket(wsUrl);
147
+ ws.onopen = function(){
148
+ setDot('green');
149
+ $('topInfo').textContent = 'connected';
150
+ if(Object.keys(sessions).length===0) newSession();
151
+ };
152
+ ws.onmessage = function(e){ handleMsg(JSON.parse(e.data)); };
153
+ ws.onclose = function(){
154
+ setDot('pink');
155
+ $('topInfo').textContent = 'reconnecting...';
156
+ setTimeout(connect, 2000);
157
+ };
158
+ ws.onerror = function(){ ws.close(); };
173
159
  }
174
- ws.onerror = () => ws.close()
175
- }
176
160
 
177
- function promptToken() {
178
- const token = prompt('Enter access password:')
179
- if (token) { localStorage.setItem('teni_token', token); connect() }
180
- }
181
-
182
- function newSession() {
183
- sessionCounter++
184
- const id = 'session_' + sessionCounter
185
- sessions[id] = { name: 'Session ' + sessionCounter, messages: [], streaming: '', busy: false }
186
- ws.send(JSON.stringify({ type: 'new_session', sessionId: id }))
187
- renderSidebar()
188
- switchSession(id)
189
- }
190
-
191
- function switchSession(id) {
192
- activeId = id
193
- renderSidebar()
194
- renderMessages()
195
- document.getElementById('topTitle').textContent = sessions[id].name
196
- }
197
-
198
- function renderSidebar() {
199
- const list = document.getElementById('sessionList')
200
- list.innerHTML = Object.entries(sessions).map(([id, s]) =>
201
- '<div class="session-item ' + (id === activeId ? 'active' : '') + '" onclick="switchSession(\\'' + id + '\\')">' +
202
- '<span class="name">' + s.name + '</span>' +
203
- '<span class="close" onclick="event.stopPropagation();closeSession(\\'' + id + '\\')">&times;</span>' +
204
- '</div>'
205
- ).join('')
206
- }
207
-
208
- function closeSession(id) {
209
- ws.send(JSON.stringify({ type: 'close_session', sessionId: id }))
210
- delete sessions[id]
211
- if (activeId === id) {
212
- const keys = Object.keys(sessions)
213
- activeId = keys.length ? keys[0] : null
161
+ function newSession(){
162
+ counter++;
163
+ var id = 'sess_'+counter;
164
+ sessions[id] = { name:'Session '+counter, messages:[], streaming:'', busy:false };
165
+ ws.send(JSON.stringify({ type:'new_session', sessionId:id }));
166
+ switchTo(id);
214
167
  }
215
- renderSidebar()
216
- if (activeId) switchSession(activeId)
217
- }
218
168
 
219
- function sendMessage() {
220
- const input = document.getElementById('input')
221
- const text = input.value.trim()
222
- if (!text || !activeId) return
223
- input.value = ''; autoResize(input)
224
-
225
- const s = sessions[activeId]
226
- s.messages.push({ type: 'user', text })
227
- s.busy = true
228
- renderMessages()
229
- document.getElementById('typing').classList.add('show')
230
-
231
- ws.send(JSON.stringify({ type: 'message', sessionId: activeId, text }))
232
- }
233
-
234
- function handleMessage(msg) {
235
- const s = sessions[msg.sessionId]
236
- if (!s) return
169
+ function switchTo(id){
170
+ activeId = id;
171
+ $('topTitle').textContent = sessions[id].name;
172
+ renderSidebar();
173
+ renderMsgs();
174
+ }
237
175
 
238
- switch (msg.type) {
239
- case 'text':
240
- s.streaming += msg.text
241
- if (msg.sessionId === activeId) renderMessages()
242
- break
243
- case 'text_done':
244
- if (s.streaming) { s.messages.push({ type: 'ai', text: s.streaming }); s.streaming = '' }
245
- s.busy = false
246
- document.getElementById('typing').classList.remove('show')
247
- if (msg.sessionId === activeId) renderMessages()
248
- break
249
- case 'tokens':
250
- s.messages.push({ type: 'system', text: msg.input + '↑ ' + msg.output + '↓ tokens • ' + msg.messages + ' msgs' })
251
- s.busy = false
252
- document.getElementById('typing').classList.remove('show')
253
- if (msg.sessionId === activeId) renderMessages()
254
- break
255
- case 'tool':
256
- s.messages.push({ type: 'tool', text: '⚙ ' + msg.name + ' ' + (msg.detail || '') })
257
- if (msg.sessionId === activeId) renderMessages()
258
- break
259
- case 'tool_result':
260
- s.messages.push({ type: msg.is_error ? 'error' : 'system', text: (msg.is_error ? '✗ ':'✓ ') + msg.name + ': ' + msg.content.slice(0,200) })
261
- if (msg.sessionId === activeId) renderMessages()
262
- break
263
- case 'error':
264
- s.messages.push({ type: 'error', text: '✗ ' + msg.message })
265
- s.busy = false
266
- document.getElementById('typing').classList.remove('show')
267
- if (msg.sessionId === activeId) renderMessages()
268
- break
269
- case 'confirm':
270
- s.messages.push({ type: 'confirm', id: msg.id, tool: msg.tool, preview: msg.preview })
271
- if (msg.sessionId === activeId) renderMessages()
272
- break
176
+ function closeSession(id){
177
+ ws.send(JSON.stringify({ type:'close_session', sessionId:id }));
178
+ delete sessions[id];
179
+ var keys = Object.keys(sessions);
180
+ if(activeId===id) activeId = keys.length?keys[0]:null;
181
+ renderSidebar();
182
+ if(activeId) switchTo(activeId);
273
183
  }
274
- }
275
184
 
276
- function respondConfirm(confirmId, answer) {
277
- ws.send(JSON.stringify({ type: 'confirm_response', sessionId: activeId, id: confirmId, answer }))
278
- // Remove the confirm bar
279
- const s = sessions[activeId]
280
- s.messages = s.messages.filter(m => !(m.type === 'confirm' && m.id === confirmId))
281
- renderMessages()
282
- }
185
+ function renderSidebar(){
186
+ var html = '';
187
+ for(var id in sessions){
188
+ var s = sessions[id];
189
+ var cls = id===activeId?' active':'';
190
+ html += '<div class="session-item'+cls+'" data-id="'+id+'">' +
191
+ '<span class="name">'+escHtml(s.name)+'</span>' +
192
+ '<span class="close-btn" data-close="'+id+'">&times;</span></div>';
193
+ }
194
+ $('sessionList').innerHTML = html;
195
+ }
283
196
 
284
- function renderMessages() {
285
- const s = sessions[activeId]
286
- if (!s) return
287
- const el = document.getElementById('messages')
288
- const wasAtBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 40
197
+ $('sessionList').addEventListener('click', function(e){
198
+ var el = e.target;
199
+ if(el.dataset.close){ e.stopPropagation(); closeSession(el.dataset.close); return; }
200
+ var item = el.closest('.session-item');
201
+ if(item && item.dataset.id) switchTo(item.dataset.id);
202
+ });
203
+ $('newSessionBtn').addEventListener('click', newSession);
204
+
205
+ function sendMessage(){
206
+ var inp = $('input');
207
+ var text = inp.value.trim();
208
+ if(!text || !activeId) return;
209
+ inp.value = ''; autoResize(inp);
210
+ var s = sessions[activeId];
211
+ s.messages.push({ type:'user', text:text });
212
+ s.busy = true;
213
+ renderMsgs();
214
+ $('typing').classList.add('show');
215
+ ws.send(JSON.stringify({ type:'message', sessionId:activeId, text:text }));
216
+ }
289
217
 
290
- let html = ''
291
- for (const m of s.messages) {
292
- if (m.type === 'confirm') {
293
- html += '<div class="confirm-bar"><span class="label">⚠ ' + m.tool + ' ' + escHtml(m.preview) + '</span>' +
294
- '<button class="btn-allow" onclick="respondConfirm(\\'' + m.id + '\\',\\'y\\')">Allow</button>' +
295
- '<button class="btn-deny" onclick="respondConfirm(\\'' + m.id + '\\',\\'n\\')">Deny</button>' +
296
- '<button class="btn-auto" onclick="respondConfirm(\\'' + m.id + '\\',\\'auto\\')">Auto</button></div>'
297
- } else {
298
- html += '<div class="msg ' + m.type + '"><div class="bubble">' + escHtml(m.text) + '</div></div>'
218
+ function handleMsg(msg){
219
+ var s = sessions[msg.sessionId];
220
+ if(!s) return;
221
+ switch(msg.type){
222
+ case 'text':
223
+ s.streaming += msg.text;
224
+ break;
225
+ case 'text_done':
226
+ if(s.streaming){ s.messages.push({ type:'ai', text:s.streaming }); s.streaming=''; }
227
+ s.busy=false; $('typing').classList.remove('show');
228
+ break;
229
+ case 'tokens':
230
+ s.messages.push({ type:'system', text:msg.input+'↑ '+msg.output+'↓ tokens • '+msg.messages+' msgs' });
231
+ s.busy=false; $('typing').classList.remove('show');
232
+ break;
233
+ case 'tool':
234
+ s.messages.push({ type:'tool', text:'⚙ '+msg.name+' '+(msg.detail||'') });
235
+ break;
236
+ case 'tool_result':
237
+ s.messages.push({ type:msg.is_error?'error':'system', text:(msg.is_error?'✗ ':'✓ ')+msg.name+': '+(msg.content||'').slice(0,200) });
238
+ break;
239
+ case 'error':
240
+ s.messages.push({ type:'error', text:'✗ '+msg.message });
241
+ s.busy=false; $('typing').classList.remove('show');
242
+ break;
243
+ case 'confirm':
244
+ s.messages.push({ type:'confirm', id:msg.id, tool:msg.tool, preview:msg.preview });
245
+ break;
299
246
  }
247
+ if(msg.sessionId===activeId) renderMsgs();
300
248
  }
301
- // Streaming text
302
- if (s.streaming) {
303
- html += '<div class="msg ai"><div class="bubble">' + escHtml(s.streaming) + '</div></div>'
304
- }
305
- el.innerHTML = html
306
- if (wasAtBottom) el.scrollTop = el.scrollHeight
307
- }
308
249
 
309
- function escHtml(s) {
310
- return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
311
- }
250
+ function respondConfirm(cid, ans){
251
+ ws.send(JSON.stringify({ type:'confirm_response', sessionId:activeId, id:cid, answer:ans }));
252
+ var s = sessions[activeId];
253
+ s.messages = s.messages.filter(function(m){ return !(m.type==='confirm'&&m.id===cid); });
254
+ renderMsgs();
255
+ }
256
+ window._rc = respondConfirm;
257
+
258
+ function renderMsgs(){
259
+ var s = sessions[activeId];
260
+ if(!s) return;
261
+ var el = $('messages');
262
+ var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 40;
263
+ var html = '';
264
+ for(var i=0;i<s.messages.length;i++){
265
+ var m = s.messages[i];
266
+ if(m.type==='confirm'){
267
+ html += '<div class="confirm-bar"><span class="label">⚠ '+escHtml(m.tool)+' '+escHtml(m.preview)+'</span>'+
268
+ '<button class="btn-allow" onclick="_rc(\\''+m.id+'\\',\\'y\\')">Allow</button>'+
269
+ '<button class="btn-deny" onclick="_rc(\\''+m.id+'\\',\\'n\\')">Deny</button>'+
270
+ '<button class="btn-auto" onclick="_rc(\\''+m.id+'\\',\\'auto\\')">Auto</button></div>';
271
+ } else {
272
+ html += '<div class="msg '+m.type+'"><div class="bubble">'+escHtml(m.text)+'</div></div>';
273
+ }
274
+ }
275
+ if(s.streaming) html += '<div class="msg ai"><div class="bubble">'+escHtml(s.streaming)+'</div></div>';
276
+ el.innerHTML = html;
277
+ if(atBottom) el.scrollTop = el.scrollHeight;
278
+ }
312
279
 
313
- // Auto-resize textarea
314
- const input = document.getElementById('input')
315
- input.addEventListener('input', () => autoResize(input))
316
- input.addEventListener('keydown', (e) => {
317
- if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage() }
318
- })
319
- function autoResize(el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 200) + 'px' }
280
+ var inp = $('input');
281
+ inp.addEventListener('input', function(){ autoResize(inp); });
282
+ inp.addEventListener('keydown', function(e){
283
+ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); sendMessage(); }
284
+ });
285
+ $('sendBtn').addEventListener('click', sendMessage);
286
+ function autoResize(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,200)+'px'; }
320
287
 
321
- connect()
288
+ connect();
289
+ })();
322
290
  </script>
323
291
  </body>
324
- </html>`}function F1(Z,$){let Q=m(),V=new Map,J=new Map,z=H1((X,G)=>{if(new URL(X.url||"/",`http://${X.headers.host}`).pathname==="/health"){G.writeHead(200,{"content-type":"application/json"}),G.end(JSON.stringify({status:"ok",sessions:V.size,version:b.version}));return}G.writeHead(200,{"content-type":"text/html","cache-control":"no-cache"}),G.end(j1(b.version))});z.on("upgrade",(X,G)=>{if(new URL(X.url||"/",`http://${X.headers.host}`).searchParams.get("token")!==$){G.write(`HTTP/1.1 401 Unauthorized\r
292
+ </html>`}function M1(Q,Z){let $=m(),V=new Map,K=new Map,Y=R1((z,J)=>{if(new URL(z.url||"/",`http://${z.headers.host||"localhost"}`).pathname==="/health"){J.writeHead(200,{"content-type":"application/json","access-control-allow-origin":"*"}),J.end(JSON.stringify({status:"ok",sessions:V.size,version:v.version}));return}J.writeHead(200,{"content-type":"text/html; charset=utf-8","cache-control":"no-cache"}),J.end(L1(v.version,Z))});Y.on("upgrade",(z,J)=>{if(new URL(z.url||"/",`http://${z.headers.host||"localhost"}`).searchParams.get("token")!==Z){J.write(`HTTP/1.1 401 Unauthorized\r
325
293
  \r
326
- `),G.destroy();return}let N=X.headers["sec-websocket-key"];if(!N){G.destroy();return}let _=R1("sha1").update(N+"258EAFA5-E914-47DA-95CA-5ABB5C0A2C15").digest("base64");G.write(`HTTP/1.1 101 Switching Protocols\r
294
+ `),J.destroy();return}let _=z.headers["sec-websocket-key"];if(!_){J.destroy();return}let q=B1("sha1").update(_+"258EAFA5-E914-47DA-95CA-5ABB5C0A2C15").digest("base64");J.write(`HTTP/1.1 101 Switching Protocols\r
327
295
  Upgrade: websocket\r
328
296
  Connection: Upgrade\r
329
- Sec-WebSocket-Accept: `+_+`\r
297
+ Sec-WebSocket-Accept: `+q+`\r
330
298
  \r
331
- `);let U=Buffer.alloc(0),B=(R)=>{try{G.write(B1(JSON.stringify(R)))}catch{}};G.on("data",(R)=>{U=Buffer.concat([U,R]);while(U.length>0){let E=D1(U);if(!E)break;let O=2,D=U[1]&127;if(D===126)O=4;else if(D===127)O=10;if(U[1]&128)O+=4;if(O+=E.payload.length,U=U.slice(O),E.opcode===8){G.end();return}if(E.opcode===9){let H=Buffer.alloc(2+E.payload.length);H[0]=138,H[1]=E.payload.length,E.payload.copy(H,2),G.write(H);continue}if(E.opcode===10)continue;try{let H=JSON.parse(E.payload.toString("utf8"));P1(H,B,V,J,Q)}catch{}}});let q=setInterval(()=>{try{O1(G)}catch{clearInterval(q)}},30000);G.on("close",()=>{clearInterval(q);for(let[R,E]of V)if(E.send===B)V.delete(R)}),G.on("error",()=>G.destroy())}),z.listen(Z,()=>{console.log(),console.log(` ${K.green("●")} ${K.bold("TeniCLI Remote Server")}`),console.log(),console.log(` ${K.gray("URL:")} ${K.cyan(`http://localhost:${Z}`)}`),console.log(` ${K.gray("Password:")} ${K.yellow($)}`),console.log(` ${K.gray("Version:")} v${b.version}`),console.log(),console.log(` ${K.gray("Tip: Use VS Code Port Forward to make it public")}`),console.log(` ${K.gray("Press Ctrl+C to stop")}`),console.log()})}function P1(Z,$,Q,V,J){switch(Z.type){case"new_session":{let z={...J,provider:{...J.provider}},X=new h(z);X.autoMode=!0,X.onOutput=(G)=>{$({...G,sessionId:Z.sessionId})},X.onConfirm=(G,W,Y)=>{return new Promise((N)=>{$({type:"confirm",sessionId:Z.sessionId,id:G,tool:W,preview:Y}),V.set(G,N),setTimeout(()=>{if(V.has(G))V.delete(G),N("y")},60000)})},Q.set(Z.sessionId,{id:Z.sessionId,chat:X,send:$,alive:!0});break}case"close_session":Q.delete(Z.sessionId);break;case"message":{let z=Q.get(Z.sessionId);if(!z)return;let X=Z.text?.trim();if(!X)return;if(X==="/clear"){z.chat.clear(),$({type:"system",sessionId:Z.sessionId,text:"Conversation cleared"});return}if(X==="/compact"){z.chat.compact();return}if(X==="/cost"){let G=z.chat.stats;$({type:"tokens",sessionId:Z.sessionId,input:G.input,output:G.output,messages:z.chat.messageCount});return}z.chat.send(X).catch((G)=>{$({type:"error",sessionId:Z.sessionId,message:G.message})});break}case"confirm_response":{let z=V.get(Z.id);if(z)V.delete(Z.id),z(Z.answer);break}}}var z0=M(()=>{V0();e();w();K0()});e();V0();Q0();w();import{writeFileSync as L1,existsSync as M1}from"fs";import{join as I1,relative as y0}from"path";import{randomBytes as h0}from"crypto";import{existsSync as M0,readFileSync as I0,writeFileSync as C0,mkdirSync as Y1,readdirSync as N1,unlinkSync as T0}from"fs";import{join as S}from"path";var L0=20;function v(){let Z=process.env.HOME||process.env.USERPROFILE||"",$=S(Z,".tenicli","history");if(!M0($))Y1($,{recursive:!0});return $}function _1(){return Date.now().toString(36)+Math.random().toString(36).slice(2,6)}function S0(Z){let $=S(v(),`${Z.id}.json`);C0($,JSON.stringify(Z,null,0),"utf8"),U1()}function G0(){let Z=v();try{return N1(Z).filter(($)=>$.endsWith(".json")).map(($)=>{try{return JSON.parse(I0(S(Z,$),"utf8"))}catch{return null}}).filter(($)=>$!==null).sort(($,Q)=>Q.updatedAt.localeCompare($.updatedAt))}catch{return[]}}function U1(){let Z=G0();if(Z.length<=L0)return;let $=v();for(let Q of Z.slice(L0))try{T0(S($,`${Q.id}.json`))}catch{}}function n(Z){let $=new Date().toISOString();return{id:_1(),title:"New conversation",model:Z,createdAt:$,updatedAt:$,messages:[],tokens:{input:0,output:0}}}function b0(Z){let $=S(v(),"__resume__.json");return C0($,JSON.stringify(Z,null,0),"utf8"),$}function k0(){let Z=S(v(),"__resume__.json");try{if(M0(Z)){let $=JSON.parse(I0(Z,"utf8"));return T0(Z),$}}catch{}return null}K0();var v0=b.version;function C1(Z){let $={prompt:"",print:!1},Q=0;while(Q<Z.length){switch(Z[Q]){case"serve":$.serve=!0;break;case"--port":$.port=parseInt(Z[++Q]);break;case"--password":$.password=Z[++Q];break;case"-p":case"--print":if($.print=!0,Q+1<Z.length&&!Z[Q+1].startsWith("-"))$.prompt=Z[++Q];break;case"-m":case"--model":$.model=Z[++Q];break;case"--base-url":$.baseUrl=Z[++Q];break;case"-v":case"--version":console.log(`teni v${v0}`),process.exit(0);case"-h":case"--help":T1(),process.exit(0);default:if(!Z[Q].startsWith("-"))$.prompt=Z.slice(Q).join(" "),Q=Z.length}Q++}return $}function T1(){console.log(`
332
- ${K.bold(K.blue("TeniCLI"))} — Lightweight AI Coding Agent
299
+ `);let A=Buffer.alloc(0),U=(R)=>{try{J.write(F1(JSON.stringify(R)))}catch{}};J.on("data",(R)=>{A=Buffer.concat([A,R]);while(A.length>0){let H=j1(A);if(!H)break;if(A=A.slice(H.totalSize),H.opcode===8){J.end();return}if(H.opcode===9){let B=Buffer.alloc(2+H.payload.length);B[0]=138,B[1]=H.payload.length,H.payload.copy(B,2),J.write(B);continue}if(H.opcode===10)continue;try{let B=JSON.parse(H.payload.toString("utf8"));I1(B,U,V,K,$)}catch{}}});let D=setInterval(()=>{try{P1(J)}catch{clearInterval(D)}},30000);J.on("close",()=>{clearInterval(D);for(let[R,H]of V)if(H.send===U)V.delete(R)}),J.on("error",()=>{try{J.destroy()}catch{}})});let X=()=>{Y.close(),Y0.delete(Q)};return Y.listen(Q,"0.0.0.0",()=>{Y0.set(Q,{password:Z,close:X})}),{close:X}}function I1(Q,Z,$,V,K){switch(Q.type){case"new_session":{let Y={...K,provider:{...K.provider}},X=new y(Y);X.autoMode=!0,X.onOutput=(z)=>{Z({...z,sessionId:Q.sessionId})},X.onConfirm=(z,J,N)=>{return new Promise((W)=>{Z({type:"confirm",sessionId:Q.sessionId,id:z,tool:J,preview:N}),V.set(z,W),setTimeout(()=>{if(V.has(z))V.delete(z),W("y")},60000)})},$.set(Q.sessionId,{id:Q.sessionId,chat:X,send:Z,alive:!0});break}case"close_session":$.delete(Q.sessionId);break;case"message":{let Y=$.get(Q.sessionId);if(!Y)return;let X=Q.text?.trim();if(!X)return;if(X==="/clear"){Y.chat.clear(),Z({type:"system",sessionId:Q.sessionId,text:"Conversation cleared"});return}if(X==="/compact"){Y.chat.compact();return}if(X==="/cost"){let z=Y.chat.stats;Z({type:"tokens",sessionId:Q.sessionId,input:z.input,output:z.output,messages:Y.chat.messageCount});return}Y.chat.send(X).catch((z)=>{Z({type:"error",sessionId:Q.sessionId,message:z.message})});break}case"confirm_response":{let Y=V.get(Q.id);if(Y)V.delete(Q.id),Y(Q.answer);break}}}var Y0;var W0=P(()=>{K0();e();z0();Y0=new Map});e();K0();$0();G0();a();import{writeFileSync as C1,existsSync as T1}from"fs";import{join as S1,relative as f0}from"path";import{randomBytes as g0}from"crypto";import{existsSync as T0,readFileSync as S0,writeFileSync as b0,mkdirSync as _1,readdirSync as U1,unlinkSync as k0}from"fs";import{join as b}from"path";var C0=20;function h(){let Q=process.env.HOME||process.env.USERPROFILE||"",Z=b(Q,".tenicli","history");if(!T0(Z))_1(Z,{recursive:!0});return Z}function q1(){return Date.now().toString(36)+Math.random().toString(36).slice(2,6)}function x0(Q){let Z=b(h(),`${Q.id}.json`);b0(Z,JSON.stringify(Q,null,0),"utf8"),E1()}function X0(){let Q=h();try{return U1(Q).filter((Z)=>Z.endsWith(".json")).map((Z)=>{try{return JSON.parse(S0(b(Q,Z),"utf8"))}catch{return null}}).filter((Z)=>Z!==null).sort((Z,$)=>$.updatedAt.localeCompare(Z.updatedAt))}catch{return[]}}function E1(){let Q=X0();if(Q.length<=C0)return;let Z=h();for(let $ of Q.slice(C0))try{k0(b(Z,`${$.id}.json`))}catch{}}function n(Q){let Z=new Date().toISOString();return{id:q1(),title:"New conversation",model:Q,createdAt:Z,updatedAt:Z,messages:[],tokens:{input:0,output:0}}}function w0(Q){let Z=b(h(),"__resume__.json");return b0(Z,JSON.stringify(Q,null,0),"utf8"),Z}function y0(){let Q=b(h(),"__resume__.json");try{if(T0(Q)){let Z=JSON.parse(S0(Q,"utf8"));return k0(Q),Z}}catch{}return null}z0();var u0=v.version;function b1(Q){let Z={prompt:"",print:!1},$=0;while($<Q.length){switch(Q[$]){case"serve":Z.serve=!0;break;case"--port":Z.port=parseInt(Q[++$]);break;case"--password":Z.password=Q[++$];break;case"-p":case"--print":if(Z.print=!0,$+1<Q.length&&!Q[$+1].startsWith("-"))Z.prompt=Q[++$];break;case"-m":case"--model":Z.model=Q[++$];break;case"--base-url":Z.baseUrl=Q[++$];break;case"-v":case"--version":console.log(`teni v${u0}`),process.exit(0);case"-h":case"--help":k1(),process.exit(0);default:if(!Q[$].startsWith("-"))Z.prompt=Q.slice($).join(" "),$=Q.length}$++}return Z}function k1(){console.log(`
300
+ ${G.bold(G.blue("TeniCLI"))} — Lightweight AI Coding Agent
333
301
 
334
- ${K.bold("USAGE")}
302
+ ${G.bold("USAGE")}
335
303
  teni Start chatting
336
304
  teni "prompt" Start with a prompt
337
305
  teni -p "prompt" Non-interactive mode
338
306
  teni serve Start web remote server
339
307
 
340
- ${K.bold("OPTIONS")}
308
+ ${G.bold("OPTIONS")}
341
309
  -p, --print <prompt> Print response and exit
342
310
  -m, --model <model> Override model
343
311
  --base-url <url> Override API base URL
344
312
  -v, --version Show version
345
313
  -h, --help Show help
346
314
 
347
- ${K.bold("SERVE OPTIONS")}
315
+ ${G.bold("SERVE OPTIONS")}
348
316
  --port <port> Server port (default: 3000)
349
317
  --password <pw> Access password (auto-generated if omitted)
350
318
 
351
- ${K.bold("IN-CHAT")}
319
+ ${G.bold("IN-CHAT")}
352
320
  /model Select model /auth Set API key
353
321
  /mode Ask/Auto toggle /compact Summarize chat
354
322
  /diff Files changed /undo Revert last write
355
323
  /init Create TENICLI.md /clear New conversation
356
324
  /update Update tenicli /cost Token usage
357
325
  /exit Quit \\\\ Multiline input
358
- `)}async function S1(Z,$){switch(Z.toLowerCase().split(" ")[0]){case"/exit":case"/quit":case"/q":console.log(`
359
- ${K.gray("Bye!")} \uD83D\uDC4B
360
- `),process.exit(0);case"/clear":return $.clear(),y.clear(),console.log(` ${A.ok} Conversation cleared`),!0;case"/compact":return await $.compact(),!0;case"/diff":{let Q=y.getChanges();if(Q.length===0)console.log(` ${K.gray("No files changed in this session.")}`);else{console.log(`
361
- ${K.bold("Files changed this session:")}`);for(let V of Q){let J=y0($.cfg.cwd,V.path),z=V.isNew?K.green("[NEW]"):K.yellow("[MOD]");console.log(` ${z} ${K.cyan(J)} ${K.gray(`(${V.lines} lines)`)}`)}console.log(` ${K.gray(`total: ${Q.length} files`)}`)}return!0}case"/undo":{let Q=y.undo();if(!Q)console.log(` ${K.gray("Nothing to undo.")}`);else{let V=y0($.cfg.cwd,Q.path);if(Q.restored)console.log(` ${A.ok} Restored: ${K.cyan(V)}`);else console.log(` ${A.ok} Deleted (was new): ${K.cyan(V)}`)}return!0}case"/init":{let Q=I1($.cfg.cwd,"TENICLI.md");if(M1(Q))console.log(` ${A.warn} TENICLI.md already exists.`);else L1(Q,k1,"utf8"),console.log(` ${A.ok} Created ${K.cyan("TENICLI.md")}`);return!0}case"/mode":{$.autoMode=!$.autoMode;let Q=$.autoMode?K.yellow("auto"):K.green("ask");return console.log(` ${A.ok} Mode: ${Q} ${K.gray($.autoMode?"(tools run without asking)":"(confirm write/exec)")}`),!0}case"/cost":{let Q=$.stats;return console.log(` ${A.ai} ${K.blue(String(Q.input))}↑ input ${K.blue(String(Q.output))}↓ output ${K.gray(`(${$.messageCount} msgs)`)}`),!0}case"/model":{let Q=u(),V=Q.customModels||[],J=[...k.map((G)=>({id:G.id,name:G.name,provider:G.provider,speed:G.speed,custom:!1})),...V.map((G)=>({id:G.id,name:G.id,provider:G.provider,speed:"custom",custom:!0}))],z=J.map((G)=>({label:`${G.name} ${$.cfg.provider.model===G.id?K.green("●"):""}`,desc:`${G.provider} • ${G.speed}`}));z.push({label:"Custom model...",desc:"type model ID"});let X=await x("Select model",z);if(X===-1)return console.log(` ${K.gray("Cancelled")}`),!0;if(X<J.length){let G=J[X];$.cfg.provider.model=G.id,$.cfg.provider.type=G.provider;let W=G.provider==="openai"?process.env.OPENAI_API_KEY||Q.keys?.openai||"":process.env.ANTHROPIC_API_KEY||Q.keys?.anthropic||"";if(W)$.cfg.provider.apiKey=W;if(!Q.baseUrls?.[G.provider])$.cfg.provider.baseUrl=G.provider==="openai"?"https://api.openai.com":"https://api.anthropic.com";p({activeModel:G.id}),console.log(` ${A.ok} Model: ${K.blue(G.name)}`)}else{let G=await x("Provider for custom model",[{label:"Anthropic",desc:"Claude-compatible"},{label:"OpenAI",desc:"GPT-compatible"}]);if(G===-1)return console.log(` ${K.gray("Cancelled")}`),!0;let W=G===0?"anthropic":"openai",Y=await P(` ${K.gray("model ID")} ${K.blue("❯")} `);if(!Y.trim())return console.log(` ${K.gray("Cancelled")}`),!0;let N=Y.trim();$.cfg.provider.model=N,$.cfg.provider.type=W;let _=V.filter((B)=>B.id!==N);_.push({id:N,provider:W}),p({activeModel:N,customModels:_});let U=W==="openai"?process.env.OPENAI_API_KEY||Q.keys?.openai||"":process.env.ANTHROPIC_API_KEY||Q.keys?.anthropic||"";if(U)$.cfg.provider.apiKey=U;console.log(` ${A.ok} Model: ${K.blue(N)} ${K.gray("(saved to list)")}`)}return!0}case"/auth":{let Q=await x("Provider",[{label:"Anthropic",desc:"Claude models"},{label:"OpenAI",desc:"GPT models"},{label:"Custom",desc:"Anthropic-compatible proxy"}]);if(Q===-1)return console.log(` ${K.gray("Cancelled")}`),!0;let J=["anthropic","openai","anthropic"][Q],z=await P(` ${K.gray("API Key")} ${K.blue("❯")} `);if(!z.trim())return console.log(` ${A.warn} Cancelled`),!0;let X={[J]:z.trim()},G={};if(Q===2){let W=await P(` ${K.gray("Base URL")} ${K.blue("❯")} `);if(W.trim())G[J]=W.trim()}if(p({keys:X,baseUrls:G}),$.cfg.provider.apiKey=z.trim(),$.cfg.provider.type=J,G[J])$.cfg.provider.baseUrl=G[J];return console.log(` ${A.ok} ${J} key saved to ~/.tenicli/config.json`),!0}case"/help":return console.log(`
362
- ${K.bold("Commands")}
363
- ${K.blue("/model")} Select AI model
364
- ${K.blue("/auth")} Set API key
365
- ${K.blue("/mode")} Toggle ask/auto ${K.gray("(confirm before write/exec)")}
366
- ${K.blue("/compact")} Summarize conversation ${K.gray("(save tokens)")}
367
- ${K.blue("/diff")} List files changed this session
368
- ${K.blue("/undo")} Revert last file write
369
- ${K.blue("/init")} Create TENICLI.md template
370
- ${K.blue("/remote")} Start web remote access
371
- ${K.blue("/history")} Browse past conversations
372
- ${K.blue("/update")} Update to latest version
373
- ${K.blue("/clear")} New conversation
374
- ${K.blue("/cost")} Show token usage
375
- ${K.blue("/exit")} Quit
376
- ${K.gray("\\\\")} Continue on next line`),!0;case"/remote":{let{getLocalIPs:Q,renderQR:V}=await Promise.resolve().then(() => (w0(),x0)),J=3000,z=h0(6).toString("hex"),W=`http://${Q()[0]||"localhost"}:3000`,{startServer:Y}=await Promise.resolve().then(() => (z0(),J0));return Y(3000,z),console.log(),l([K.bold(K.green("Remote Access Enabled")),"",`${K.gray("URL:")} ${K.cyan(W)}`,`${K.gray("Password:")} ${K.yellow(z)}`,"",K.gray("Anyone on the same WiFi can access this URL."),K.gray("For public access, use VS Code Port Forward or ngrok.")],58),console.log(),console.log(V(W)),console.log(),console.log(` ${K.gray("Press Ctrl+C to stop the server")}`),console.log(),!0}case"/history":{let Q=G0();if(Q.length===0)return console.log(` ${K.gray("No saved conversations.")}`),!0;let V=Q.slice(0,10).map((X)=>({label:X.title.slice(0,40),desc:`${X.model} • ${new Date(X.updatedAt).toLocaleDateString()}`})),J=await x("Resume conversation",V);if(J===-1)return console.log(` ${K.gray("Cancelled")}`),!0;let z=Q[J];return $.clear(),$.importState({messages:z.messages,tokens:z.tokens}),$.__convId=z.id,console.log(` ${A.ok} Restored: ${K.blue(z.title)} ${K.gray(`(${z.messages.length} msgs)`)}`),!0}case"/update":{console.log(`
377
- ${A.tool} ${K.yellow("Updating tenicli...")}`);try{let Q=$.exportState(),V=n($.cfg.provider.model);V.title=$.getTitle(),V.messages=Q.messages,V.tokens=Q.tokens,b0(V);let{execSync:J,spawn:z}=await import("child_process");return J("npm i -g tenicli@latest 2>&1",{encoding:"utf8"}),console.log(` ${A.ok} ${K.green("Updated! Restarting...")}
378
- `),z(process.execPath,process.argv.slice(1),{stdio:"inherit",detached:!1}).on("exit",(G)=>process.exit(G||0)),!0}catch(Q){L(`Update failed: ${Q.message}`)}return!0}default:return console.log(` ${A.warn} Unknown: ${Z.split(" ")[0]} — try /help`),!0}}async function b1(){let Z=C1(process.argv.slice(2)),$=m();if(Z.model)$.provider.model=Z.model;if(Z.baseUrl)$.provider.baseUrl=Z.baseUrl;if(Z.serve){let Y=Z.port||3000,N=Z.password||h0(6).toString("hex"),{startServer:_}=await Promise.resolve().then(() => (z0(),J0));_(Y,N);return}let Q=new h($);if(Z.print&&Z.prompt){if(!$.provider.apiKey)L("No API key. Run: teni then /auth"),process.exit(1);await Q.send(Z.prompt),process.exit(0)}q0(v0);let V=k.find((Y)=>Y.id===$.provider.model)?.name||$.provider.model,J=Q.autoMode?K.yellow("auto"):K.green("ask"),z=[`${K.gray("model")} ${K.blue(V)} ${K.gray("mode")} ${J} ${K.gray("cwd")} ${K.cyan($.cwd)}`];if(!$.provider.apiKey)z.push(""),z.push(`${A.warn} ${K.yellow("No API key configured. Run /auth to set one.")}`);l(z,60),console.log();let X=k0(),G=n($.provider.model);if(X)Q.importState({messages:X.messages,tokens:X.tokens}),G=X,console.log(` ${A.ok} ${K.green("Session restored after update")} ${K.gray(`(${X.messages.length} msgs)`)}`),console.log();let W=()=>{if(Q.messageCount===0)return;let Y=Q.exportState();G.title=Q.getTitle(),G.messages=Y.messages,G.tokens=Y.tokens,G.model=$.provider.model,G.updatedAt=new Date().toISOString(),S0(G)};if(process.on("SIGINT",()=>{W(),console.log(`
379
- ${K.gray("Bye!")} \uD83D\uDC4B
380
- `),process.exit(0)}),Z.prompt){if(console.log(` ${A.prompt} ${Z.prompt}`),$.provider.apiKey)await Q.send(Z.prompt);W()}while(!0)try{let N=(await A0()).trim();if(!N)continue;if(N.startsWith("/")){if(N==="/clear")W(),G=n($.provider.model);await S1(N,Q);continue}if(!Q.cfg.provider.apiKey){console.log(` ${A.warn} ${K.yellow("No API key. Run /auth first.")}`);continue}await Q.send(N),W()}catch(Y){if(Y.message==="EOF")W(),console.log(`
381
- ${K.gray("Bye!")} \uD83D\uDC4B
382
- `),process.exit(0);L(Y.message)}}b1().catch((Z)=>{L(Z.message),process.exit(1)});var k1=`# Project Instructions
326
+ `)}async function x1(Q,Z){switch(Q.toLowerCase().split(" ")[0]){case"/exit":case"/quit":case"/q":console.log(`
327
+ ${G.gray("Bye!")} \uD83D\uDC4B
328
+ `),process.exit(0);case"/clear":return Z.clear(),w.clear(),console.log(` ${E.ok} Conversation cleared`),!0;case"/compact":return await Z.compact(),!0;case"/diff":{let $=w.getChanges();if($.length===0)console.log(` ${G.gray("No files changed in this session.")}`);else{console.log(`
329
+ ${G.bold("Files changed this session:")}`);for(let V of $){let K=f0(Z.cfg.cwd,V.path),Y=V.isNew?G.green("[NEW]"):G.yellow("[MOD]");console.log(` ${Y} ${G.cyan(K)} ${G.gray(`(${V.lines} lines)`)}`)}console.log(` ${G.gray(`total: ${$.length} files`)}`)}return!0}case"/undo":{let $=w.undo();if(!$)console.log(` ${G.gray("Nothing to undo.")}`);else{let V=f0(Z.cfg.cwd,$.path);if($.restored)console.log(` ${E.ok} Restored: ${G.cyan(V)}`);else console.log(` ${E.ok} Deleted (was new): ${G.cyan(V)}`)}return!0}case"/init":{let $=S1(Z.cfg.cwd,"TENICLI.md");if(T1($))console.log(` ${E.warn} TENICLI.md already exists.`);else C1($,y1,"utf8"),console.log(` ${E.ok} Created ${G.cyan("TENICLI.md")}`);return!0}case"/mode":{Z.autoMode=!Z.autoMode;let $=Z.autoMode?G.yellow("auto"):G.green("ask");return console.log(` ${E.ok} Mode: ${$} ${G.gray(Z.autoMode?"(tools run without asking)":"(confirm write/exec)")}`),!0}case"/cost":{let $=Z.stats;return console.log(` ${E.ai} ${G.blue(String($.input))}↑ input ${G.blue(String($.output))}↓ output ${G.gray(`(${Z.messageCount} msgs)`)}`),!0}case"/model":{let $=u(),V=$.customModels||[],K=[...k.map((z)=>({id:z.id,name:z.name,provider:z.provider,speed:z.speed,custom:!1})),...V.map((z)=>({id:z.id,name:z.id,provider:z.provider,speed:"custom",custom:!0}))],Y=K.map((z)=>({label:`${z.name} ${Z.cfg.provider.model===z.id?G.green("●"):""}`,desc:`${z.provider} • ${z.speed}`}));Y.push({label:"Custom model...",desc:"type model ID"});let X=await T("Select model",Y);if(X===-1)return console.log(` ${G.gray("Cancelled")}`),!0;if(X<K.length){let z=K[X];Z.cfg.provider.model=z.id,Z.cfg.provider.type=z.provider;let J=z.provider==="openai"?process.env.OPENAI_API_KEY||$.keys?.openai||"":process.env.ANTHROPIC_API_KEY||$.keys?.anthropic||"";if(J)Z.cfg.provider.apiKey=J;if(!$.baseUrls?.[z.provider])Z.cfg.provider.baseUrl=z.provider==="openai"?"https://api.openai.com":"https://api.anthropic.com";p({activeModel:z.id}),console.log(` ${E.ok} Model: ${G.blue(z.name)}`)}else{let z=await T("Provider for custom model",[{label:"Anthropic",desc:"Claude-compatible"},{label:"OpenAI",desc:"GPT-compatible"}]);if(z===-1)return console.log(` ${G.gray("Cancelled")}`),!0;let J=z===0?"anthropic":"openai",N=await L(` ${G.gray("model ID")} ${G.blue("❯")} `);if(!N.trim())return console.log(` ${G.gray("Cancelled")}`),!0;let W=N.trim();Z.cfg.provider.model=W,Z.cfg.provider.type=J;let _=V.filter((A)=>A.id!==W);_.push({id:W,provider:J}),p({activeModel:W,customModels:_});let q=J==="openai"?process.env.OPENAI_API_KEY||$.keys?.openai||"":process.env.ANTHROPIC_API_KEY||$.keys?.anthropic||"";if(q)Z.cfg.provider.apiKey=q;console.log(` ${E.ok} Model: ${G.blue(W)} ${G.gray("(saved to list)")}`)}return!0}case"/auth":{let $=await T("Provider",[{label:"Anthropic",desc:"Claude models"},{label:"OpenAI",desc:"GPT models"},{label:"Custom",desc:"Anthropic-compatible proxy"}]);if($===-1)return console.log(` ${G.gray("Cancelled")}`),!0;let K=["anthropic","openai","anthropic"][$],Y=await L(` ${G.gray("API Key")} ${G.blue("❯")} `);if(!Y.trim())return console.log(` ${E.warn} Cancelled`),!0;let X={[K]:Y.trim()},z={};if($===2){let J=await L(` ${G.gray("Base URL")} ${G.blue("❯")} `);if(J.trim())z[K]=J.trim()}if(p({keys:X,baseUrls:z}),Z.cfg.provider.apiKey=Y.trim(),Z.cfg.provider.type=K,z[K])Z.cfg.provider.baseUrl=z[K];return console.log(` ${E.ok} ${K} key saved to ~/.tenicli/config.json`),!0}case"/help":return console.log(`
330
+ ${G.bold("Commands")}
331
+ ${G.blue("/model")} Select AI model
332
+ ${G.blue("/auth")} Set API key
333
+ ${G.blue("/mode")} Toggle ask/auto ${G.gray("(confirm before write/exec)")}
334
+ ${G.blue("/compact")} Summarize conversation ${G.gray("(save tokens)")}
335
+ ${G.blue("/diff")} List files changed this session
336
+ ${G.blue("/undo")} Revert last file write
337
+ ${G.blue("/init")} Create TENICLI.md template
338
+ ${G.blue("/remote")} Start web remote access
339
+ ${G.blue("/history")} Browse past conversations
340
+ ${G.blue("/quota")} Show API rate limits
341
+ ${G.blue("/update")} Update to latest version
342
+ ${G.blue("/clear")} New conversation
343
+ ${G.blue("/cost")} Show token usage
344
+ ${G.blue("/exit")} Quit
345
+ ${G.gray("\\\\")} Continue on next line`),!0;case"/remote":{let{getLocalIPs:$}=await Promise.resolve().then(() => (v0(),h0)),{startServer:V,getActiveServers:K}=await Promise.resolve().then(() => (W0(),N0)),Y=K();if(Y.size>0){let _=Array.from(Y.entries()).map(([A,U])=>({label:`Port ${A} ${G.green("")}`,desc:`password: ${U.password}`}));_.push({label:G.cyan("+ New server"),desc:"start on random port"}),_.push({label:G.yellow("Stop all"),desc:"close all remote servers"});let q=await T("Remote servers",_);if(q===-1)return console.log(` ${G.gray("Cancelled")}`),!0;if(q<Y.size){let A=Array.from(Y.entries())[q],D=$()[0]||"localhost";return console.log(`
346
+ ${G.gray("URL:")} ${G.cyan(`http://${D}:${A[0]}`)}`),console.log(` ${G.gray("Password:")} ${G.yellow(A[1].password)}`),!0}else if(q===Y.size);else{for(let[A,U]of Y)U.close();return console.log(` ${E.ok} All remote servers stopped`),!0}}let X=3000+Math.floor(Math.random()*7000),z=g0(6).toString("hex"),W=`http://${$()[0]||"localhost"}:${X}`;return V(X,z),console.log(),x([G.bold(G.green("Remote Access Enabled")),"",`${G.gray("URL:")} ${G.cyan(W)}`,`${G.gray("Password:")} ${G.yellow(z)}`,`${G.gray("Port:")} ${G.blue(String(X))}`,"",G.gray("Anyone on the same WiFi can access this URL."),G.gray("Use /remote again to manage servers.")],58),console.log(),!0}case"/quota":{let $=Q0;if(!$.requestsLimit&&!$.tokensLimit)return console.log(` ${G.gray("No rate limit data yet. Send a message first.")}`),!0;let V=[G.bold("API Rate Limits"),""];if($.requestsLimit!==void 0){let K=$.requestsLimit-($.requestsRemaining||0),Y=Math.round(($.requestsRemaining||0)/$.requestsLimit*100),X=Y>50?G.green:Y>20?G.yellow:G.pink;V.push(`${G.gray("Requests:")} ${X(String($.requestsRemaining))}/${$.requestsLimit} remaining ${G.gray(`(${Y}%)`)}`)}if($.tokensLimit!==void 0){let K=Math.round(($.tokensRemaining||0)/$.tokensLimit*100),Y=K>50?G.green:K>20?G.yellow:G.pink;V.push(`${G.gray("Tokens:")} ${Y(String($.tokensRemaining?.toLocaleString()))}/${$.tokensLimit.toLocaleString()} remaining ${G.gray(`(${K}%)`)}`)}if($.requestsReset){let K=new Date($.requestsReset);V.push(`${G.gray("Resets at:")} ${G.cyan(K.toLocaleTimeString())}`)}return console.log(),x(V,58),!0}case"/history":{let $=X0();if($.length===0)return console.log(` ${G.gray("No saved conversations.")}`),!0;let V=$.slice(0,10).map((X)=>({label:X.title.slice(0,40),desc:`${X.model} • ${new Date(X.updatedAt).toLocaleDateString()}`})),K=await T("Resume conversation",V);if(K===-1)return console.log(` ${G.gray("Cancelled")}`),!0;let Y=$[K];return Z.clear(),Z.importState({messages:Y.messages,tokens:Y.tokens}),Z.__convId=Y.id,console.log(` ${E.ok} Restored: ${G.blue(Y.title)} ${G.gray(`(${Y.messages.length} msgs)`)}`),!0}case"/update":{console.log(`
347
+ ${E.tool} ${G.yellow("Updating tenicli...")}`);try{let $=Z.exportState(),V=n(Z.cfg.provider.model);V.title=Z.getTitle(),V.messages=$.messages,V.tokens=$.tokens,w0(V);let{execSync:K,spawn:Y}=await import("child_process");return K("npm i -g tenicli@latest 2>&1",{encoding:"utf8"}),console.log(` ${E.ok} ${G.green("Updated! Restarting...")}
348
+ `),Y(process.execPath,process.argv.slice(1),{stdio:"inherit",detached:!1}).on("exit",(z)=>process.exit(z||0)),!0}catch($){M(`Update failed: ${$.message}`)}return!0}default:return console.log(` ${E.warn} Unknown: ${Q.split(" ")[0]} — try /help`),!0}}async function w1(){let Q=b1(process.argv.slice(2)),Z=m();if(Q.model)Z.provider.model=Q.model;if(Q.baseUrl)Z.provider.baseUrl=Q.baseUrl;if(Q.serve){let N=Q.port||3000,W=Q.password||g0(6).toString("hex"),{startServer:_}=await Promise.resolve().then(() => (W0(),N0));_(N,W);return}let $=new y(Z);if(Q.print&&Q.prompt){if(!Z.provider.apiKey)M("No API key. Run: teni then /auth"),process.exit(1);await $.send(Q.prompt),process.exit(0)}H0(u0);let V=k.find((N)=>N.id===Z.provider.model)?.name||Z.provider.model,K=$.autoMode?G.yellow("auto"):G.green("ask"),Y=[`${G.gray("model")} ${G.blue(V)} ${G.gray("mode")} ${K} ${G.gray("cwd")} ${G.cyan(Z.cwd)}`];if(!Z.provider.apiKey)Y.push(""),Y.push(`${E.warn} ${G.yellow("No API key configured. Run /auth to set one.")}`);x(Y,60),console.log();let X=y0(),z=n(Z.provider.model);if(X)$.importState({messages:X.messages,tokens:X.tokens}),z=X,console.log(` ${E.ok} ${G.green("Session restored after update")} ${G.gray(`(${X.messages.length} msgs)`)}`),console.log();let J=()=>{if($.messageCount===0)return;let N=$.exportState();z.title=$.getTitle(),z.messages=N.messages,z.tokens=N.tokens,z.model=Z.provider.model,z.updatedAt=new Date().toISOString(),x0(z)};if(process.on("SIGINT",()=>{J(),console.log(`
349
+ ${G.gray("Bye!")} \uD83D\uDC4B
350
+ `),process.exit(0)}),Q.prompt){if(console.log(` ${E.prompt} ${Q.prompt}`),Z.provider.apiKey)await $.send(Q.prompt);J()}while(!0)try{let W=(await D0()).trim();if(!W)continue;if(W.startsWith("/")){if(W==="/clear")J(),z=n(Z.provider.model);await x1(W,$);continue}if(!$.cfg.provider.apiKey){console.log(` ${E.warn} ${G.yellow("No API key. Run /auth first.")}`);continue}await $.send(W),J()}catch(N){if(N.message==="EOF")J(),console.log(`
351
+ ${G.gray("Bye!")} \uD83D\uDC4B
352
+ `),process.exit(0);M(N.message)}}w1().catch((Q)=>{M(Q.message),process.exit(1)});var y1=`# Project Instructions
383
353
 
384
354
  ## Overview
385
355
  Describe your project here so the AI understands the context.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tenicli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Lightweight AI coding CLI — fast, compact, multi-provider",
5
5
  "type": "module",
6
6
  "bin": {