tenicli 0.2.1 → 0.2.3
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 +227 -253
- 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 Q=$.trim();if(!Q||Q.startsWith("#"))continue;let V=Q.indexOf("=");if(V===-1)continue;let
|
|
2
|
+
import{createRequire as n0}from"node:module";var a0=Object.defineProperty;var i0=(Z)=>Z;function c0(Z,$){this[Z]=i0.bind(null,$)}var q0=(Z,$)=>{for(var Q in $)a0(Z,Q,{get:$[Q],enumerable:!0,configurable:!0,set:c0.bind($,Q)})};var P=(Z,$)=>()=>(Z&&($=Z(Z=0)),$);var g=n0(import.meta.url);import{existsSync as u,readFileSync as s,writeFileSync as o0,mkdirSync as r0}from"fs";import{join as S}from"path";function e(){let Z=process.env.HOME||process.env.USERPROFILE||"";return S(Z,".tenicli")}function t(){return S(e(),"config.json")}function p(){try{if(u(t()))return JSON.parse(s(t(),"utf8"))}catch{}return{}}function m(Z){let $=e();if(!u($))r0($,{recursive:!0});let Q=p(),V={...Q,...Z,keys:{...Q.keys,...Z.keys},baseUrls:{...Q.baseUrls,...Z.baseUrls}};o0(t(),JSON.stringify(V,null,2),"utf8")}function d(){let Z=process.cwd();E0(S(Z,".tenicli.env")),E0(S(Z,".env"));let $=p(),Q=process.env,V=Q.TENICLI_MODEL||$.activeModel||x[0].id,Y=x.find((N)=>N.id===V)?.provider||Q.TENICLI_PROVIDER||"anthropic",X=t0(Y,$,Q),z=Y==="openai"?"https://api.openai.com":"https://api.anthropic.com",J=Q.TENICLI_BASE_URL||$.baseUrls?.[Y]||z;return{provider:{type:Y,baseUrl:J,apiKey:X,model:V},maxTokens:parseInt(Q.TENICLI_MAX_TOKENS||"8192"),systemPrompt:s0(Z),cwd:Z}}function t0(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 E0(Z){try{if(!u(Z))return;for(let $ of s(Z,"utf8").split(`
|
|
3
|
+
`)){let Q=$.trim();if(!Q||Q.startsWith("#"))continue;let V=Q.indexOf("=");if(V===-1)continue;let K=Q.slice(0,V).trim(),Y=Q.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 s0(Z){for(let $ of[S(Z,"TENICLI.md"),S(e(),"TENICLI.md")])if(u($))return s($,"utf8");return e0}var x,e0=`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
|
|
13
|
-
`);V=X.pop();for(let
|
|
14
|
-
`)}function
|
|
15
|
-
`),process.exit(0);if(
|
|
16
|
-
`);if(
|
|
17
|
-
\x1B[2K`);let
|
|
18
|
-
`),process.exit(0);if(
|
|
19
|
-
`),
|
|
20
|
-
${
|
|
21
|
-
`)}async function
|
|
22
|
-
${
|
|
23
|
-
${
|
|
24
|
-
${
|
|
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
|
|
26
|
-
`);if(Z.start_line||Z.end_line){let
|
|
27
|
-
`)}if(
|
|
28
|
-
`);return V}function
|
|
29
|
-
`).length} lines to ${Z.path}`}function
|
|
30
|
-
`):"(empty directory)"}async function
|
|
31
|
-
`);for(let
|
|
32
|
-
`):"No matches found."}async function
|
|
33
|
-
`:"")+`[stderr] ${
|
|
34
|
-
[exit code: ${_}]`,X(
|
|
35
|
-
... (truncated, ${Z.length-$} chars omitted)`}function
|
|
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((
|
|
37
|
-
${
|
|
38
|
-
${
|
|
39
|
-
${
|
|
40
|
-
`)}if(!
|
|
41
|
-
`;else{let
|
|
42
|
-
`;let
|
|
12
|
+
- Write production-quality code matching the project's style.`;var Z0=P(()=>{x=[{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*Q0(Z,$,Q,V,K){if(Z.type==="openai")yield*$1(Z,$,Q,V,K);else yield*Z1(Z,$,Q,V,K)}async function*Z1(Z,$,Q,V,K){let Y=`${Z.baseUrl.replace(/\/$/,"")}/v1/messages`,X={model:Z.model,max_tokens:K,system:Q,messages:$,stream:!0};if(V.length)X.tools=V;let z=await A0(Y,X,{"anthropic-version":"2023-06-01","x-api-key":Z.apiKey,authorization:`Bearer ${Z.apiKey}`});for await(let J of H0(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*$1(Z,$,Q,V,K){let Y=`${Z.baseUrl.replace(/\/$/,"")}/v1/chat/completions`,X=Q1($,Q),z=V.map((_)=>({type:"function",function:{name:_.name,description:_.description,parameters:_.input_schema}})),J={model:Z.model,max_tokens:K,messages:X,stream:!0,stream_options:{include_usage:!0}};if(z.length)J.tools=z;let N=await A0(Y,J,{authorization:`Bearer ${Z.apiKey}`}),W=new Map;for await(let _ of H0(N)){let q=_.choices?.[0];if(!q){if(_.usage)yield{type:"usage",input:_.usage.prompt_tokens||0,output:_.usage.completion_tokens||0};continue}let H=q.delta||{};if(H.content)yield{type:"text",text:H.content};if(H.tool_calls)for(let U of H.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 Q1(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 K=V.content;for(let Y of K)if(Y.type==="tool_result")Q.push({role:"tool",tool_call_id:Y.tool_use_id,content:Y.content||""});else Q.push({role:"user",content:Y.text||""})}else if(typeof V.content==="string")Q.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)Q.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 Q.push({role:"assistant",content:X})}return Q}async function A0(Z,$,Q){let V=await fetch(Z,{method:"POST",headers:{"content-type":"application/json",...Q},body:JSON.stringify($)});if(!V.ok){let A=await V.text();throw Error(`API ${V.status}: ${A.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 H=V.headers.get("x-ratelimit-reset-requests");if(H)K.requestsReset=H;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 B=V.headers.get("x-ratelimit-reset-tokens");if(B)K.tokensReset=B;if(Object.keys(K).length>0)$0=K;return V}async function*H0(Z){let $=Z.body.getReader(),Q=new TextDecoder,V="";while(!0){let{done:K,value:Y}=await $.read();if(K)break;V+=Q.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 $0;var V0=P(()=>{$0={}});function V1(){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((z)=>z[X]).join(" ");let K=Z.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 w(Z,$=60){let Q=(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($)}${j.tr}`));for(let V of Z)console.log(G.gray(` ${j.v}`)+` ${Q(V,$-2)} `+G.gray(j.v));console.log(G.gray(` ${j.bl}${j.line($)}${j.br}`))}function R0(Z="0.0.0"){console.clear(),console.log(),console.log(V1()),console.log(),w([G.gray("type to chat")+` ${E.dot} `+G.gray("/help for commands")+` ${E.dot} `+G.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 ${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 M(Z,$=!1){return new Promise((Q,V)=>{if(process.stdout.write(Z),!$||!process.stdin.isTTY){let U="",D=(A)=>{let R=typeof A==="string"?A:A.toString("utf8");if(R.charCodeAt(0)===3)process.stdout.write(`
|
|
15
|
+
`),process.exit(0);if(R.charCodeAt(0)===4){B(),V(Error("EOF"));return}U+=R;let O=U.indexOf(`
|
|
16
|
+
`);if(O!==-1)B(),Q(U.slice(0,O).replace(/\r$/,""))},B=()=>{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 G1.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;let D=Z.replace(/\x1b\[[0-9;]*m/g,"").length;for(let B=0;B<U.length;B++){process.stdout.write(`
|
|
17
|
+
\x1B[2K\r`);let A=U[B];if(B===X)process.stdout.write(` ${G.blue(G.bold(A.cmd))} ${G.gray(A.desc)}`);else process.stdout.write(` ${G.gray(A.cmd)} ${G.gray(G.dim(A.desc))}`)}Y=U.length,process.stdout.write(`\x1B[${Y}A\r\x1B[${D+K.length}C`)},_="",q=(U)=>{let D=typeof U==="string"?U:U.toString("utf8");for(let B=0;B<D.length;B++){let A=D[B],R=A.charCodeAt(0);if(_.length>0||R===27){if(_+=A,_.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(R===3)N(),process.stdin.setRawMode(z),process.stdout.write(`
|
|
18
|
+
`),process.exit(0);if(R===4){N(),process.stdin.setRawMode(z),H(),V(Error("EOF"));return}if(R===13||R===10){let O=J();if(O.length>0&&K!==O[X].cmd){N();let T=O[X].cmd;process.stdout.write("\b \b".repeat(K.length)),K=T,process.stdout.write(K)}N(),process.stdin.setRawMode(z),process.stdout.write(`
|
|
19
|
+
`),H(),Q(K);return}if(R===127||R===8){if(K.length>0)N(),K=K.slice(0,-1),process.stdout.write("\b \b"),X=0,W();continue}if(R===9){let O=J();if(O.length>0){N();let T=O[X].cmd,r=T.slice(K.length);K=T,process.stdout.write(r),W()}continue}if(R<32)continue;N(),K+=A,process.stdout.write(A),X=0,W()}},H=()=>{process.stdin.removeListener("data",q)};process.stdin.on("data",q)})}async function B0(){let Z=[],$=!0;while(!0){let Q=$?`
|
|
20
|
+
${G.gray(j.tl+j.line(3))} ${E.prompt} `:` ${G.gray(j.v)} `,V=await M(Q,$);if($=!1,V.endsWith("\\"))Z.push(V.slice(0,-1));else{Z.push(V);break}}return Z.join(`
|
|
21
|
+
`)}async function L(Z,$){console.log(`
|
|
22
|
+
${G.bold(Z)}`),$.forEach((Q,V)=>{let K=G.blue(` ${V+1}.`),Y=Q.desc?G.gray(` (${Q.desc})`):"";console.log(`${K} ${Q.label}${Y}`)}),console.log(` ${G.gray(" 0. Cancel")}`);while(!0){let V=(await M(`
|
|
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<=$.length)return K-1;console.log(` ${E.warn} enter 1-${$.length} or 0 to cancel`)}}function b(Z,$){console.log(`
|
|
24
|
+
${E.tool} ${G.yellow(Z)} ${G.gray($)}`)}function I(Z){console.error(` ${E.err} ${G.pink(Z)}`)}var D0=(Z)=>`\x1B[${Z}m`,l=(Z,$)=>(Q)=>`${D0(Z)}${Q}${D0($)}`,F=(Z)=>($)=>`\x1B[38;5;${Z}m${$}\x1B[39m`,G,E,j,G1;var i=P(()=>{G={bold:l("1","22"),dim:l("2","22"),italic:l("3","23"),under:l("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:(Z)=>"─".repeat(Z)};G1=[{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 G0,writeFileSync as j0,existsSync as n,readdirSync as F0,statSync as P0,mkdirSync as K1}from"fs";import{resolve as X1,relative as c,join as M0,dirname as z1}from"path";import{spawn as T0}from"child_process";class I0{writes=[];recordWrite(Z,$){let Q=n(Z)?G0(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 j0(Z.path,Z.backup,"utf8"),{path:Z.path,restored:!0};else{try{g("fs").unlinkSync(Z.path)}catch{}return{path:Z.path,restored:!1}}}get count(){return this.writes.length}clear(){this.writes=[]}}async function C0(Z,$,Q){try{let V;switch(Z){case"read_file":V=Y1($,Q),b("read_file",G.dim(c(Q,C($.path,Q))));break;case"write_file":V=N1($,Q),b("write_file",G.dim(c(Q,C($.path,Q))));break;case"list_dir":V=W1($,Q),b("list_dir",G.dim($.path||"."));break;case"search_files":V=await _1($,Q),b("search_files",G.dim(`"${$.pattern}"`));break;case"exec_command":V=await U1($,Q),b("exec_command",G.dim(O0($.command,60)));break;default:V=`Unknown tool: ${Z}`}return{type:"tool_result",content:O0(V,J1)}}catch(V){return{type:"tool_result",content:`Error: ${V.message}`,is_error:!0}}}function C(Z,$){return X1($,Z)}function Y1(Z,$){let Q=C(Z.path,$);if(!n(Q))return`File not found: ${Z.path}`;let V=G0(Q,"utf8"),K=V.split(`
|
|
26
|
+
`);if(Z.start_line||Z.end_line){let Y=Math.max(1,Z.start_line||1)-1,X=Math.min(K.length,Z.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 N1(Z,$){let Q=C(Z.path,$),V=z1(Q);if(!n(V))K1(V,{recursive:!0});return h.recordWrite(Q,Z.content),j0(Q,Z.content,"utf8"),`Written ${Z.content.split(`
|
|
29
|
+
`).length} lines to ${Z.path}`}function W1(Z,$){let Q=C(Z.path||".",$);if(!n(Q))return`Directory not found: ${Z.path||"."}`;let V=Z.depth||1,K=[];function Y(X,z){if(z>V)return;try{let J=F0(X);for(let N of J){if(N.startsWith(".")||N==="node_modules")continue;let W=M0(X,N),_=c(Q,W);try{let q=P0(W),H=" ".repeat(z);if(q.isDirectory())K.push(`${H}${_}/`),Y(W,z+1);else{let U=q.size>1024?`${(q.size/1024).toFixed(1)}KB`:`${q.size}B`;K.push(`${H}${_} (${U})`)}}catch{}}}catch{}}return Y(Q,0),K.length>0?K.join(`
|
|
30
|
+
`):"(empty directory)"}async function _1(Z,$){let Q=C(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((J,N)=>{let W=T0("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 F0(X)){if(z.startsWith(".")||z==="node_modules")continue;let J=M0(X,z);try{let N=P0(J);if(N.isDirectory()){Y(J);continue}if(N.size>500000)continue;if(Z.include&&!q1(z,Z.include))continue;let _=G0(J,"utf8").split(`
|
|
31
|
+
`);for(let q=0;q<_.length;q++)if(_[q].includes(V)){if(K.push(`${c($,J)}:${q+1}: ${_[q].trim()}`),K.length>=50)return}}catch{}}}catch{}}return Y(Q),K.length>0?K.join(`
|
|
32
|
+
`):"No matches found."}async function U1(Z,$){let Q=C(Z.cwd||".",$),V=process.platform==="win32",K=V?"cmd":"sh",Y=V?"/c":"-c";return new Promise((X)=>{let z=T0(K,[Y,Z.command],{cwd:Q,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 O0(Z,$){if(Z.length<=$)return Z;return Z.slice(0,$)+`
|
|
35
|
+
... (truncated, ${Z.length-$} chars omitted)`}function q1(Z,$){if($.startsWith("*."))return Z.endsWith($.slice(1));return Z.includes($.replace(/\*/g,""))}var h,L0,J1=30000;var K0=P(()=>{i();h=new I0,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(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((K)=>K.type==="tool_use"),V=[];for(let K of Q){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 M(` ${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 C0(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: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
|
+
${G.gray(`tokens: ${this.tokens.input}↑ ${this.tokens.output}↓`)}`)}async streamResponse(){let Z=!this.onOutput,$=Z?new a("Thinking").start():null,Q=[],V="",K="",Y="",X="",z="end_turn",J=!1;try{let N=Q0(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($?.stop(),J=!0,Z)process.stdout.write(`
|
|
39
|
+
${E.ai} `)}if(W.text)this.write(W.text);V+=W.text;break;case"tool_start":if(!J)$?.stop(),J=!0;if(V)Q.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{}Q.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($?.stop(),Z)console.log();let W=N.message||String(N);if(this.emit({type:"error",message:W}),Z)I(W);return{content:[],stopReason:"error"}}if(V){if(Q.push({type:"text",text:V}),this.emit({type:"text_done"}),Z)process.stdout.write(`
|
|
40
|
+
`)}if(!J)$?.stop();return{content:Q,stopReason:z}}async compact(){if(this.messages.length<4){this.log(` ${E.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 z=X.content.filter((N)=>N.type==="text").map((N)=>N.text?.slice(0,300)).join(" ");if(z)$+=`${X.role}: ${z}
|
|
42
|
+
`;let J=X.content.filter((N)=>N.type==="tool_use").map((N)=>`[tool: ${N.name}]`).join(", ");if(J)$+=` tools: ${J}
|
|
43
43
|
`}let Q=this.messages.length,V=`Summarize this conversation concisely. Keep key decisions, file changes, and current state. Be brief:
|
|
44
44
|
|
|
45
|
-
${$.slice(0,6000)}`;this.messages=[{role:"user",content:V},{role:"assistant",content:`[Conversation compacted from ${Q} messages. Summary of what happened:]`}];let
|
|
46
|
-
${
|
|
47
|
-
`)}function
|
|
45
|
+
${$.slice(0,6000)}`;this.messages=[{role:"user",content:V},{role:"assistant",content:`[Conversation compacted from ${Q} messages. Summary of what happened:]`}];let K=Q0(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?"}],Z?.stop(),this.log(` ${E.ok} Compacted ${Q} messages → 2 ${G.gray(`(saved ~${Math.round($.length/4)} tokens)`)}`)}catch($){Z?.stop();let Q=`Compact failed: ${$.message}`;if(this.emit({type:"error",message:Q}),!this.onOutput)I(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 X0=P(()=>{V0();K0();i()});var v;var J0=P(()=>{v={name:"tenicli",version:"0.2.3",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 v0={};q0(v0,{renderQR:()=>B1,getLocalIPs:()=>O1});function B1(Z){let $=[],Q=Math.max(Z.length+4,30),V="██",K=" ";for(let X=0;X<7;X++){let z="";for(let J=0;J<7;J++)z+=Y0.FINDER[X][J]?"██":" ";z+=" ";for(let J=0;J<Q-15;J++)z+=X===6&&J%2===0?"██":X===6?" ":" ";z+=" ";for(let J=0;J<7;J++)z+=Y0.FINDER[X][J]?"██":" ";$.push(z)}$.push(" ".repeat(Q/2+1));let Y=5;for(let X=0;X<Y;X++){let z="";for(let J=0;J<Q+1;J++){if(J===6){z+=X%2===0?"██":" ";continue}z+=(J+X*3+J*X)%3===0?"██":" "}$.push(z)}$.push(" ".repeat(Q/2+1));for(let X=0;X<7;X++){let z="";for(let J=0;J<7;J++)z+=Y0.FINDER[X][J]?"██":" ";z+=" ";for(let J=0;J<Q-14;J++)z+=(J+X*5)%3===0?"██":" ";$.push(z)}return $.map((X)=>" "+X).join(`
|
|
47
|
+
`)}function O1(){try{let{networkInterfaces:Z}=g("os"),$=Z(),Q=[];for(let V of Object.keys($))for(let K of $[V])if(K.family==="IPv4"&&!K.internal)Q.push(K.address);return Q}catch{return[]}}var Y0;var g0=P(()=>{Y0={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 W0={};q0(W0,{startServer:()=>T1,getActiveServers:()=>P1});import{createServer as j1}from"http";import{createHash as F1}from"crypto";function P1(){return N0}function M1(Z){if(Z.length<2)return null;let $=Z[0]&15,Q=(Z[1]&128)!==0,V=Z[1]&127,K=2;if(V===126){if(Z.length<4)return null;V=Z.readUInt16BE(2),K=4}else if(V===127){if(Z.length<10)return null;V=Number(Z.readBigUInt64BE(2)),K=10}let X=K+(Q?4:0)+V;if(Z.length<X)return null;if(Q){let z=Z.slice(K,K+4),J=Buffer.alloc(V);for(let N=0;N<V;N++)J[N]=Z[K+4+N]^z[N%4];return{opcode:$,payload:J,totalSize:X}}return{opcode:$,payload:Z.slice(K,K+V),totalSize:X}}function I1(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 L1(Z){let $=Buffer.alloc(2);$[0]=137,$[1]=0,Z.write($)}function C1(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(--
|
|
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,258 @@ ${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>
|
|
114
|
+
<div class="dot" id="statusDot"></div>
|
|
132
115
|
<h1>TeniCLI v${Z}</h1>
|
|
133
116
|
</div>
|
|
134
117
|
<div class="session-list" id="sessionList"></div>
|
|
135
|
-
<div class="new-session"
|
|
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">
|
|
142
|
-
<span class="info" id="topInfo"
|
|
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"
|
|
131
|
+
<button class="send-btn" id="sendBtn">↑</button>
|
|
150
132
|
</div>
|
|
151
133
|
</div>
|
|
152
134
|
</div>
|
|
153
|
-
|
|
154
135
|
<script>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
136
|
+
(function(){
|
|
137
|
+
var TOKEN = '${$}';
|
|
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,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
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
|
-
|
|
177
|
-
function promptToken() {
|
|
178
|
-
const token = prompt('Enter access password:')
|
|
179
|
-
if (token) { localStorage.setItem('teni_token', token); connect() }
|
|
180
|
-
}
|
|
181
160
|
|
|
182
|
-
function newSession()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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 + '\\')">×</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
|
-
|
|
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
168
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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+'">×</span></div>';
|
|
193
|
+
}
|
|
194
|
+
$('sessionList').innerHTML = html;
|
|
195
|
+
}
|
|
283
196
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
if
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
function autoResize(el)
|
|
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
|
|
292
|
+
</html>`}function T1(Z,$){let Q=d(),V=new Map,K=new Map,Y=j1((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(C1(v.version,$))});Y.on("upgrade",(z,J)=>{if(new URL(z.url||"/",`http://${z.headers.host||"localhost"}`).searchParams.get("token")!==$){J.write(`HTTP/1.1 401 Unauthorized\r
|
|
325
293
|
\r
|
|
326
|
-
`),
|
|
294
|
+
`),J.destroy();return}let _=z.headers["sec-websocket-key"];if(!_){J.destroy();return}let q=F1("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: `+
|
|
297
|
+
Sec-WebSocket-Accept: `+q+`\r
|
|
330
298
|
\r
|
|
331
|
-
`);let
|
|
332
|
-
${
|
|
299
|
+
`);let H=Buffer.alloc(0),U=(B)=>{try{J.write(I1(JSON.stringify(B)))}catch{}};J.on("data",(B)=>{H=Buffer.concat([H,B]);while(H.length>0){let A=M1(H);if(!A)break;if(H=H.slice(A.totalSize),A.opcode===8){J.end();return}if(A.opcode===9){let R=Buffer.alloc(2+A.payload.length);R[0]=138,R[1]=A.payload.length,A.payload.copy(R,2),J.write(R);continue}if(A.opcode===10)continue;try{let R=JSON.parse(A.payload.toString("utf8"));S1(R,U,V,K,Q)}catch{}}});let D=setInterval(()=>{try{L1(J)}catch{clearInterval(D)}},30000);J.on("close",()=>{clearInterval(D);for(let[B,A]of V)if(A.send===U)V.delete(B)}),J.on("error",()=>{try{J.destroy()}catch{}})});let X=()=>{Y.close(),N0.delete(Z)};return Y.listen(Z,"0.0.0.0",()=>{N0.set(Z,{password:$,close:X})}),{close:X}}function S1(Z,$,Q,V,K){switch(Z.type){case"new_session":{let Y={...K,provider:{...K.provider}},X=new y(Y);X.autoMode=!0,X.onOutput=(z)=>{$({...z,sessionId:Z.sessionId})},X.onConfirm=(z,J,N)=>{return new Promise((W)=>{$({type:"confirm",sessionId:Z.sessionId,id:z,tool:J,preview:N}),V.set(z,W),setTimeout(()=>{if(V.has(z))V.delete(z),W("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 Y=Q.get(Z.sessionId);if(!Y)return;let X=Z.text?.trim();if(!X)return;if(X==="/clear"){Y.chat.clear(),$({type:"system",sessionId:Z.sessionId,text:"Conversation cleared"});return}if(X==="/compact"){Y.chat.compact();return}if(X==="/cost"){let z=Y.chat.stats;$({type:"tokens",sessionId:Z.sessionId,input:z.input,output:z.output,messages:Y.chat.messageCount});return}Y.chat.send(X).catch((z)=>{$({type:"error",sessionId:Z.sessionId,message:z.message})});break}case"confirm_response":{let Y=V.get(Z.id);if(Y)V.delete(Z.id),Y(Z.answer);break}}}var N0;var _0=P(()=>{X0();Z0();J0();N0=new Map});Z0();X0();V0();K0();i();import{writeFileSync as b1,existsSync as k1}from"fs";import{join as x1,relative as u0}from"path";import{randomBytes as p0}from"crypto";import{existsSync as b0,readFileSync as k0,writeFileSync as x0,mkdirSync as E1,readdirSync as A1,unlinkSync as w0}from"fs";import{join as k}from"path";var S0=20;function f(){let Z=process.env.HOME||process.env.USERPROFILE||"",$=k(Z,".tenicli","history");if(!b0($))E1($,{recursive:!0});return $}function H1(){return Date.now().toString(36)+Math.random().toString(36).slice(2,6)}function h0(Z){let $=k(f(),`${Z.id}.json`);x0($,JSON.stringify(Z,null,0),"utf8"),D1()}function z0(){let Z=f();try{return A1(Z).filter(($)=>$.endsWith(".json")).map(($)=>{try{return JSON.parse(k0(k(Z,$),"utf8"))}catch{return null}}).filter(($)=>$!==null).sort(($,Q)=>Q.updatedAt.localeCompare($.updatedAt))}catch{return[]}}function D1(){let Z=z0();if(Z.length<=S0)return;let $=f();for(let Q of Z.slice(S0))try{w0(k($,`${Q.id}.json`))}catch{}}function o(Z){let $=new Date().toISOString();return{id:H1(),title:"New conversation",model:Z,createdAt:$,updatedAt:$,messages:[],tokens:{input:0,output:0}}}function y0(Z){let $=k(f(),"__resume__.json");return x0($,JSON.stringify(Z,null,0),"utf8"),$}function f0(){let Z=k(f(),"__resume__.json");try{if(b0(Z)){let $=JSON.parse(k0(Z,"utf8"));return w0(Z),$}}catch{}return null}J0();var m0=v.version;function w1(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${m0}`),process.exit(0);case"-h":case"--help":h1(),process.exit(0);default:if(!Z[Q].startsWith("-"))$.prompt=Z.slice(Q).join(" "),Q=Z.length}Q++}return $}function h1(){console.log(`
|
|
300
|
+
${G.bold(G.blue("TeniCLI"))} — Lightweight AI Coding Agent
|
|
333
301
|
|
|
334
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
|
359
|
-
${
|
|
360
|
-
`),process.exit(0);case"/clear":return $.clear(),
|
|
361
|
-
${
|
|
362
|
-
${
|
|
363
|
-
${
|
|
364
|
-
${
|
|
365
|
-
${
|
|
366
|
-
${
|
|
367
|
-
${
|
|
368
|
-
${
|
|
369
|
-
${
|
|
370
|
-
${
|
|
371
|
-
${
|
|
372
|
-
${
|
|
373
|
-
${
|
|
374
|
-
${
|
|
375
|
-
${
|
|
376
|
-
${
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
${
|
|
382
|
-
`),
|
|
326
|
+
`)}async function y1(Z,$){switch(Z.toLowerCase().split(" ")[0]){case"/exit":case"/quit":case"/q":console.log(`
|
|
327
|
+
${G.gray("Bye!")} \uD83D\uDC4B
|
|
328
|
+
`),process.exit(0);case"/clear":return $.clear(),h.clear(),console.log(` ${E.ok} Conversation cleared`),!0;case"/compact":return await $.compact(),!0;case"/diff":{let Q=h.getChanges();if(Q.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 Q){let K=u0($.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: ${Q.length} files`)}`)}return!0}case"/undo":{let Q=h.undo();if(!Q)console.log(` ${G.gray("Nothing to undo.")}`);else{let V=u0($.cfg.cwd,Q.path);if(Q.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 Q=x1($.cfg.cwd,"TENICLI.md");if(k1(Q))console.log(` ${E.warn} TENICLI.md already exists.`);else b1(Q,v1,"utf8"),console.log(` ${E.ok} Created ${G.cyan("TENICLI.md")}`);return!0}case"/mode":{$.autoMode=!$.autoMode;let Q=$.autoMode?G.yellow("auto"):G.green("ask");return console.log(` ${E.ok} Mode: ${Q} ${G.gray($.autoMode?"(tools run without asking)":"(confirm write/exec)")}`),!0}case"/cost":{let Q=$.stats;return console.log(` ${E.ai} ${G.blue(String(Q.input))}↑ input ${G.blue(String(Q.output))}↓ output ${G.gray(`(${$.messageCount} msgs)`)}`),!0}case"/model":{let Q=p(),V=Q.customModels||[],K=[...x.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} ${$.cfg.provider.model===z.id?G.green("●"):""}`,desc:`${z.provider} • ${z.speed}`}));Y.push({label:"Custom model...",desc:"type model ID"});let X=await L("Select model",Y);if(X===-1)return console.log(` ${G.gray("Cancelled")}`),!0;if(X<K.length){let z=K[X];$.cfg.provider.model=z.id,$.cfg.provider.type=z.provider;let J=z.provider==="openai"?process.env.OPENAI_API_KEY||Q.keys?.openai||"":process.env.ANTHROPIC_API_KEY||Q.keys?.anthropic||"";if(J)$.cfg.provider.apiKey=J;if(!Q.baseUrls?.[z.provider])$.cfg.provider.baseUrl=z.provider==="openai"?"https://api.openai.com":"https://api.anthropic.com";m({activeModel:z.id}),console.log(` ${E.ok} Model: ${G.blue(z.name)}`)}else{let z=await L("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 M(` ${G.gray("model ID")} ${G.blue("❯")} `);if(!N.trim())return console.log(` ${G.gray("Cancelled")}`),!0;let W=N.trim();$.cfg.provider.model=W,$.cfg.provider.type=J;let _=V.filter((H)=>H.id!==W);_.push({id:W,provider:J}),m({activeModel:W,customModels:_});let q=J==="openai"?process.env.OPENAI_API_KEY||Q.keys?.openai||"":process.env.ANTHROPIC_API_KEY||Q.keys?.anthropic||"";if(q)$.cfg.provider.apiKey=q;console.log(` ${E.ok} Model: ${G.blue(W)} ${G.gray("(saved to list)")}`)}return!0}case"/auth":{let Q=await L("Provider",[{label:"Anthropic",desc:"Claude models"},{label:"OpenAI",desc:"GPT models"},{label:"Custom",desc:"Anthropic-compatible proxy"}]);if(Q===-1)return console.log(` ${G.gray("Cancelled")}`),!0;let K=["anthropic","openai","anthropic"][Q],Y=await M(` ${G.gray("API Key")} ${G.blue("❯")} `);if(!Y.trim())return console.log(` ${E.warn} Cancelled`),!0;let X={[K]:Y.trim()},z={};if(Q===2){let J=await M(` ${G.gray("Base URL")} ${G.blue("❯")} `);if(J.trim())z[K]=J.trim()}if(m({keys:X,baseUrls:z}),$.cfg.provider.apiKey=Y.trim(),$.cfg.provider.type=K,z[K])$.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:Q,renderQR:V}=await Promise.resolve().then(() => (g0(),v0)),{startServer:K,getActiveServers:Y}=await Promise.resolve().then(() => (_0(),W0)),X=Y();if(X.size>0){let q=Array.from(X.entries()).map(([U,D])=>({label:`Port ${U} ${G.green("●")}`,desc:`password: ${D.password}`}));q.push({label:G.cyan("+ New server"),desc:"start on random port"}),q.push({label:G.yellow("Stop all"),desc:"close all remote servers"});let H=await L("Remote servers",q);if(H===-1)return console.log(` ${G.gray("Cancelled")}`),!0;if(H<X.size){let U=Array.from(X.entries())[H],D=U[0],B=await L(`Server on port ${D}`,[{label:"Show QR & Local URL",desc:"display QR code for Wi-Fi access"},{label:"Publish to Internet",desc:"use localtunnel (free public URL)"},{label:"Stop server",desc:"close this port"}]);if(B===0){let O=`http://${Q()[0]||"localhost"}:${D}`;console.log(`
|
|
346
|
+
${G.gray("URL:")} ${G.cyan(O)}`),console.log(` ${G.gray("Password:")} ${G.yellow(U[1].password)}
|
|
347
|
+
`),console.log(V(O)),console.log()}else if(B===1){console.log(`
|
|
348
|
+
${E.tool} ${G.yellow("Starting localtunnel...")}`);try{let{spawn:A}=await import("child_process"),R=A(/^win/.test(process.platform)?"npx.cmd":"npx",["--yes","localtunnel","--port",String(D)]);R.unref(),await new Promise((O,T)=>{let r=setTimeout(()=>{T(Error("Localtunnel timed out"))},1e4);R.stdout.on("data",(d0)=>{let U0=d0.toString();if(U0.includes("your url is:")){clearTimeout(r);let l0=U0.split("your url is:")[1].trim();console.log(` ${E.ok} ${G.green("Published to Internet!")}
|
|
349
|
+
`),console.log(` ${G.gray("Public URL:")} ${G.cyan(l0)}`),console.log(` ${G.gray("Password:")} ${G.yellow(U[1].password)}
|
|
350
|
+
`),console.log(` ${G.gray("Note: The tunnel runs in the background.")}`),O()}}),R.stderr.on("data",()=>{})}),U[1].tunnel=R}catch(A){console.log(` ${E.warn} ${G.pink(`Failed to publish: ${A.message}`)}`)}}else if(B===2){if(U[1].close(),U[1].tunnel)try{U[1].tunnel.kill()}catch{}console.log(` ${E.ok} Stopped server on port ${D}`)}return!0}else if(H===X.size);else{for(let[U,D]of X)if(D.close(),D.tunnel)try{D.tunnel.kill()}catch{}return console.log(` ${E.ok} All remote servers stopped`),!0}}let z=3000+Math.floor(Math.random()*7000),J=p0(6).toString("hex"),_=`http://${Q()[0]||"localhost"}:${z}`;return K(z,J),console.log(),w([G.bold(G.green("Remote Access Enabled")),"",`${G.gray("URL:")} ${G.cyan(_)}`,`${G.gray("Password:")} ${G.yellow(J)}`,`${G.gray("Port:")} ${G.blue(String(z))}`,"",G.gray("Use /remote again to manage or publish this server.")],58),console.log(),console.log(V(_)),console.log(),!0}case"/quota":{let Q=$0;if(!Q.requestsLimit&&!Q.tokensLimit)return console.log(` ${G.gray("No rate limit data yet. Send a message first.")}`),!0;let V=[G.bold("API Rate Limits"),""];if(Q.requestsLimit!==void 0){let K=Q.requestsLimit-(Q.requestsRemaining||0),Y=Math.round((Q.requestsRemaining||0)/Q.requestsLimit*100),X=Y>50?G.green:Y>20?G.yellow:G.pink;V.push(`${G.gray("Requests:")} ${X(String(Q.requestsRemaining))}/${Q.requestsLimit} remaining ${G.gray(`(${Y}%)`)}`)}if(Q.tokensLimit!==void 0){let K=Math.round((Q.tokensRemaining||0)/Q.tokensLimit*100),Y=K>50?G.green:K>20?G.yellow:G.pink;V.push(`${G.gray("Tokens:")} ${Y(String(Q.tokensRemaining?.toLocaleString()))}/${Q.tokensLimit.toLocaleString()} remaining ${G.gray(`(${K}%)`)}`)}if(Q.requestsReset){let K=new Date(Q.requestsReset);V.push(`${G.gray("Resets at:")} ${G.cyan(K.toLocaleTimeString())}`)}return console.log(),w(V,58),!0}case"/history":{let Q=z0();if(Q.length===0)return console.log(` ${G.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()}`})),K=await L("Resume conversation",V);if(K===-1)return console.log(` ${G.gray("Cancelled")}`),!0;let Y=Q[K];return $.clear(),$.importState({messages:Y.messages,tokens:Y.tokens}),$.__convId=Y.id,console.log(` ${E.ok} Restored: ${G.blue(Y.title)} ${G.gray(`(${Y.messages.length} msgs)`)}`),!0}case"/update":{console.log(`
|
|
351
|
+
${E.tool} ${G.yellow("Updating tenicli...")}`);try{let Q=$.exportState(),V=o($.cfg.provider.model);V.title=$.getTitle(),V.messages=Q.messages,V.tokens=Q.tokens,y0(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...")}
|
|
352
|
+
`),Y(process.execPath,process.argv.slice(1),{stdio:"inherit",detached:!1}).on("exit",(z)=>process.exit(z||0)),!0}catch(Q){I(`Update failed: ${Q.message}`)}return!0}default:return console.log(` ${E.warn} Unknown: ${Z.split(" ")[0]} — try /help`),!0}}async function f1(){let Z=w1(process.argv.slice(2)),$=d();if(Z.model)$.provider.model=Z.model;if(Z.baseUrl)$.provider.baseUrl=Z.baseUrl;if(Z.serve){let N=Z.port||3000,W=Z.password||p0(6).toString("hex"),{startServer:_}=await Promise.resolve().then(() => (_0(),W0));_(N,W);return}let Q=new y($);if(Z.print&&Z.prompt){if(!$.provider.apiKey)I("No API key. Run: teni then /auth"),process.exit(1);await Q.send(Z.prompt),process.exit(0)}R0(m0);let V=x.find((N)=>N.id===$.provider.model)?.name||$.provider.model,K=Q.autoMode?G.yellow("auto"):G.green("ask"),Y=[`${G.gray("model")} ${G.blue(V)} ${G.gray("mode")} ${K} ${G.gray("cwd")} ${G.cyan($.cwd)}`];if(!$.provider.apiKey)Y.push(""),Y.push(`${E.warn} ${G.yellow("No API key configured. Run /auth to set one.")}`);w(Y,60),console.log();let X=f0(),z=o($.provider.model);if(X)Q.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(Q.messageCount===0)return;let N=Q.exportState();z.title=Q.getTitle(),z.messages=N.messages,z.tokens=N.tokens,z.model=$.provider.model,z.updatedAt=new Date().toISOString(),h0(z)};if(process.on("SIGINT",()=>{J(),console.log(`
|
|
353
|
+
${G.gray("Bye!")} \uD83D\uDC4B
|
|
354
|
+
`),process.exit(0)}),Z.prompt){if(console.log(` ${E.prompt} ${Z.prompt}`),$.provider.apiKey)await Q.send(Z.prompt);J()}while(!0)try{let W=(await B0()).trim();if(!W)continue;if(W.startsWith("/")){if(W==="/clear")J(),z=o($.provider.model);await y1(W,Q);continue}if(!Q.cfg.provider.apiKey){console.log(` ${E.warn} ${G.yellow("No API key. Run /auth first.")}`);continue}await Q.send(W),J()}catch(N){if(N.message==="EOF")J(),console.log(`
|
|
355
|
+
${G.gray("Bye!")} \uD83D\uDC4B
|
|
356
|
+
`),process.exit(0);I(N.message)}}f1().catch((Z)=>{I(Z.message),process.exit(1)});var v1=`# Project Instructions
|
|
383
357
|
|
|
384
358
|
## Overview
|
|
385
359
|
Describe your project here so the AI understands the context.
|