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