tenicli 0.1.0 → 0.1.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 +39 -37
  2. package/package.json +15 -3
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{createRequire as zQ}from"node:module";var GQ=zQ(import.meta.url);import{existsSync as C,readFileSync as b,writeFileSync as VQ,mkdirSync as XQ}from"fs";import{join as E}from"path";var D=[{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"}];function h(){let Q=process.env.HOME||process.env.USERPROFILE||"";return E(Q,".tenicli")}function w(){return E(h(),"config.json")}function F(){try{if(C(w()))return JSON.parse(b(w(),"utf8"))}catch{}return{}}function P(Q){let Z=h();if(!C(Z))XQ(Z,{recursive:!0});let $=F(),z={...$,...Q,keys:{...$.keys,...Q.keys},baseUrls:{...$.baseUrls,...Q.baseUrls}};VQ(w(),JSON.stringify(z,null,2),"utf8")}function m(){let Q=process.cwd();u(E(Q,".tenicli.env")),u(E(Q,".env"));let Z=F(),$=process.env,z=$.TENICLI_MODEL||Z.activeModel||D[0].id,X=D.find((W)=>W.id===z)?.provider||$.TENICLI_PROVIDER||"anthropic",Y=YQ(X,Z,$),K=X==="openai"?"https://api.openai.com":"https://api.anthropic.com",q=$.TENICLI_BASE_URL||Z.baseUrls?.[X]||K;return{provider:{type:X,baseUrl:q,apiKey:Y,model:z},maxTokens:parseInt($.TENICLI_MAX_TOKENS||"8192"),systemPrompt:KQ(Q),cwd:Q}}function YQ(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 u(Q){try{if(!C(Q))return;for(let Z of b(Q,"utf8").split(`
3
- `)){let $=Z.trim();if(!$||$.startsWith("#"))continue;let z=$.indexOf("=");if(z===-1)continue;let V=$.slice(0,z).trim(),X=$.slice(z+1).trim();if(X.startsWith('"')&&X.endsWith('"')||X.startsWith("'")&&X.endsWith("'"))X=X.slice(1,-1);if(!process.env[V])process.env[V]=X}}catch{}}function KQ(Q){for(let Z of[E(Q,"TENICLI.md"),E(h(),"TENICLI.md")])if(C(Z))return b(Z,"utf8");return qQ}var qQ=`You are TeniCLI, a fast AI coding assistant in the terminal.
2
+ import{createRequire as XQ}from"node:module";var p=XQ(import.meta.url);import{existsSync as F,readFileSync as h,writeFileSync as YQ,mkdirSync as KQ}from"fs";import{join as I}from"path";var O=[{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"}];function x(){let Q=process.env.HOME||process.env.USERPROFILE||"";return I(Q,".tenicli")}function b(){return I(x(),"config.json")}function P(){try{if(F(b()))return JSON.parse(h(b(),"utf8"))}catch{}return{}}function L(Q){let Z=x();if(!F(Z))KQ(Z,{recursive:!0});let $=P(),z={...$,...Q,keys:{...$.keys,...Q.keys},baseUrls:{...$.baseUrls,...Q.baseUrls}};YQ(b(),JSON.stringify(z,null,2),"utf8")}function l(){let Q=process.cwd();d(I(Q,".tenicli.env")),d(I(Q,".env"));let Z=P(),$=process.env,z=$.TENICLI_MODEL||Z.activeModel||O[0].id,Y=O.find((W)=>W.id===z)?.provider||$.TENICLI_PROVIDER||"anthropic",X=qQ(Y,Z,$),K=Y==="openai"?"https://api.openai.com":"https://api.anthropic.com",q=$.TENICLI_BASE_URL||Z.baseUrls?.[Y]||K;return{provider:{type:Y,baseUrl:q,apiKey:X,model:z},maxTokens:parseInt($.TENICLI_MAX_TOKENS||"8192"),systemPrompt:WQ(Q),cwd:Q}}function qQ(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 d(Q){try{if(!F(Q))return;for(let Z of h(Q,"utf8").split(`
3
+ `)){let $=Z.trim();if(!$||$.startsWith("#"))continue;let z=$.indexOf("=");if(z===-1)continue;let V=$.slice(0,z).trim(),Y=$.slice(z+1).trim();if(Y.startsWith('"')&&Y.endsWith('"')||Y.startsWith("'")&&Y.endsWith("'"))Y=Y.slice(1,-1);if(!process.env[V])process.env[V]=Y}}catch{}}function WQ(Q){for(let Z of[I(Q,"TENICLI.md"),I(x(),"TENICLI.md")])if(F(Z))return h(Z,"utf8");return JQ}var JQ=`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,37 +9,37 @@ 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.`;async function*x(Q,Z,$,z,V){if(Q.type==="openai")yield*JQ(Q,Z,$,z,V);else yield*WQ(Q,Z,$,z,V)}async function*WQ(Q,Z,$,z,V){let X=`${Q.baseUrl.replace(/\/$/,"")}/v1/messages`,Y={model:Q.model,max_tokens:V,system:$,messages:Z,stream:!0};if(z.length)Y.tools=z;let K=await p(X,Y,{"anthropic-version":"2023-06-01","x-api-key":Q.apiKey,authorization:`Bearer ${Q.apiKey}`});for await(let q of d(K))switch(q.type){case"message_start":if(q.message?.usage)yield{type:"usage",input:q.message.usage.input_tokens||0,output:0};break;case"content_block_start":if(q.content_block?.type==="text")yield{type:"text",text:""};else if(q.content_block?.type==="tool_use")yield{type:"tool_start",id:q.content_block.id,name:q.content_block.name};break;case"content_block_delta":if(q.delta?.type==="text_delta")yield{type:"text",text:q.delta.text};else if(q.delta?.type==="input_json_delta")yield{type:"tool_input",partial:q.delta.partial_json};break;case"content_block_stop":yield{type:"tool_end"};break;case"message_delta":if(q.usage)yield{type:"usage",input:0,output:q.usage.output_tokens||0};yield{type:"done",stopReason:q.delta?.stop_reason||"end_turn"};break}}async function*JQ(Q,Z,$,z,V){let X=`${Q.baseUrl.replace(/\/$/,"")}/v1/chat/completions`,Y=BQ(Z,$),K=z.map((_)=>({type:"function",function:{name:_.name,description:_.description,parameters:_.input_schema}})),q={model:Q.model,max_tokens:V,messages:Y,stream:!0,stream_options:{include_usage:!0}};if(K.length)q.tools=K;let W=await p(X,q,{authorization:`Bearer ${Q.apiKey}`}),N=new Map;for await(let _ of d(W)){let B=_.choices?.[0];if(!B){if(_.usage)yield{type:"usage",input:_.usage.prompt_tokens||0,output:_.usage.completion_tokens||0};continue}let A=B.delta||{};if(A.content)yield{type:"text",text:A.content};if(A.tool_calls)for(let R of A.tool_calls){if(R.id)N.set(R.index,{id:R.id,name:R.function?.name||"",args:""}),yield{type:"tool_start",id:R.id,name:R.function?.name||""};if(R.function?.arguments){let g=N.get(R.index);if(g)g.args+=R.function.arguments;yield{type:"tool_input",partial:R.function.arguments}}}if(B.finish_reason){for(let[,R]of N)yield{type:"tool_end"};yield{type:"done",stopReason:B.finish_reason==="tool_calls"?"tool_use":B.finish_reason}}}}function BQ(Q,Z){let $=[{role:"system",content:Z}];for(let z of Q)if(z.role==="user")if(typeof z.content==="string")$.push({role:"user",content:z.content});else{let V=z.content;for(let X of V)if(X.type==="tool_result")$.push({role:"tool",tool_call_id:X.tool_use_id,content:X.content||""});else $.push({role:"user",content:X.text||""})}else if(typeof z.content==="string")$.push({role:"assistant",content:z.content});else{let V=z.content,X=V.filter((K)=>K.type==="tool_use"),Y=V.filter((K)=>K.type==="text").map((K)=>K.text).join("");if(X.length)$.push({role:"assistant",content:Y||null,tool_calls:X.map((K)=>({id:K.id,type:"function",function:{name:K.name,arguments:JSON.stringify(K.input||{})}}))});else $.push({role:"assistant",content:Y})}return $}async function p(Q,Z,$){let z=await fetch(Q,{method:"POST",headers:{"content-type":"application/json",...$},body:JSON.stringify(Z)});if(!z.ok){let V=await z.text();throw Error(`API ${z.status}: ${V.slice(0,300)}`)}return z}async function*d(Q){let Z=Q.body.getReader(),$=new TextDecoder,z="";while(!0){let{done:V,value:X}=await Z.read();if(V)break;z+=$.decode(X,{stream:!0});let Y=z.split(`
13
- `);z=Y.pop();for(let K of Y)if(K.startsWith("data: ")){let q=K.slice(6).trim();if(q==="[DONE]")return;try{yield JSON.parse(q)}catch{}}}}import{readFileSync as f,writeFileSync as i,existsSync as S,readdirSync as r,statSync as o,mkdirSync as NQ}from"fs";import{resolve as RQ,relative as k,join as t,dirname as HQ}from"path";var l=(Q)=>`\x1B[${Q}m`,L=(Q,Z)=>($)=>`${l(Q)}${$}${l(Z)}`,H=(Q)=>(Z)=>`\x1B[38;5;${Q}m${Z}\x1B[39m`,G={bold:L("1","22"),dim:L("2","22"),italic:L("3","23"),under:L("4","24"),blue:H(111),purple:H(141),green:H(149),yellow:H(179),pink:H(210),cyan:H(117),gray:H(60),text:H(146),orange:H(215)};var J={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:["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]};function _Q(){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],z=["","","","",""];for(let Y=0;Y<5;Y++)z[Y]=$.map((K)=>K[Y]).join(" ");let V=Q.map((Y)=>G.cyan(Y.padEnd(14," "))),X=[" ".repeat(z[0].length),...z].map((Y)=>G.blue(Y));return V.map((Y,K)=>`${Y} ${X[K]||""}`).join(`
14
- `)}function a(){console.log(),console.log(_Q()),console.log(),console.log(G.gray(" ────────────────────────────────────────────────────────────────────────")),console.log(G.gray(" type to chat")+` ${J.dot} `+G.gray("/help for commands")+` ${J.dot} `+G.gray("v0.1.0")),console.log()}class T{i=0;timer=null;msg;constructor(Q="Thinking"){this.msg=Q}start(){return this.timer=setInterval(()=>{process.stdout.write(`\x1B[2K\r ${G.blue(J.spinner[this.i%J.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 U(Q){return new Promise((Z,$)=>{process.stdout.write(Q);let z=[],V=(Y)=>{if(Y[0]===3)process.stdout.write(`
15
- `),process.exit(0);if(Y[0]===4){X(),$(Error("EOF"));return}z.push(Y);let K=Buffer.concat(z).toString("utf8"),q=K.indexOf(`
16
- `);if(q!==-1)X(),Z(K.slice(0,q).replace(/\r$/,""))},X=()=>{process.stdin.removeListener("data",V)};process.stdin.setEncoding("utf8"),process.stdin.on("data",V),process.stdin.resume()})}async function c(){let Q=[],Z=!0;while(!0){let $=Z?`
17
- ${J.prompt} `:` ${G.gray("│")} `,z=await U($);if(Z=!1,z.endsWith("\\"))Q.push(z.slice(0,-1));else{Q.push(z);break}}return Q.join(`
18
- `)}async function y(Q,Z){console.log(`
19
- ${G.bold(Q)}`),Z.forEach(($,z)=>{let V=G.blue(` ${z+1}.`),X=$.desc?G.gray(` (${$.desc})`):"";console.log(`${V} ${$.label}${X}`)});while(!0){let $=await U(`
20
- ${G.gray("choose")} ${G.blue("❯")} `),z=parseInt($.trim());if(z>=1&&z<=Z.length)return z-1;console.log(` ${J.warn} enter 1-${Z.length}`)}}function I(Q,Z){console.log(`
21
- ${J.tool} ${G.yellow(Q)} ${G.gray(Z)}`)}function j(Q){console.error(` ${J.err} ${G.pink(Q)}`)}import{spawn as ZQ}from"child_process";class s{writes=[];recordWrite(Q,Z){let $=S(Q)?f(Q,"utf8"):null;this.writes.push({path:Q,backup:$,newLines:Z.split(`
22
- `).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 i(Q.path,Q.backup,"utf8"),{path:Q.path,restored:!0};else{try{GQ("fs").unlinkSync(Q.path)}catch{}return{path:Q.path,restored:!1}}}get count(){return this.writes.length}clear(){this.writes=[]}}var M=new s,e=[{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"]}}],UQ=30000;async function QQ(Q,Z,$){try{let z;switch(Q){case"read_file":z=AQ(Z,$),I("read_file",G.dim(k($,O(Z.path,$))));break;case"write_file":z=DQ(Z,$),I("write_file",G.dim(k($,O(Z.path,$))));break;case"list_dir":z=jQ(Z,$),I("list_dir",G.dim(Z.path||"."));break;case"search_files":z=await OQ(Z,$),I("search_files",G.dim(`"${Z.pattern}"`));break;case"exec_command":z=await EQ(Z,$),I("exec_command",G.dim(n(Z.command,60)));break;default:z=`Unknown tool: ${Q}`}return{type:"tool_result",content:n(z,UQ)}}catch(z){return{type:"tool_result",content:`Error: ${z.message}`,is_error:!0}}}function O(Q,Z){return RQ(Z,Q)}function AQ(Q,Z){let $=O(Q.path,Z);if(!S($))return`File not found: ${Q.path}`;let z=f($,"utf8"),V=z.split(`
23
- `);if(Q.start_line||Q.end_line){let X=Math.max(1,Q.start_line||1)-1,Y=Math.min(V.length,Q.end_line||V.length);return V.slice(X,Y).map((K,q)=>`${X+q+1}: ${K}`).join(`
24
- `)}if(V.length>50)return V.map((X,Y)=>`${Y+1}: ${X}`).join(`
25
- `);return z}function DQ(Q,Z){let $=O(Q.path,Z),z=HQ($);if(!S(z))NQ(z,{recursive:!0});return M.recordWrite($,Q.content),i($,Q.content,"utf8"),`Written ${Q.content.split(`
26
- `).length} lines to ${Q.path}`}function jQ(Q,Z){let $=O(Q.path||".",Z);if(!S($))return`Directory not found: ${Q.path||"."}`;let z=Q.depth||1,V=[];function X(Y,K){if(K>z)return;try{let q=r(Y);for(let W of q){if(W.startsWith(".")||W==="node_modules")continue;let N=t(Y,W),_=k($,N);try{let B=o(N),A=" ".repeat(K);if(B.isDirectory())V.push(`${A}${_}/`),X(N,K+1);else{let R=B.size>1024?`${(B.size/1024).toFixed(1)}KB`:`${B.size}B`;V.push(`${A}${_} (${R})`)}}catch{}}}catch{}}return X($,0),V.length>0?V.join(`
27
- `):"(empty directory)"}async function OQ(Q,Z){let $=O(Q.path||".",Z),z=Q.pattern;try{let Y=["-n","--max-count=50","--no-heading"];if(Q.include)Y.push("--glob",Q.include);return Y.push(z,$),await new Promise((q,W)=>{let N=ZQ("rg",Y,{shell:!0}),_="";N.stdout.on("data",(B)=>_+=B.toString()),N.on("close",(B)=>{if(B===0||B===1)q(_.trim()||"No matches found.");else W(Error("rg failed"))}),N.on("error",W)})}catch{}let V=[];function X(Y){try{for(let K of r(Y)){if(K.startsWith(".")||K==="node_modules")continue;let q=t(Y,K);try{let W=o(q);if(W.isDirectory()){X(q);continue}if(W.size>500000)continue;if(Q.include&&!IQ(K,Q.include))continue;let _=f(q,"utf8").split(`
28
- `);for(let B=0;B<_.length;B++)if(_[B].includes(z)){if(V.push(`${k(Z,q)}:${B+1}: ${_[B].trim()}`),V.length>=50)return}}catch{}}}catch{}}return X($),V.length>0?V.join(`
29
- `):"No matches found."}async function EQ(Q,Z){let $=O(Q.cwd||".",Z),z=process.platform==="win32",V=z?"cmd":"sh",X=z?"/c":"-c";return new Promise((Y)=>{let K=ZQ(V,[X,Q.command],{cwd:$,env:{...process.env,PAGER:"cat"}}),q="",W="";K.stdout.on("data",(_)=>q+=_.toString()),K.stderr.on("data",(_)=>W+=_.toString());let N=setTimeout(()=>K.kill(),30000);K.on("close",(_)=>{clearTimeout(N);let B="";if(q.trim())B+=q.trim();if(W.trim())B+=(B?`
30
- `:"")+`[stderr] ${W.trim()}`;B+=`
31
- [exit code: ${_}]`,Y(B)}),K.on("error",(_)=>{clearTimeout(N),Y(`[error] Failed to start process: ${_.message}`)})})}function n(Q,Z){if(Q.length<=Z)return Q;return Q.slice(0,Z)+`
32
- ... (truncated, ${Q.length-Z} chars omitted)`}function IQ(Q,Z){if(Z.startsWith("*."))return Q.endsWith(Z.slice(1));return Q.includes(Z.replace(/\*/g,""))}class v{messages=[];tokens={input:0,output:0};cfg;autoMode=!1;constructor(Q){this.cfg=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==="tool_use"){let $=Q.content.filter((V)=>V.type==="tool_use"),z=[];for(let V of $){if(!this.autoMode&&(V.name==="write_file"||V.name==="exec_command")){let Y=V.name==="write_file"?V.input?.path:V.input?.command?.slice(0,80);console.log(`
33
- ${J.warn} ${G.yellow(V.name)} ${G.gray(Y||"")}`);let q=(await U(` ${G.gray("allow?")} ${G.blue("[y/n/auto]")} `)).trim().toLowerCase();if(q==="auto")this.autoMode=!0;else if(q!=="y"&&q!=="yes"&&q!==""){z.push({type:"tool_result",tool_use_id:V.id,content:"User denied this action.",is_error:!0});continue}}let X=await QQ(V.name,V.input,this.cfg.cwd);z.push({type:"tool_result",tool_use_id:V.id,content:X.content,is_error:X.is_error})}this.messages.push({role:"assistant",content:Q.content}),this.messages.push({role:"user",content:z});continue}let Z=Q.content.filter(($)=>$.type==="text").map(($)=>$.text).join("");if(Z)this.messages.push({role:"assistant",content:Z});break}console.log(`
34
- ${G.gray(`tokens: ${this.tokens.input}↑ ${this.tokens.output}↓`)}`)}async streamResponse(){let Q=new T("Thinking").start(),Z=[],$="",z="",V="",X="",Y="end_turn",K=!1;try{let q=x(this.cfg.provider,this.messages,this.cfg.systemPrompt,e,this.cfg.maxTokens);for await(let W of q)switch(W.type){case"text":if(!K)Q.stop(),K=!0,process.stdout.write(`
35
- ${J.ai} `);if(W.text)process.stdout.write(W.text);$+=W.text;break;case"tool_start":if(!K)Q.stop(),K=!0;if($)Z.push({type:"text",text:$}),$="";z=W.id,V=W.name,X="";break;case"tool_input":X+=W.partial;break;case"tool_end":if(z){let N={};try{N=JSON.parse(X)}catch{}Z.push({type:"tool_use",id:z,name:V,input:N}),z="",X=""}break;case"usage":this.tokens.input+=W.input,this.tokens.output+=W.output;break;case"done":Y=W.stopReason;break}}catch(q){return Q.stop(),j(q.message),{content:[],stopReason:"error"}}if($)Z.push({type:"text",text:$}),process.stdout.write(`
36
- `);if(!K)Q.stop();return{content:Z,stopReason:Y}}async compact(){if(this.messages.length<4){console.log(` ${J.warn} Not enough messages to compact.`);return}let Q=new T("Compacting").start();try{let Z="";for(let Y of this.messages)if(typeof Y.content==="string")Z+=`${Y.role}: ${Y.content.slice(0,500)}
37
- `;else{let K=Y.content.filter((W)=>W.type==="text").map((W)=>W.text?.slice(0,300)).join(" ");if(K)Z+=`${Y.role}: ${K}
38
- `;let q=Y.content.filter((W)=>W.type==="tool_use").map((W)=>`[tool: ${W.name}]`).join(", ");if(q)Z+=` tools: ${q}
12
+ - Write production-quality code matching the project's style.`;async function*y(Q,Z,$,z,V){if(Q.type==="openai")yield*_Q(Q,Z,$,z,V);else yield*NQ(Q,Z,$,z,V)}async function*NQ(Q,Z,$,z,V){let Y=`${Q.baseUrl.replace(/\/$/,"")}/v1/messages`,X={model:Q.model,max_tokens:V,system:$,messages:Z,stream:!0};if(z.length)X.tools=z;let K=await a(Y,X,{"anthropic-version":"2023-06-01","x-api-key":Q.apiKey,authorization:`Bearer ${Q.apiKey}`});for await(let q of c(K))switch(q.type){case"message_start":if(q.message?.usage)yield{type:"usage",input:q.message.usage.input_tokens||0,output:0};break;case"content_block_start":if(q.content_block?.type==="text")yield{type:"text",text:""};else if(q.content_block?.type==="tool_use")yield{type:"tool_start",id:q.content_block.id,name:q.content_block.name};break;case"content_block_delta":if(q.delta?.type==="text_delta")yield{type:"text",text:q.delta.text};else if(q.delta?.type==="input_json_delta")yield{type:"tool_input",partial:q.delta.partial_json};break;case"content_block_stop":yield{type:"tool_end"};break;case"message_delta":if(q.usage)yield{type:"usage",input:0,output:q.usage.output_tokens||0};yield{type:"done",stopReason:q.delta?.stop_reason||"end_turn"};break}}async function*_Q(Q,Z,$,z,V){let Y=`${Q.baseUrl.replace(/\/$/,"")}/v1/chat/completions`,X=BQ(Z,$),K=z.map((_)=>({type:"function",function:{name:_.name,description:_.description,parameters:_.input_schema}})),q={model:Q.model,max_tokens:V,messages:X,stream:!0,stream_options:{include_usage:!0}};if(K.length)q.tools=K;let W=await a(Y,q,{authorization:`Bearer ${Q.apiKey}`}),B=new Map;for await(let _ of c(W)){let N=_.choices?.[0];if(!N){if(_.usage)yield{type:"usage",input:_.usage.prompt_tokens||0,output:_.usage.completion_tokens||0};continue}let j=N.delta||{};if(j.content)yield{type:"text",text:j.content};if(j.tool_calls)for(let R of j.tool_calls){if(R.id)B.set(R.index,{id:R.id,name:R.function?.name||"",args:""}),yield{type:"tool_start",id:R.id,name:R.function?.name||""};if(R.function?.arguments){let m=B.get(R.index);if(m)m.args+=R.function.arguments;yield{type:"tool_input",partial:R.function.arguments}}}if(N.finish_reason){for(let[,R]of B)yield{type:"tool_end"};yield{type:"done",stopReason:N.finish_reason==="tool_calls"?"tool_use":N.finish_reason}}}}function BQ(Q,Z){let $=[{role:"system",content:Z}];for(let z of Q)if(z.role==="user")if(typeof z.content==="string")$.push({role:"user",content:z.content});else{let V=z.content;for(let Y of V)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 z.content==="string")$.push({role:"assistant",content:z.content});else{let V=z.content,Y=V.filter((K)=>K.type==="tool_use"),X=V.filter((K)=>K.type==="text").map((K)=>K.text).join("");if(Y.length)$.push({role:"assistant",content:X||null,tool_calls:Y.map((K)=>({id:K.id,type:"function",function:{name:K.name,arguments:JSON.stringify(K.input||{})}}))});else $.push({role:"assistant",content:X})}return $}async function a(Q,Z,$){let z=await fetch(Q,{method:"POST",headers:{"content-type":"application/json",...$},body:JSON.stringify(Z)});if(!z.ok){let V=await z.text();throw Error(`API ${z.status}: ${V.slice(0,300)}`)}return z}async function*c(Q){let Z=Q.body.getReader(),$=new TextDecoder,z="";while(!0){let{done:V,value:Y}=await Z.read();if(V)break;z+=$.decode(Y,{stream:!0});let X=z.split(`
13
+ `);z=X.pop();for(let K of X)if(K.startsWith("data: ")){let q=K.slice(6).trim();if(q==="[DONE]")return;try{yield JSON.parse(q)}catch{}}}}import{readFileSync as g,writeFileSync as t,existsSync as w,readdirSync as s,statSync as e,mkdirSync as HQ}from"fs";import{resolve as UQ,relative as S,join as QQ,dirname as AQ}from"path";var n=(Q)=>`\x1B[${Q}m`,T=(Q,Z)=>($)=>`${n(Q)}${$}${n(Z)}`,U=(Q)=>(Z)=>`\x1B[38;5;${Q}m${Z}\x1B[39m`,G={bold:T("1","22"),dim:T("2","22"),italic:T("3","23"),under:T("4","24"),blue:U(111),purple:U(141),green:U(149),yellow:U(179),pink:U(210),cyan:U(117),gray:U(60),text:U(146),orange:U(215)};var J={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:["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]};function RQ(){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],z=["","","","",""];for(let X=0;X<5;X++)z[X]=$.map((K)=>K[X]).join(" ");let V=Q.map((X)=>G.cyan(X.padEnd(14," "))),Y=[" ".repeat(z[0].length),...z].map((X)=>G.blue(X));return V.map((X,K)=>`${X} ${Y[K]||""}`).join(`
14
+ `)}var H={h:"─",v:"│",tl:"╭",tr:"╮",bl:"╰",br:"╯",line:(Q)=>"─".repeat(Q)};function v(Q,Z=60){let $=(z,V)=>{let Y=z.replace(/\x1b\[[0-9;]*m/g,""),X=V-Y.length;return X>0?z+" ".repeat(X):z};console.log(G.gray(` ${H.tl}${H.line(Z)}${H.tr}`));for(let z of Q)console.log(G.gray(` ${H.v}`)+` ${$(z,Z-2)} `+G.gray(H.v));console.log(G.gray(` ${H.bl}${H.line(Z)}${H.br}`))}function r(){console.clear(),console.log(),console.log(RQ()),console.log(),v([G.gray("type to chat")+` ${J.dot} `+G.gray("/help for commands")+` ${J.dot} `+G.gray("v0.1.0")],60),console.log()}class k{i=0;timer=null;msg;constructor(Q="Thinking"){this.msg=Q}start(){return this.timer=setInterval(()=>{process.stdout.write(`\x1B[2K\r ${G.blue(J.spinner[this.i%J.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 A(Q){return new Promise((Z,$)=>{process.stdout.write(Q);let z=[],V=(X)=>{if(X[0]===3)process.stdout.write(`
15
+ `),process.exit(0);if(X[0]===4){Y(),$(Error("EOF"));return}z.push(X);let K=Buffer.concat(z).toString("utf8"),q=K.indexOf(`
16
+ `);if(q!==-1)Y(),Z(K.slice(0,q).replace(/\r$/,""))},Y=()=>{process.stdin.removeListener("data",V)};process.stdin.setEncoding("utf8"),process.stdin.on("data",V),process.stdin.resume()})}async function i(){let Q=[],Z=!0;while(!0){let $=Z?`
17
+ ${G.gray(H.tl+H.line(3))} ${J.prompt} `:` ${G.gray(H.v)} `,z=await A($);if(Z=!1,z.endsWith("\\"))Q.push(z.slice(0,-1));else{Q.push(z);break}}return Q.join(`
18
+ `)}async function f(Q,Z){console.log(`
19
+ ${G.bold(Q)}`),Z.forEach(($,z)=>{let V=G.blue(` ${z+1}.`),Y=$.desc?G.gray(` (${$.desc})`):"";console.log(`${V} ${$.label}${Y}`)});while(!0){let $=await A(`
20
+ ${G.gray("choose")} ${G.blue("❯")} `),z=parseInt($.trim());if(z>=1&&z<=Z.length)return z-1;console.log(` ${J.warn} enter 1-${Z.length}`)}}function M(Q,Z){console.log(`
21
+ ${J.tool} ${G.yellow(Q)} ${G.gray(Z)}`)}function D(Q){console.error(` ${J.err} ${G.pink(Q)}`)}import{spawn as GQ}from"child_process";class ZQ{writes=[];recordWrite(Q,Z){let $=w(Q)?g(Q,"utf8"):null;this.writes.push({path:Q,backup:$,newLines:Z.split(`
22
+ `).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 t(Q.path,Q.backup,"utf8"),{path:Q.path,restored:!0};else{try{p("fs").unlinkSync(Q.path)}catch{}return{path:Q.path,restored:!1}}}get count(){return this.writes.length}clear(){this.writes=[]}}var C=new ZQ,$Q=[{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"]}}],DQ=30000;async function zQ(Q,Z,$){try{let z;switch(Q){case"read_file":z=jQ(Z,$),M("read_file",G.dim(S($,E(Z.path,$))));break;case"write_file":z=OQ(Z,$),M("write_file",G.dim(S($,E(Z.path,$))));break;case"list_dir":z=EQ(Z,$),M("list_dir",G.dim(Z.path||"."));break;case"search_files":z=await IQ(Z,$),M("search_files",G.dim(`"${Z.pattern}"`));break;case"exec_command":z=await MQ(Z,$),M("exec_command",G.dim(o(Z.command,60)));break;default:z=`Unknown tool: ${Q}`}return{type:"tool_result",content:o(z,DQ)}}catch(z){return{type:"tool_result",content:`Error: ${z.message}`,is_error:!0}}}function E(Q,Z){return UQ(Z,Q)}function jQ(Q,Z){let $=E(Q.path,Z);if(!w($))return`File not found: ${Q.path}`;let z=g($,"utf8"),V=z.split(`
23
+ `);if(Q.start_line||Q.end_line){let Y=Math.max(1,Q.start_line||1)-1,X=Math.min(V.length,Q.end_line||V.length);return V.slice(Y,X).map((K,q)=>`${Y+q+1}: ${K}`).join(`
24
+ `)}if(V.length>50)return V.map((Y,X)=>`${X+1}: ${Y}`).join(`
25
+ `);return z}function OQ(Q,Z){let $=E(Q.path,Z),z=AQ($);if(!w(z))HQ(z,{recursive:!0});return C.recordWrite($,Q.content),t($,Q.content,"utf8"),`Written ${Q.content.split(`
26
+ `).length} lines to ${Q.path}`}function EQ(Q,Z){let $=E(Q.path||".",Z);if(!w($))return`Directory not found: ${Q.path||"."}`;let z=Q.depth||1,V=[];function Y(X,K){if(K>z)return;try{let q=s(X);for(let W of q){if(W.startsWith(".")||W==="node_modules")continue;let B=QQ(X,W),_=S($,B);try{let N=e(B),j=" ".repeat(K);if(N.isDirectory())V.push(`${j}${_}/`),Y(B,K+1);else{let R=N.size>1024?`${(N.size/1024).toFixed(1)}KB`:`${N.size}B`;V.push(`${j}${_} (${R})`)}}catch{}}}catch{}}return Y($,0),V.length>0?V.join(`
27
+ `):"(empty directory)"}async function IQ(Q,Z){let $=E(Q.path||".",Z),z=Q.pattern;try{let X=["-n","--max-count=50","--no-heading"];if(Q.include)X.push("--glob",Q.include);return X.push(z,$),await new Promise((q,W)=>{let B=GQ("rg",X,{shell:!0}),_="";B.stdout.on("data",(N)=>_+=N.toString()),B.on("close",(N)=>{if(N===0||N===1)q(_.trim()||"No matches found.");else W(Error("rg failed"))}),B.on("error",W)})}catch{}let V=[];function Y(X){try{for(let K of s(X)){if(K.startsWith(".")||K==="node_modules")continue;let q=QQ(X,K);try{let W=e(q);if(W.isDirectory()){Y(q);continue}if(W.size>500000)continue;if(Q.include&&!CQ(K,Q.include))continue;let _=g(q,"utf8").split(`
28
+ `);for(let N=0;N<_.length;N++)if(_[N].includes(z)){if(V.push(`${S(Z,q)}:${N+1}: ${_[N].trim()}`),V.length>=50)return}}catch{}}}catch{}}return Y($),V.length>0?V.join(`
29
+ `):"No matches found."}async function MQ(Q,Z){let $=E(Q.cwd||".",Z),z=process.platform==="win32",V=z?"cmd":"sh",Y=z?"/c":"-c";return new Promise((X)=>{let K=GQ(V,[Y,Q.command],{cwd:$,env:{...process.env,PAGER:"cat"}}),q="",W="";K.stdout.on("data",(_)=>q+=_.toString()),K.stderr.on("data",(_)=>W+=_.toString());let B=setTimeout(()=>K.kill(),30000);K.on("close",(_)=>{clearTimeout(B);let N="";if(q.trim())N+=q.trim();if(W.trim())N+=(N?`
30
+ `:"")+`[stderr] ${W.trim()}`;N+=`
31
+ [exit code: ${_}]`,X(N)}),K.on("error",(_)=>{clearTimeout(B),X(`[error] Failed to start process: ${_.message}`)})})}function o(Q,Z){if(Q.length<=Z)return Q;return Q.slice(0,Z)+`
32
+ ... (truncated, ${Q.length-Z} chars omitted)`}function CQ(Q,Z){if(Z.startsWith("*."))return Q.endsWith(Z.slice(1));return Q.includes(Z.replace(/\*/g,""))}class u{messages=[];tokens={input:0,output:0};cfg;autoMode=!1;constructor(Q){this.cfg=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==="tool_use"){let $=Q.content.filter((V)=>V.type==="tool_use"),z=[];for(let V of $){if(!this.autoMode&&(V.name==="write_file"||V.name==="exec_command")){let X=V.name==="write_file"?V.input?.path:V.input?.command?.slice(0,80);console.log(`
33
+ ${J.warn} ${G.yellow(V.name)} ${G.gray(X||"")}`);let q=(await A(` ${G.gray("allow?")} ${G.blue("[y/n/auto]")} `)).trim().toLowerCase();if(q==="auto")this.autoMode=!0;else if(q!=="y"&&q!=="yes"&&q!==""){z.push({type:"tool_result",tool_use_id:V.id,content:"User denied this action.",is_error:!0});continue}}let Y=await zQ(V.name,V.input,this.cfg.cwd);z.push({type:"tool_result",tool_use_id:V.id,content:Y.content,is_error:Y.is_error})}this.messages.push({role:"assistant",content:Q.content}),this.messages.push({role:"user",content:z});continue}let Z=Q.content.filter(($)=>$.type==="text").map(($)=>$.text).join("");if(Z)this.messages.push({role:"assistant",content:Z});break}console.log(`
34
+ ${G.gray(`tokens: ${this.tokens.input}↑ ${this.tokens.output}↓`)}`)}async streamResponse(){let Q=new k("Thinking").start(),Z=[],$="",z="",V="",Y="",X="end_turn",K=!1;try{let q=y(this.cfg.provider,this.messages,this.cfg.systemPrompt,$Q,this.cfg.maxTokens);for await(let W of q)switch(W.type){case"text":if(!K)Q.stop(),K=!0,process.stdout.write(`
35
+ ${J.ai} `);if(W.text)process.stdout.write(W.text);$+=W.text;break;case"tool_start":if(!K)Q.stop(),K=!0;if($)Z.push({type:"text",text:$}),$="";z=W.id,V=W.name,Y="";break;case"tool_input":Y+=W.partial;break;case"tool_end":if(z){let B={};try{B=JSON.parse(Y)}catch{}Z.push({type:"tool_use",id:z,name:V,input:B}),z="",Y=""}break;case"usage":this.tokens.input+=W.input,this.tokens.output+=W.output;break;case"done":X=W.stopReason;break}}catch(q){return Q.stop(),D(q.message),{content:[],stopReason:"error"}}if($)Z.push({type:"text",text:$}),process.stdout.write(`
36
+ `);if(!K)Q.stop();return{content:Z,stopReason:X}}async compact(){if(this.messages.length<4){console.log(` ${J.warn} Not enough messages to compact.`);return}let Q=new k("Compacting").start();try{let Z="";for(let X of this.messages)if(typeof X.content==="string")Z+=`${X.role}: ${X.content.slice(0,500)}
37
+ `;else{let K=X.content.filter((W)=>W.type==="text").map((W)=>W.text?.slice(0,300)).join(" ");if(K)Z+=`${X.role}: ${K}
38
+ `;let q=X.content.filter((W)=>W.type==="tool_use").map((W)=>`[tool: ${W.name}]`).join(", ");if(q)Z+=` tools: ${q}
39
39
  `}let $=this.messages.length,z=`Summarize this conversation concisely. Keep key decisions, file changes, and current state. Be brief:
40
40
 
41
- ${Z.slice(0,6000)}`;this.messages=[{role:"user",content:z},{role:"assistant",content:`[Conversation compacted from ${$} messages. Summary of what happened:]`}];let V=x(this.cfg.provider,[{role:"user",content:z}],"You are a conversation summarizer. Create a brief summary preserving key facts, decisions, and file changes.",[],this.cfg.maxTokens),X="";for await(let Y of V){if(Y.type==="text"&&Y.text)X+=Y.text;if(Y.type==="usage")this.tokens.input+=Y.input,this.tokens.output+=Y.output}this.messages=[{role:"user",content:`[Previous conversation summary]
42
- ${X}`},{role:"assistant",content:"Understood. I have the context from our previous conversation. How can I continue helping you?"}],Q.stop(),console.log(` ${J.ok} Compacted ${$} messages → 2 ${G.gray(`(saved ~${Math.round(Z.length/4)} tokens)`)}`)}catch(Z){Q.stop(),j(`Compact failed: ${Z.message}`)}}get stats(){return this.tokens}get messageCount(){return this.messages.length}clear(){this.messages=[],this.tokens={input:0,output:0}}}import{writeFileSync as MQ,existsSync as CQ}from"fs";import{join as FQ,relative as $Q}from"path";var PQ="0.1.0";function LQ(Q){let Z={prompt:"",print:!1},$=0;while($<Q.length){switch(Q[$]){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${PQ}`),process.exit(0);case"-h":case"--help":TQ(),process.exit(0);default:if(!Q[$].startsWith("-"))Z.prompt=Q.slice($).join(" "),$=Q.length}$++}return Z}function TQ(){console.log(`
41
+ ${Z.slice(0,6000)}`;this.messages=[{role:"user",content:z},{role:"assistant",content:`[Conversation compacted from ${$} messages. Summary of what happened:]`}];let V=y(this.cfg.provider,[{role:"user",content:z}],"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 V){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]
42
+ ${Y}`},{role:"assistant",content:"Understood. I have the context from our previous conversation. How can I continue helping you?"}],Q.stop(),console.log(` ${J.ok} Compacted ${$} messages → 2 ${G.gray(`(saved ~${Math.round(Z.length/4)} tokens)`)}`)}catch(Z){Q.stop(),D(`Compact failed: ${Z.message}`)}}get stats(){return this.tokens}get messageCount(){return this.messages.length}clear(){this.messages=[],this.tokens={input:0,output:0}}}import{writeFileSync as FQ,existsSync as PQ}from"fs";import{join as LQ,relative as VQ}from"path";var TQ="0.1.0";function kQ(Q){let Z={prompt:"",print:!1},$=0;while($<Q.length){switch(Q[$]){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${TQ}`),process.exit(0);case"-h":case"--help":SQ(),process.exit(0);default:if(!Q[$].startsWith("-"))Z.prompt=Q.slice($).join(" "),$=Q.length}$++}return Z}function SQ(){console.log(`
43
43
  ${G.bold(G.blue("TeniCLI"))} — Lightweight AI Coding Agent
44
44
 
45
45
  ${G.bold("USAGE")}
@@ -61,10 +61,10 @@ ${G.bold("IN-CHAT")}
61
61
  /init Create TENICLI.md /clear New conversation
62
62
  /cost Token usage /exit Quit
63
63
  \\\\ Multiline input
64
- `)}async function kQ(Q,Z){switch(Q.toLowerCase().split(" ")[0]){case"/exit":case"/quit":case"/q":console.log(`
64
+ `)}async function wQ(Q,Z){switch(Q.toLowerCase().split(" ")[0]){case"/exit":case"/quit":case"/q":console.log(`
65
65
  ${G.gray("Bye!")} \uD83D\uDC4B
66
- `),process.exit(0);case"/clear":return Z.clear(),M.clear(),console.log(` ${J.ok} Conversation cleared`),!0;case"/compact":return await Z.compact(),!0;case"/diff":{let $=M.getChanges();if($.length===0)console.log(` ${G.gray("No files changed in this session.")}`);else{console.log(`
67
- ${G.bold("Files changed this session:")}`);for(let z of $){let V=$Q(Z.cfg.cwd,z.path),X=z.isNew?G.green("[NEW]"):G.yellow("[MOD]");console.log(` ${X} ${G.cyan(V)} ${G.gray(`(${z.lines} lines)`)}`)}console.log(` ${G.gray(`total: ${$.length} files`)}`)}return!0}case"/undo":{let $=M.undo();if(!$)console.log(` ${G.gray("Nothing to undo.")}`);else{let z=$Q(Z.cfg.cwd,$.path);if($.restored)console.log(` ${J.ok} Restored: ${G.cyan(z)}`);else console.log(` ${J.ok} Deleted (was new): ${G.cyan(z)}`)}return!0}case"/init":{let $=FQ(Z.cfg.cwd,"TENICLI.md");if(CQ($))console.log(` ${J.warn} TENICLI.md already exists.`);else MQ($,wQ,"utf8"),console.log(` ${J.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(` ${J.ok} Mode: ${$} ${G.gray(Z.autoMode?"(tools run without asking)":"(confirm write/exec)")}`),!0}case"/cost":{let $=Z.stats;return console.log(` ${J.ai} ${G.blue(String($.input))}↑ input ${G.blue(String($.output))}↓ output ${G.gray(`(${Z.messageCount} msgs)`)}`),!0}case"/model":{let $=D.map((V)=>({label:`${V.name} ${Z.cfg.provider.model===V.id?G.green("●"):""}`,desc:`${V.provider} • ${V.speed}`}));$.push({label:"Custom model...",desc:"type model ID"});let z=await y("Select model",$);if(z<D.length){let V=D[z];Z.cfg.provider.model=V.id,Z.cfg.provider.type=V.provider;let X=F(),Y=V.provider==="openai"?process.env.OPENAI_API_KEY||X.keys?.openai||"":process.env.ANTHROPIC_API_KEY||X.keys?.anthropic||"";if(Y)Z.cfg.provider.apiKey=Y;if(!X.baseUrls?.[V.provider])Z.cfg.provider.baseUrl=V.provider==="openai"?"https://api.openai.com":"https://api.anthropic.com";P({activeModel:V.id}),console.log(` ${J.ok} Model: ${G.blue(V.name)}`)}else{let V=await U(` ${G.gray("model ID")} ${G.blue("❯")} `);if(V.trim())Z.cfg.provider.model=V.trim(),P({activeModel:V.trim()}),console.log(` ${J.ok} Model: ${G.blue(V.trim())}`)}return!0}case"/auth":{let $=await y("Provider",[{label:"Anthropic",desc:"Claude models"},{label:"OpenAI",desc:"GPT models"},{label:"Custom",desc:"Anthropic-compatible proxy"}]),V=["anthropic","openai","anthropic"][$],X=await U(` ${G.gray("API Key")} ${G.blue("❯")} `);if(!X.trim())return console.log(` ${J.warn} Cancelled`),!0;let Y={[V]:X.trim()},K={};if($===2){let q=await U(` ${G.gray("Base URL")} ${G.blue("❯")} `);if(q.trim())K[V]=q.trim()}if(P({keys:Y,baseUrls:K}),Z.cfg.provider.apiKey=X.trim(),Z.cfg.provider.type=V,K[V])Z.cfg.provider.baseUrl=K[V];return console.log(` ${J.ok} ${V} key saved to ~/.tenicli/config.json`),!0}case"/help":return console.log(`
66
+ `),process.exit(0);case"/clear":return Z.clear(),C.clear(),console.log(` ${J.ok} Conversation cleared`),!0;case"/compact":return await Z.compact(),!0;case"/diff":{let $=C.getChanges();if($.length===0)console.log(` ${G.gray("No files changed in this session.")}`);else{console.log(`
67
+ ${G.bold("Files changed this session:")}`);for(let z of $){let V=VQ(Z.cfg.cwd,z.path),Y=z.isNew?G.green("[NEW]"):G.yellow("[MOD]");console.log(` ${Y} ${G.cyan(V)} ${G.gray(`(${z.lines} lines)`)}`)}console.log(` ${G.gray(`total: ${$.length} files`)}`)}return!0}case"/undo":{let $=C.undo();if(!$)console.log(` ${G.gray("Nothing to undo.")}`);else{let z=VQ(Z.cfg.cwd,$.path);if($.restored)console.log(` ${J.ok} Restored: ${G.cyan(z)}`);else console.log(` ${J.ok} Deleted (was new): ${G.cyan(z)}`)}return!0}case"/init":{let $=LQ(Z.cfg.cwd,"TENICLI.md");if(PQ($))console.log(` ${J.warn} TENICLI.md already exists.`);else FQ($,hQ,"utf8"),console.log(` ${J.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(` ${J.ok} Mode: ${$} ${G.gray(Z.autoMode?"(tools run without asking)":"(confirm write/exec)")}`),!0}case"/cost":{let $=Z.stats;return console.log(` ${J.ai} ${G.blue(String($.input))}↑ input ${G.blue(String($.output))}↓ output ${G.gray(`(${Z.messageCount} msgs)`)}`),!0}case"/model":{let $=O.map((V)=>({label:`${V.name} ${Z.cfg.provider.model===V.id?G.green("●"):""}`,desc:`${V.provider} • ${V.speed}`}));$.push({label:"Custom model...",desc:"type model ID"});let z=await f("Select model",$);if(z<O.length){let V=O[z];Z.cfg.provider.model=V.id,Z.cfg.provider.type=V.provider;let Y=P(),X=V.provider==="openai"?process.env.OPENAI_API_KEY||Y.keys?.openai||"":process.env.ANTHROPIC_API_KEY||Y.keys?.anthropic||"";if(X)Z.cfg.provider.apiKey=X;if(!Y.baseUrls?.[V.provider])Z.cfg.provider.baseUrl=V.provider==="openai"?"https://api.openai.com":"https://api.anthropic.com";L({activeModel:V.id}),console.log(` ${J.ok} Model: ${G.blue(V.name)}`)}else{let V=await A(` ${G.gray("model ID")} ${G.blue("❯")} `);if(V.trim())Z.cfg.provider.model=V.trim(),L({activeModel:V.trim()}),console.log(` ${J.ok} Model: ${G.blue(V.trim())}`)}return!0}case"/auth":{let $=await f("Provider",[{label:"Anthropic",desc:"Claude models"},{label:"OpenAI",desc:"GPT models"},{label:"Custom",desc:"Anthropic-compatible proxy"}]),V=["anthropic","openai","anthropic"][$],Y=await A(` ${G.gray("API Key")} ${G.blue("❯")} `);if(!Y.trim())return console.log(` ${J.warn} Cancelled`),!0;let X={[V]:Y.trim()},K={};if($===2){let q=await A(` ${G.gray("Base URL")} ${G.blue("❯")} `);if(q.trim())K[V]=q.trim()}if(L({keys:X,baseUrls:K}),Z.cfg.provider.apiKey=Y.trim(),Z.cfg.provider.type=V,K[V])Z.cfg.provider.baseUrl=K[V];return console.log(` ${J.ok} ${V} key saved to ~/.tenicli/config.json`),!0}case"/help":return console.log(`
68
68
  ${G.bold("Commands")}
69
69
  ${G.blue("/model")} Select AI model
70
70
  ${G.blue("/auth")} Set API key
@@ -73,13 +73,15 @@ ${G.bold("IN-CHAT")}
73
73
  ${G.blue("/diff")} List files changed this session
74
74
  ${G.blue("/undo")} Revert last file write
75
75
  ${G.blue("/init")} Create TENICLI.md template
76
+ ${G.blue("/update")} Update to latest version
76
77
  ${G.blue("/clear")} New conversation
77
78
  ${G.blue("/cost")} Show token usage
78
79
  ${G.blue("/exit")} Quit
79
- ${G.gray("\\\\")} Continue on next line`),!0;default:return console.log(` ${J.warn} Unknown: ${Q.split(" ")[0]} — try /help`),!0}}async function SQ(){let Q=LQ(process.argv.slice(2)),Z=m();if(Q.model)Z.provider.model=Q.model;if(Q.baseUrl)Z.provider.baseUrl=Q.baseUrl;let $=new v(Z);if(Q.print&&Q.prompt){if(!Z.provider.apiKey)j("No API key. Run: teni then /auth"),process.exit(1);await $.send(Q.prompt),process.exit(0)}a();let z=D.find((X)=>X.id===Z.provider.model)?.name||Z.provider.model,V=$.autoMode?G.yellow("auto"):G.green("ask");if(console.log(` ${G.gray("model")} ${G.blue(z)} ${G.gray("mode")} ${V} ${G.gray("cwd")} ${G.cyan(Z.cwd)}`),!Z.provider.apiKey)console.log(`
80
- ${J.warn} ${G.yellow("No API key configured. Run /auth to set one.")}`);if(console.log(),Q.prompt){if(console.log(` ${J.prompt} ${Q.prompt}`),Z.provider.apiKey)await $.send(Q.prompt)}while(!0)try{let Y=(await c()).trim();if(!Y)continue;if(Y.startsWith("/")){await kQ(Y,$);continue}if(!$.cfg.provider.apiKey){console.log(` ${J.warn} ${G.yellow("No API key. Run /auth first.")}`);continue}await $.send(Y)}catch(X){if(X.message==="EOF")console.log(`
80
+ ${G.gray("\\\\")} Continue on next line`),!0;case"/update":{console.log(`
81
+ ${J.tool} ${G.yellow("Checking for updates...")}`);try{let{execSync:$}=await import("child_process"),z=$("npm i -g tenicli@latest 2>&1",{encoding:"utf8"});console.log(` ${J.ok} ${G.green("Updated!")} Restart teni to use the new version.`),console.log(G.gray(` ${z.trim().split(`
82
+ `).pop()}`))}catch($){D(`Update failed: ${$.message}`)}return!0}default:return console.log(` ${J.warn} Unknown: ${Q.split(" ")[0]} — try /help`),!0}}async function bQ(){let Q=kQ(process.argv.slice(2)),Z=l();if(Q.model)Z.provider.model=Q.model;if(Q.baseUrl)Z.provider.baseUrl=Q.baseUrl;let $=new u(Z);if(Q.print&&Q.prompt){if(!Z.provider.apiKey)D("No API key. Run: teni then /auth"),process.exit(1);await $.send(Q.prompt),process.exit(0)}r();let z=O.find((X)=>X.id===Z.provider.model)?.name||Z.provider.model,V=$.autoMode?G.yellow("auto"):G.green("ask"),Y=[`${G.gray("model")} ${G.blue(z)} ${G.gray("mode")} ${V} ${G.gray("cwd")} ${G.cyan(Z.cwd)}`];if(!Z.provider.apiKey)Y.push(""),Y.push(`${J.warn} ${G.yellow("No API key configured. Run /auth to set one.")}`);if(v(Y,60),console.log(),Q.prompt){if(console.log(` ${J.prompt} ${Q.prompt}`),Z.provider.apiKey)await $.send(Q.prompt)}while(!0)try{let K=(await i()).trim();if(!K)continue;if(K.startsWith("/")){await wQ(K,$);continue}if(!$.cfg.provider.apiKey){console.log(` ${J.warn} ${G.yellow("No API key. Run /auth first.")}`);continue}await $.send(K)}catch(X){if(X.message==="EOF")console.log(`
81
83
  ${G.gray("Bye!")} \uD83D\uDC4B
82
- `),process.exit(0);j(X.message)}}SQ().catch((Q)=>{j(Q.message),process.exit(1)});var wQ=`# Project Instructions
84
+ `),process.exit(0);D(X.message)}}bQ().catch((Q)=>{D(Q.message),process.exit(1)});var hQ=`# Project Instructions
83
85
 
84
86
  ## Overview
85
87
  Describe your project here so the AI understands the context.
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "tenicli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Lightweight AI coding CLI — fast, compact, multi-provider",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "teni": "./dist/index.js"
8
8
  },
9
- "files": ["dist/", "README.md", "LICENSE"],
9
+ "files": [
10
+ "dist/",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
10
14
  "scripts": {
11
15
  "dev": "bun run src/index.ts",
12
16
  "build:npm": "bun build src/index.ts --outfile dist/index.js --target node --minify",
@@ -16,7 +20,15 @@
16
20
  "build:mac": "bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile teni",
17
21
  "prepublishOnly": "bun run build:npm"
18
22
  },
19
- "keywords": ["ai", "cli", "coding", "agent", "anthropic", "openai", "terminal"],
23
+ "keywords": [
24
+ "ai",
25
+ "cli",
26
+ "coding",
27
+ "agent",
28
+ "anthropic",
29
+ "openai",
30
+ "terminal"
31
+ ],
20
32
  "author": "Yan Tenica",
21
33
  "license": "MIT",
22
34
  "repository": {