tenicli 0.1.0
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/README.md +135 -0
- package/README.vi.md +132 -0
- package/dist/index.js +102 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<pre>
|
|
3
|
+
██ ██
|
|
4
|
+
██████████ █████ ████ █ █ ███ ███ █ ███
|
|
5
|
+
███ ██ █ █ █ ██ █ █ █ █ █
|
|
6
|
+
██████████ █ ███ █ ██ █ █ █ █
|
|
7
|
+
██████████ █ █ █ █ █ █ █ █
|
|
8
|
+
██ ██ ██ █ ████ █ █ ███ ███ ████ ███
|
|
9
|
+
</pre>
|
|
10
|
+
|
|
11
|
+
**⚡ Lightweight AI coding agent for your terminal — fast, compact, multi-provider.**
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Zero dependencies** — Pure Bun + TypeScript, nothing else
|
|
18
|
+
- **Multi-provider** — Anthropic & OpenAI out of the box, BYOK (Bring Your Own Key)
|
|
19
|
+
- **Agentic** — Autonomous Plan → Execute → Verify loop with 5 built-in tools
|
|
20
|
+
- **Blazing fast** — Sub-200ms startup, compiles to single binary in <1s
|
|
21
|
+
- **Vietnamese-first** — Proper UTF-8 input that actually works (looking at you, Claude Code 👀)
|
|
22
|
+
- **Tokyo Night UI** — Beautiful 256-color terminal theme
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Install & run (requires Bun)
|
|
28
|
+
npx teni
|
|
29
|
+
|
|
30
|
+
# Or clone and run
|
|
31
|
+
git clone https://github.com/Nhqvu2005/TeniCli.git
|
|
32
|
+
cd TeniCli
|
|
33
|
+
bun install
|
|
34
|
+
bun run dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
On first launch, run `/auth` to set your API key:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
/auth
|
|
41
|
+
> 1. Anthropic
|
|
42
|
+
> API Key: sk-ant-xxxxx
|
|
43
|
+
✓ Saved to ~/.tenicli/config.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
teni # Interactive chat
|
|
50
|
+
teni "fix the auth bug" # Start with a prompt
|
|
51
|
+
teni -p "explain this" # Non-interactive (print & exit)
|
|
52
|
+
teni -m gpt-4o # Override model
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### In-Chat Commands
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `/model` | Switch AI model (Anthropic / OpenAI) |
|
|
60
|
+
| `/auth` | Configure API key |
|
|
61
|
+
| `/mode` | Toggle ask/auto (confirm before write/exec) |
|
|
62
|
+
| `/compact` | Summarize conversation to save tokens |
|
|
63
|
+
| `/diff` | List all files changed this session |
|
|
64
|
+
| `/undo` | Revert last file write |
|
|
65
|
+
| `/init` | Create `TENICLI.md` project template |
|
|
66
|
+
| `/clear` | Start new conversation |
|
|
67
|
+
| `/cost` | Show token usage |
|
|
68
|
+
| `/help` | List commands |
|
|
69
|
+
| `\\` | Continue input on next line |
|
|
70
|
+
|
|
71
|
+
## Built-in Tools
|
|
72
|
+
|
|
73
|
+
The agent can autonomously use these tools:
|
|
74
|
+
|
|
75
|
+
| Tool | Description |
|
|
76
|
+
|------|-------------|
|
|
77
|
+
| `read_file` | Read file contents (with optional line range) |
|
|
78
|
+
| `write_file` | Create or overwrite files |
|
|
79
|
+
| `list_dir` | List directory tree |
|
|
80
|
+
| `search_files` | Grep/ripgrep search across codebase |
|
|
81
|
+
| `exec_command` | Execute shell commands (30s timeout) |
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
### Environment Variables
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
TENICLI_API_KEY # API key (or ANTHROPIC_API_KEY / OPENAI_API_KEY)
|
|
89
|
+
TENICLI_BASE_URL # Custom API endpoint (for proxies)
|
|
90
|
+
TENICLI_MODEL # Default model
|
|
91
|
+
TENICLI_MAX_TOKENS # Max output tokens (default: 8192)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### System Prompt
|
|
95
|
+
|
|
96
|
+
Create a `TENICLI.md` in your project root (like `CLAUDE.md`) to customize the AI's behavior per project.
|
|
97
|
+
|
|
98
|
+
### Persistent Config
|
|
99
|
+
|
|
100
|
+
API keys and preferences are stored in `~/.tenicli/config.json`.
|
|
101
|
+
|
|
102
|
+
## Build
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Compile to single binary
|
|
106
|
+
bun run build:win # → teni.exe (Windows)
|
|
107
|
+
bun run build:linux # → teni (Linux)
|
|
108
|
+
bun run build:mac # → teni (macOS)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Architecture
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
src/
|
|
115
|
+
├── index.ts ← Entry point, CLI args, slash commands
|
|
116
|
+
├── ui.ts ← Tokyo Night colors, mascot, UTF-8 input
|
|
117
|
+
├── config.ts ← Multi-provider config, persistent storage
|
|
118
|
+
├── provider.ts ← Unified streaming (Anthropic + OpenAI SSE)
|
|
119
|
+
├── tools.ts ← 5 tools: read, write, list, search, exec
|
|
120
|
+
└── chat.ts ← Agentic loop with tool execution cycle
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Total: 6 files, ~700 lines, 0 runtime dependencies.**
|
|
124
|
+
|
|
125
|
+
## Roadmap
|
|
126
|
+
|
|
127
|
+
- [ ] More providers (Gemini, Ollama/local models)
|
|
128
|
+
- [ ] Web UI for remote access
|
|
129
|
+
- [ ] MCP (Model Context Protocol) support
|
|
130
|
+
- [ ] Session history & replay
|
|
131
|
+
- [ ] Plugin system
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT © [Yan Tenica](https://github.com/Nhqvu2005)
|
package/README.vi.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<pre>
|
|
3
|
+
██ ██
|
|
4
|
+
██████████ █████ ████ █ █ ███ ███ █ ███
|
|
5
|
+
███ ██ █ █ █ ██ █ █ █ █ █
|
|
6
|
+
██████████ █ ███ █ ██ █ █ █ █
|
|
7
|
+
██████████ █ █ █ █ █ █ █ █
|
|
8
|
+
██ ██ ██ █ ████ █ █ ███ ███ ████ ███
|
|
9
|
+
</pre>
|
|
10
|
+
|
|
11
|
+
**⚡ Trợ lý AI lập trình siêu nhẹ cho terminal — nhanh, đa nền tảng, tự chủ.**
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Tính Năng
|
|
16
|
+
|
|
17
|
+
- **Không phụ thuộc** — Chỉ Bun + TypeScript thuần, không cần cài thêm gì
|
|
18
|
+
- **Đa nhà cung cấp** — Anthropic & OpenAI có sẵn, dùng API key của bạn (BYOK)
|
|
19
|
+
- **Tự động hóa** — Vòng lặp Plan → Execute → Verify với 5 tool tích hợp
|
|
20
|
+
- **Cực nhanh** — Khởi động <200ms, compile thành binary đơn trong <1s
|
|
21
|
+
- **Hỗ trợ tiếng Việt** — Input UTF-8 chuẩn, không bị lỗi mất chữ như Claude Code
|
|
22
|
+
- **Giao diện Tokyo Night** — Theme 256-color đẹp mắt, dễ chịu
|
|
23
|
+
|
|
24
|
+
## Bắt Đầu Nhanh
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Clone và chạy
|
|
28
|
+
git clone https://github.com/Nhqvu2005/TeniCli.git
|
|
29
|
+
cd TeniCli
|
|
30
|
+
bun install
|
|
31
|
+
bun run dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Lần đầu chạy, dùng `/auth` để cài API key:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
/auth
|
|
38
|
+
> 1. Anthropic
|
|
39
|
+
> API Key: sk-ant-xxxxx
|
|
40
|
+
✓ Đã lưu vào ~/.tenicli/config.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Cách Dùng
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
teni # Chat tương tác
|
|
47
|
+
teni "sửa lỗi đăng nhập" # Bắt đầu với prompt
|
|
48
|
+
teni -p "giải thích code" # Chế độ non-interactive
|
|
49
|
+
teni -m gpt-4o # Chọn model khác
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Lệnh Trong Chat
|
|
53
|
+
|
|
54
|
+
| Lệnh | Mô tả |
|
|
55
|
+
|-------|-------|
|
|
56
|
+
| `/model` | Chuyển đổi model AI (Anthropic / OpenAI) |
|
|
57
|
+
| `/auth` | Cấu hình API key |
|
|
58
|
+
| `/mode` | Bật/tắt chế độ ask/auto (hỏi trước khi ghi/chạy lệnh) |
|
|
59
|
+
| `/compact` | Tóm tắt hội thoại bằng AI để tiết kiệm token |
|
|
60
|
+
| `/diff` | Xem danh sách file đã thay đổi trong session |
|
|
61
|
+
| `/undo` | Hoàn tác file vừa ghi |
|
|
62
|
+
| `/init` | Tạo file `TENICLI.md` template cho project |
|
|
63
|
+
| `/clear` | Bắt đầu cuộc trò chuyện mới |
|
|
64
|
+
| `/cost` | Xem token đã dùng |
|
|
65
|
+
| `/help` | Danh sách lệnh |
|
|
66
|
+
| `\\\\` | Xuống dòng tiếp tục nhập |
|
|
67
|
+
|
|
68
|
+
## Tool Tích Hợp
|
|
69
|
+
|
|
70
|
+
Agent có thể tự động sử dụng các tool sau:
|
|
71
|
+
|
|
72
|
+
| Tool | Mô tả |
|
|
73
|
+
|------|-------|
|
|
74
|
+
| `read_file` | Đọc nội dung file (hỗ trợ chọn dòng) |
|
|
75
|
+
| `write_file` | Tạo hoặc ghi đè file |
|
|
76
|
+
| `list_dir` | Liệt kê cây thư mục |
|
|
77
|
+
| `search_files` | Tìm kiếm text trong codebase (grep/ripgrep) |
|
|
78
|
+
| `exec_command` | Chạy lệnh shell (timeout 30s) |
|
|
79
|
+
|
|
80
|
+
## Cấu Hình
|
|
81
|
+
|
|
82
|
+
### Biến Môi Trường
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
TENICLI_API_KEY # API key (hoặc ANTHROPIC_API_KEY / OPENAI_API_KEY)
|
|
86
|
+
TENICLI_BASE_URL # Endpoint API tuỳ chỉnh (cho proxy)
|
|
87
|
+
TENICLI_MODEL # Model mặc định
|
|
88
|
+
TENICLI_MAX_TOKENS # Số token output tối đa (mặc định: 8192)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### System Prompt
|
|
92
|
+
|
|
93
|
+
Tạo file `TENICLI.md` trong thư mục gốc dự án (giống `CLAUDE.md`) để tuỳ chỉnh hành vi AI cho từng project.
|
|
94
|
+
|
|
95
|
+
### Cấu Hình Bền Vững
|
|
96
|
+
|
|
97
|
+
API keys và tuỳ chọn được lưu tại `~/.tenicli/config.json`.
|
|
98
|
+
|
|
99
|
+
## Build
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Compile thành binary đơn
|
|
103
|
+
bun run build:win # → teni.exe (Windows)
|
|
104
|
+
bun run build:linux # → teni (Linux)
|
|
105
|
+
bun run build:mac # → teni (macOS)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Kiến Trúc
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
src/
|
|
112
|
+
├── index.ts ← Entry point, phân tích args, lệnh slash
|
|
113
|
+
├── ui.ts ← Màu Tokyo Night, mascot, input UTF-8
|
|
114
|
+
├── config.ts ← Config đa provider, lưu trữ bền vững
|
|
115
|
+
├── provider.ts ← Streaming thống nhất (Anthropic + OpenAI SSE)
|
|
116
|
+
├── tools.ts ← 5 tools: đọc, ghi, liệt kê, tìm, thực thi
|
|
117
|
+
└── chat.ts ← Vòng lặp agentic với tool execution
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Tổng cộng: 6 files, ~700 dòng, 0 phụ thuộc runtime.**
|
|
121
|
+
|
|
122
|
+
## Lộ Trình
|
|
123
|
+
|
|
124
|
+
- [ ] Thêm provider (Gemini, Ollama/local models)
|
|
125
|
+
- [ ] Giao diện web để truy cập từ xa
|
|
126
|
+
- [ ] Hỗ trợ MCP (Model Context Protocol)
|
|
127
|
+
- [ ] Lịch sử session & replay
|
|
128
|
+
- [ ] Hệ thống plugin
|
|
129
|
+
|
|
130
|
+
## Giấy Phép
|
|
131
|
+
|
|
132
|
+
MIT © [Yan Tenica](https://github.com/Nhqvu2005)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createRequire as zQ}from"node:module";var GQ=zQ(import.meta.url);import{existsSync as C,readFileSync as b,writeFileSync as VQ,mkdirSync as XQ}from"fs";import{join as E}from"path";var D=[{id:"claude-sonnet-4-20250514",name:"Claude Sonnet 4",provider:"anthropic",speed:"fast"},{id:"claude-haiku-3-5-20241022",name:"Claude Haiku 3.5",provider:"anthropic",speed:"fast"},{id:"claude-opus-4-20250514",name:"Claude Opus 4",provider:"anthropic",speed:"slow"},{id:"gpt-4o",name:"GPT-4o",provider:"openai",speed:"fast"},{id:"gpt-4o-mini",name:"GPT-4o Mini",provider:"openai",speed:"fast"},{id:"o3-mini",name:"o3-mini",provider:"openai",speed:"normal"}];function h(){let Q=process.env.HOME||process.env.USERPROFILE||"";return E(Q,".tenicli")}function w(){return E(h(),"config.json")}function F(){try{if(C(w()))return JSON.parse(b(w(),"utf8"))}catch{}return{}}function P(Q){let Z=h();if(!C(Z))XQ(Z,{recursive:!0});let $=F(),z={...$,...Q,keys:{...$.keys,...Q.keys},baseUrls:{...$.baseUrls,...Q.baseUrls}};VQ(w(),JSON.stringify(z,null,2),"utf8")}function m(){let Q=process.cwd();u(E(Q,".tenicli.env")),u(E(Q,".env"));let Z=F(),$=process.env,z=$.TENICLI_MODEL||Z.activeModel||D[0].id,X=D.find((W)=>W.id===z)?.provider||$.TENICLI_PROVIDER||"anthropic",Y=YQ(X,Z,$),K=X==="openai"?"https://api.openai.com":"https://api.anthropic.com",q=$.TENICLI_BASE_URL||Z.baseUrls?.[X]||K;return{provider:{type:X,baseUrl:q,apiKey:Y,model:z},maxTokens:parseInt($.TENICLI_MAX_TOKENS||"8192"),systemPrompt:KQ(Q),cwd:Q}}function YQ(Q,Z,$){if(Q==="anthropic")return $.TENICLI_API_KEY||$.ANTHROPIC_API_KEY||Z.keys?.anthropic||"";if(Q==="openai")return $.TENICLI_API_KEY||$.OPENAI_API_KEY||Z.keys?.openai||"";return $.TENICLI_API_KEY||""}function u(Q){try{if(!C(Q))return;for(let Z of b(Q,"utf8").split(`
|
|
3
|
+
`)){let $=Z.trim();if(!$||$.startsWith("#"))continue;let z=$.indexOf("=");if(z===-1)continue;let V=$.slice(0,z).trim(),X=$.slice(z+1).trim();if(X.startsWith('"')&&X.endsWith('"')||X.startsWith("'")&&X.endsWith("'"))X=X.slice(1,-1);if(!process.env[V])process.env[V]=X}}catch{}}function KQ(Q){for(let Z of[E(Q,"TENICLI.md"),E(h(),"TENICLI.md")])if(C(Z))return b(Z,"utf8");return qQ}var qQ=`You are TeniCLI, a fast AI coding assistant in the terminal.
|
|
4
|
+
|
|
5
|
+
TOOLS: read/write files, execute commands, search code, list directories.
|
|
6
|
+
|
|
7
|
+
RULES:
|
|
8
|
+
- Be concise. Show only what matters.
|
|
9
|
+
- Use tools proactively — read before edit, verify after changes.
|
|
10
|
+
- Ask before destructive operations (delete, overwrite).
|
|
11
|
+
- The user may write in Vietnamese — respond in the same language they use.
|
|
12
|
+
- Write production-quality code matching the project's style.`;async function*x(Q,Z,$,z,V){if(Q.type==="openai")yield*JQ(Q,Z,$,z,V);else yield*WQ(Q,Z,$,z,V)}async function*WQ(Q,Z,$,z,V){let X=`${Q.baseUrl.replace(/\/$/,"")}/v1/messages`,Y={model:Q.model,max_tokens:V,system:$,messages:Z,stream:!0};if(z.length)Y.tools=z;let K=await p(X,Y,{"anthropic-version":"2023-06-01","x-api-key":Q.apiKey,authorization:`Bearer ${Q.apiKey}`});for await(let q of d(K))switch(q.type){case"message_start":if(q.message?.usage)yield{type:"usage",input:q.message.usage.input_tokens||0,output:0};break;case"content_block_start":if(q.content_block?.type==="text")yield{type:"text",text:""};else if(q.content_block?.type==="tool_use")yield{type:"tool_start",id:q.content_block.id,name:q.content_block.name};break;case"content_block_delta":if(q.delta?.type==="text_delta")yield{type:"text",text:q.delta.text};else if(q.delta?.type==="input_json_delta")yield{type:"tool_input",partial:q.delta.partial_json};break;case"content_block_stop":yield{type:"tool_end"};break;case"message_delta":if(q.usage)yield{type:"usage",input:0,output:q.usage.output_tokens||0};yield{type:"done",stopReason:q.delta?.stop_reason||"end_turn"};break}}async function*JQ(Q,Z,$,z,V){let X=`${Q.baseUrl.replace(/\/$/,"")}/v1/chat/completions`,Y=BQ(Z,$),K=z.map((_)=>({type:"function",function:{name:_.name,description:_.description,parameters:_.input_schema}})),q={model:Q.model,max_tokens:V,messages:Y,stream:!0,stream_options:{include_usage:!0}};if(K.length)q.tools=K;let W=await p(X,q,{authorization:`Bearer ${Q.apiKey}`}),N=new Map;for await(let _ of d(W)){let B=_.choices?.[0];if(!B){if(_.usage)yield{type:"usage",input:_.usage.prompt_tokens||0,output:_.usage.completion_tokens||0};continue}let A=B.delta||{};if(A.content)yield{type:"text",text:A.content};if(A.tool_calls)for(let R of A.tool_calls){if(R.id)N.set(R.index,{id:R.id,name:R.function?.name||"",args:""}),yield{type:"tool_start",id:R.id,name:R.function?.name||""};if(R.function?.arguments){let g=N.get(R.index);if(g)g.args+=R.function.arguments;yield{type:"tool_input",partial:R.function.arguments}}}if(B.finish_reason){for(let[,R]of N)yield{type:"tool_end"};yield{type:"done",stopReason:B.finish_reason==="tool_calls"?"tool_use":B.finish_reason}}}}function BQ(Q,Z){let $=[{role:"system",content:Z}];for(let z of Q)if(z.role==="user")if(typeof z.content==="string")$.push({role:"user",content:z.content});else{let V=z.content;for(let X of V)if(X.type==="tool_result")$.push({role:"tool",tool_call_id:X.tool_use_id,content:X.content||""});else $.push({role:"user",content:X.text||""})}else if(typeof z.content==="string")$.push({role:"assistant",content:z.content});else{let V=z.content,X=V.filter((K)=>K.type==="tool_use"),Y=V.filter((K)=>K.type==="text").map((K)=>K.text).join("");if(X.length)$.push({role:"assistant",content:Y||null,tool_calls:X.map((K)=>({id:K.id,type:"function",function:{name:K.name,arguments:JSON.stringify(K.input||{})}}))});else $.push({role:"assistant",content:Y})}return $}async function p(Q,Z,$){let z=await fetch(Q,{method:"POST",headers:{"content-type":"application/json",...$},body:JSON.stringify(Z)});if(!z.ok){let V=await z.text();throw Error(`API ${z.status}: ${V.slice(0,300)}`)}return z}async function*d(Q){let Z=Q.body.getReader(),$=new TextDecoder,z="";while(!0){let{done:V,value:X}=await Z.read();if(V)break;z+=$.decode(X,{stream:!0});let Y=z.split(`
|
|
13
|
+
`);z=Y.pop();for(let K of Y)if(K.startsWith("data: ")){let q=K.slice(6).trim();if(q==="[DONE]")return;try{yield JSON.parse(q)}catch{}}}}import{readFileSync as f,writeFileSync as i,existsSync as S,readdirSync as r,statSync as o,mkdirSync as NQ}from"fs";import{resolve as RQ,relative as k,join as t,dirname as HQ}from"path";var l=(Q)=>`\x1B[${Q}m`,L=(Q,Z)=>($)=>`${l(Q)}${$}${l(Z)}`,H=(Q)=>(Z)=>`\x1B[38;5;${Q}m${Z}\x1B[39m`,G={bold:L("1","22"),dim:L("2","22"),italic:L("3","23"),under:L("4","24"),blue:H(111),purple:H(141),green:H(149),yellow:H(179),pink:H(210),cyan:H(117),gray:H(60),text:H(146),orange:H(215)};var J={prompt:G.blue("❯"),ai:G.purple("◆"),tool:G.yellow("⚙"),ok:G.green("✓"),err:G.pink("✗"),warn:G.yellow("⚠"),arrow:G.gray("→"),dot:G.gray("•"),spinner:["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]};function _Q(){let Q=[" ██ ██ ","██████████","███ ██ █","██████████","██████████"," ██ ██ ██ "],Z={T:["█████"," █ "," █ "," █ "," █ "],E:["████ ","█ ","███ ","█ ","████ "],N:["█ █ ","██ █ ","█ ██ ","█ █ ","█ █ "],I:["███"," █ "," █ "," █ ","███"],space:[" "," "," "," "," "],C:[" ███","█ ","█ ","█ "," ███"],L:["█ ","█ ","█ ","█ ","████"]},$=[Z.T,Z.E,Z.N,Z.I,Z.space,Z.C,Z.L,Z.I],z=["","","","",""];for(let Y=0;Y<5;Y++)z[Y]=$.map((K)=>K[Y]).join(" ");let V=Q.map((Y)=>G.cyan(Y.padEnd(14," "))),X=[" ".repeat(z[0].length),...z].map((Y)=>G.blue(Y));return V.map((Y,K)=>`${Y} ${X[K]||""}`).join(`
|
|
14
|
+
`)}function a(){console.log(),console.log(_Q()),console.log(),console.log(G.gray(" ────────────────────────────────────────────────────────────────────────")),console.log(G.gray(" type to chat")+` ${J.dot} `+G.gray("/help for commands")+` ${J.dot} `+G.gray("v0.1.0")),console.log()}class T{i=0;timer=null;msg;constructor(Q="Thinking"){this.msg=Q}start(){return this.timer=setInterval(()=>{process.stdout.write(`\x1B[2K\r ${G.blue(J.spinner[this.i%J.spinner.length])} ${G.gray(this.msg)}`),this.i++},80),this}stop(){if(this.timer)clearInterval(this.timer),this.timer=null;process.stdout.write("\x1B[2K\r")}}function U(Q){return new Promise((Z,$)=>{process.stdout.write(Q);let z=[],V=(Y)=>{if(Y[0]===3)process.stdout.write(`
|
|
15
|
+
`),process.exit(0);if(Y[0]===4){X(),$(Error("EOF"));return}z.push(Y);let K=Buffer.concat(z).toString("utf8"),q=K.indexOf(`
|
|
16
|
+
`);if(q!==-1)X(),Z(K.slice(0,q).replace(/\r$/,""))},X=()=>{process.stdin.removeListener("data",V)};process.stdin.setEncoding("utf8"),process.stdin.on("data",V),process.stdin.resume()})}async function c(){let Q=[],Z=!0;while(!0){let $=Z?`
|
|
17
|
+
${J.prompt} `:` ${G.gray("│")} `,z=await U($);if(Z=!1,z.endsWith("\\"))Q.push(z.slice(0,-1));else{Q.push(z);break}}return Q.join(`
|
|
18
|
+
`)}async function y(Q,Z){console.log(`
|
|
19
|
+
${G.bold(Q)}`),Z.forEach(($,z)=>{let V=G.blue(` ${z+1}.`),X=$.desc?G.gray(` (${$.desc})`):"";console.log(`${V} ${$.label}${X}`)});while(!0){let $=await U(`
|
|
20
|
+
${G.gray("choose")} ${G.blue("❯")} `),z=parseInt($.trim());if(z>=1&&z<=Z.length)return z-1;console.log(` ${J.warn} enter 1-${Z.length}`)}}function I(Q,Z){console.log(`
|
|
21
|
+
${J.tool} ${G.yellow(Q)} ${G.gray(Z)}`)}function j(Q){console.error(` ${J.err} ${G.pink(Q)}`)}import{spawn as ZQ}from"child_process";class s{writes=[];recordWrite(Q,Z){let $=S(Q)?f(Q,"utf8"):null;this.writes.push({path:Q,backup:$,newLines:Z.split(`
|
|
22
|
+
`).length,time:new Date})}getChanges(){let Q=new Map;for(let Z of this.writes)Q.set(Z.path,{isNew:Z.backup===null,lines:Z.newLines,time:Z.time});return Array.from(Q.entries()).map(([Z,$])=>({path:Z,...$}))}undo(){let Q=this.writes.pop();if(!Q)return null;if(Q.backup!==null)return i(Q.path,Q.backup,"utf8"),{path:Q.path,restored:!0};else{try{GQ("fs").unlinkSync(Q.path)}catch{}return{path:Q.path,restored:!1}}}get count(){return this.writes.length}clear(){this.writes=[]}}var M=new s,e=[{name:"read_file",description:"Read contents of a file. Returns the file text.",input_schema:{type:"object",properties:{path:{type:"string",description:"File path (relative to cwd or absolute)"},start_line:{type:"number",description:"Optional: start line (1-indexed)"},end_line:{type:"number",description:"Optional: end line (1-indexed, inclusive)"}},required:["path"]}},{name:"write_file",description:"Write content to a file. Creates parent directories if needed.",input_schema:{type:"object",properties:{path:{type:"string",description:"File path"},content:{type:"string",description:"Full file content to write"}},required:["path","content"]}},{name:"list_dir",description:"List files and directories in a path. Returns names with type indicators.",input_schema:{type:"object",properties:{path:{type:"string",description:"Directory path (default: cwd)"},depth:{type:"number",description:"Max depth (default: 1)"}},required:[]}},{name:"search_files",description:"Search for text in files using pattern matching (like grep). Returns matching lines with file paths.",input_schema:{type:"object",properties:{pattern:{type:"string",description:"Text or regex pattern to search for"},path:{type:"string",description:"Directory to search in (default: cwd)"},include:{type:"string",description:'Glob pattern to filter files, e.g. "*.ts"'}},required:["pattern"]}},{name:"exec_command",description:"Execute a shell command. Returns stdout and stderr.",input_schema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute"},cwd:{type:"string",description:"Working directory (default: project cwd)"}},required:["command"]}}],UQ=30000;async function QQ(Q,Z,$){try{let z;switch(Q){case"read_file":z=AQ(Z,$),I("read_file",G.dim(k($,O(Z.path,$))));break;case"write_file":z=DQ(Z,$),I("write_file",G.dim(k($,O(Z.path,$))));break;case"list_dir":z=jQ(Z,$),I("list_dir",G.dim(Z.path||"."));break;case"search_files":z=await OQ(Z,$),I("search_files",G.dim(`"${Z.pattern}"`));break;case"exec_command":z=await EQ(Z,$),I("exec_command",G.dim(n(Z.command,60)));break;default:z=`Unknown tool: ${Q}`}return{type:"tool_result",content:n(z,UQ)}}catch(z){return{type:"tool_result",content:`Error: ${z.message}`,is_error:!0}}}function O(Q,Z){return RQ(Z,Q)}function AQ(Q,Z){let $=O(Q.path,Z);if(!S($))return`File not found: ${Q.path}`;let z=f($,"utf8"),V=z.split(`
|
|
23
|
+
`);if(Q.start_line||Q.end_line){let X=Math.max(1,Q.start_line||1)-1,Y=Math.min(V.length,Q.end_line||V.length);return V.slice(X,Y).map((K,q)=>`${X+q+1}: ${K}`).join(`
|
|
24
|
+
`)}if(V.length>50)return V.map((X,Y)=>`${Y+1}: ${X}`).join(`
|
|
25
|
+
`);return z}function DQ(Q,Z){let $=O(Q.path,Z),z=HQ($);if(!S(z))NQ(z,{recursive:!0});return M.recordWrite($,Q.content),i($,Q.content,"utf8"),`Written ${Q.content.split(`
|
|
26
|
+
`).length} lines to ${Q.path}`}function jQ(Q,Z){let $=O(Q.path||".",Z);if(!S($))return`Directory not found: ${Q.path||"."}`;let z=Q.depth||1,V=[];function X(Y,K){if(K>z)return;try{let q=r(Y);for(let W of q){if(W.startsWith(".")||W==="node_modules")continue;let N=t(Y,W),_=k($,N);try{let B=o(N),A=" ".repeat(K);if(B.isDirectory())V.push(`${A}${_}/`),X(N,K+1);else{let R=B.size>1024?`${(B.size/1024).toFixed(1)}KB`:`${B.size}B`;V.push(`${A}${_} (${R})`)}}catch{}}}catch{}}return X($,0),V.length>0?V.join(`
|
|
27
|
+
`):"(empty directory)"}async function OQ(Q,Z){let $=O(Q.path||".",Z),z=Q.pattern;try{let Y=["-n","--max-count=50","--no-heading"];if(Q.include)Y.push("--glob",Q.include);return Y.push(z,$),await new Promise((q,W)=>{let N=ZQ("rg",Y,{shell:!0}),_="";N.stdout.on("data",(B)=>_+=B.toString()),N.on("close",(B)=>{if(B===0||B===1)q(_.trim()||"No matches found.");else W(Error("rg failed"))}),N.on("error",W)})}catch{}let V=[];function X(Y){try{for(let K of r(Y)){if(K.startsWith(".")||K==="node_modules")continue;let q=t(Y,K);try{let W=o(q);if(W.isDirectory()){X(q);continue}if(W.size>500000)continue;if(Q.include&&!IQ(K,Q.include))continue;let _=f(q,"utf8").split(`
|
|
28
|
+
`);for(let B=0;B<_.length;B++)if(_[B].includes(z)){if(V.push(`${k(Z,q)}:${B+1}: ${_[B].trim()}`),V.length>=50)return}}catch{}}}catch{}}return X($),V.length>0?V.join(`
|
|
29
|
+
`):"No matches found."}async function EQ(Q,Z){let $=O(Q.cwd||".",Z),z=process.platform==="win32",V=z?"cmd":"sh",X=z?"/c":"-c";return new Promise((Y)=>{let K=ZQ(V,[X,Q.command],{cwd:$,env:{...process.env,PAGER:"cat"}}),q="",W="";K.stdout.on("data",(_)=>q+=_.toString()),K.stderr.on("data",(_)=>W+=_.toString());let N=setTimeout(()=>K.kill(),30000);K.on("close",(_)=>{clearTimeout(N);let B="";if(q.trim())B+=q.trim();if(W.trim())B+=(B?`
|
|
30
|
+
`:"")+`[stderr] ${W.trim()}`;B+=`
|
|
31
|
+
[exit code: ${_}]`,Y(B)}),K.on("error",(_)=>{clearTimeout(N),Y(`[error] Failed to start process: ${_.message}`)})})}function n(Q,Z){if(Q.length<=Z)return Q;return Q.slice(0,Z)+`
|
|
32
|
+
... (truncated, ${Q.length-Z} chars omitted)`}function IQ(Q,Z){if(Z.startsWith("*."))return Q.endsWith(Z.slice(1));return Q.includes(Z.replace(/\*/g,""))}class v{messages=[];tokens={input:0,output:0};cfg;autoMode=!1;constructor(Q){this.cfg=Q}async send(Q){this.messages.push({role:"user",content:Q}),await this.agentLoop()}async agentLoop(){while(!0){let Q=await this.streamResponse();if(Q.stopReason==="tool_use"){let $=Q.content.filter((V)=>V.type==="tool_use"),z=[];for(let V of $){if(!this.autoMode&&(V.name==="write_file"||V.name==="exec_command")){let Y=V.name==="write_file"?V.input?.path:V.input?.command?.slice(0,80);console.log(`
|
|
33
|
+
${J.warn} ${G.yellow(V.name)} ${G.gray(Y||"")}`);let q=(await U(` ${G.gray("allow?")} ${G.blue("[y/n/auto]")} `)).trim().toLowerCase();if(q==="auto")this.autoMode=!0;else if(q!=="y"&&q!=="yes"&&q!==""){z.push({type:"tool_result",tool_use_id:V.id,content:"User denied this action.",is_error:!0});continue}}let X=await QQ(V.name,V.input,this.cfg.cwd);z.push({type:"tool_result",tool_use_id:V.id,content:X.content,is_error:X.is_error})}this.messages.push({role:"assistant",content:Q.content}),this.messages.push({role:"user",content:z});continue}let Z=Q.content.filter(($)=>$.type==="text").map(($)=>$.text).join("");if(Z)this.messages.push({role:"assistant",content:Z});break}console.log(`
|
|
34
|
+
${G.gray(`tokens: ${this.tokens.input}↑ ${this.tokens.output}↓`)}`)}async streamResponse(){let Q=new T("Thinking").start(),Z=[],$="",z="",V="",X="",Y="end_turn",K=!1;try{let q=x(this.cfg.provider,this.messages,this.cfg.systemPrompt,e,this.cfg.maxTokens);for await(let W of q)switch(W.type){case"text":if(!K)Q.stop(),K=!0,process.stdout.write(`
|
|
35
|
+
${J.ai} `);if(W.text)process.stdout.write(W.text);$+=W.text;break;case"tool_start":if(!K)Q.stop(),K=!0;if($)Z.push({type:"text",text:$}),$="";z=W.id,V=W.name,X="";break;case"tool_input":X+=W.partial;break;case"tool_end":if(z){let N={};try{N=JSON.parse(X)}catch{}Z.push({type:"tool_use",id:z,name:V,input:N}),z="",X=""}break;case"usage":this.tokens.input+=W.input,this.tokens.output+=W.output;break;case"done":Y=W.stopReason;break}}catch(q){return Q.stop(),j(q.message),{content:[],stopReason:"error"}}if($)Z.push({type:"text",text:$}),process.stdout.write(`
|
|
36
|
+
`);if(!K)Q.stop();return{content:Z,stopReason:Y}}async compact(){if(this.messages.length<4){console.log(` ${J.warn} Not enough messages to compact.`);return}let Q=new T("Compacting").start();try{let Z="";for(let Y of this.messages)if(typeof Y.content==="string")Z+=`${Y.role}: ${Y.content.slice(0,500)}
|
|
37
|
+
`;else{let K=Y.content.filter((W)=>W.type==="text").map((W)=>W.text?.slice(0,300)).join(" ");if(K)Z+=`${Y.role}: ${K}
|
|
38
|
+
`;let q=Y.content.filter((W)=>W.type==="tool_use").map((W)=>`[tool: ${W.name}]`).join(", ");if(q)Z+=` tools: ${q}
|
|
39
|
+
`}let $=this.messages.length,z=`Summarize this conversation concisely. Keep key decisions, file changes, and current state. Be brief:
|
|
40
|
+
|
|
41
|
+
${Z.slice(0,6000)}`;this.messages=[{role:"user",content:z},{role:"assistant",content:`[Conversation compacted from ${$} messages. Summary of what happened:]`}];let V=x(this.cfg.provider,[{role:"user",content:z}],"You are a conversation summarizer. Create a brief summary preserving key facts, decisions, and file changes.",[],this.cfg.maxTokens),X="";for await(let Y of V){if(Y.type==="text"&&Y.text)X+=Y.text;if(Y.type==="usage")this.tokens.input+=Y.input,this.tokens.output+=Y.output}this.messages=[{role:"user",content:`[Previous conversation summary]
|
|
42
|
+
${X}`},{role:"assistant",content:"Understood. I have the context from our previous conversation. How can I continue helping you?"}],Q.stop(),console.log(` ${J.ok} Compacted ${$} messages → 2 ${G.gray(`(saved ~${Math.round(Z.length/4)} tokens)`)}`)}catch(Z){Q.stop(),j(`Compact failed: ${Z.message}`)}}get stats(){return this.tokens}get messageCount(){return this.messages.length}clear(){this.messages=[],this.tokens={input:0,output:0}}}import{writeFileSync as MQ,existsSync as CQ}from"fs";import{join as FQ,relative as $Q}from"path";var PQ="0.1.0";function LQ(Q){let Z={prompt:"",print:!1},$=0;while($<Q.length){switch(Q[$]){case"-p":case"--print":if(Z.print=!0,$+1<Q.length&&!Q[$+1].startsWith("-"))Z.prompt=Q[++$];break;case"-m":case"--model":Z.model=Q[++$];break;case"--base-url":Z.baseUrl=Q[++$];break;case"-v":case"--version":console.log(`teni v${PQ}`),process.exit(0);case"-h":case"--help":TQ(),process.exit(0);default:if(!Q[$].startsWith("-"))Z.prompt=Q.slice($).join(" "),$=Q.length}$++}return Z}function TQ(){console.log(`
|
|
43
|
+
${G.bold(G.blue("TeniCLI"))} — Lightweight AI Coding Agent
|
|
44
|
+
|
|
45
|
+
${G.bold("USAGE")}
|
|
46
|
+
teni Start chatting
|
|
47
|
+
teni "prompt" Start with a prompt
|
|
48
|
+
teni -p "prompt" Non-interactive mode
|
|
49
|
+
|
|
50
|
+
${G.bold("OPTIONS")}
|
|
51
|
+
-p, --print <prompt> Print response and exit
|
|
52
|
+
-m, --model <model> Override model
|
|
53
|
+
--base-url <url> Override API base URL
|
|
54
|
+
-v, --version Show version
|
|
55
|
+
-h, --help Show help
|
|
56
|
+
|
|
57
|
+
${G.bold("IN-CHAT")}
|
|
58
|
+
/model Select model /auth Set API key
|
|
59
|
+
/mode Ask/Auto toggle /compact Summarize chat
|
|
60
|
+
/diff Files changed /undo Revert last write
|
|
61
|
+
/init Create TENICLI.md /clear New conversation
|
|
62
|
+
/cost Token usage /exit Quit
|
|
63
|
+
\\\\ Multiline input
|
|
64
|
+
`)}async function kQ(Q,Z){switch(Q.toLowerCase().split(" ")[0]){case"/exit":case"/quit":case"/q":console.log(`
|
|
65
|
+
${G.gray("Bye!")} \uD83D\uDC4B
|
|
66
|
+
`),process.exit(0);case"/clear":return Z.clear(),M.clear(),console.log(` ${J.ok} Conversation cleared`),!0;case"/compact":return await Z.compact(),!0;case"/diff":{let $=M.getChanges();if($.length===0)console.log(` ${G.gray("No files changed in this session.")}`);else{console.log(`
|
|
67
|
+
${G.bold("Files changed this session:")}`);for(let z of $){let V=$Q(Z.cfg.cwd,z.path),X=z.isNew?G.green("[NEW]"):G.yellow("[MOD]");console.log(` ${X} ${G.cyan(V)} ${G.gray(`(${z.lines} lines)`)}`)}console.log(` ${G.gray(`total: ${$.length} files`)}`)}return!0}case"/undo":{let $=M.undo();if(!$)console.log(` ${G.gray("Nothing to undo.")}`);else{let z=$Q(Z.cfg.cwd,$.path);if($.restored)console.log(` ${J.ok} Restored: ${G.cyan(z)}`);else console.log(` ${J.ok} Deleted (was new): ${G.cyan(z)}`)}return!0}case"/init":{let $=FQ(Z.cfg.cwd,"TENICLI.md");if(CQ($))console.log(` ${J.warn} TENICLI.md already exists.`);else MQ($,wQ,"utf8"),console.log(` ${J.ok} Created ${G.cyan("TENICLI.md")}`);return!0}case"/mode":{Z.autoMode=!Z.autoMode;let $=Z.autoMode?G.yellow("auto"):G.green("ask");return console.log(` ${J.ok} Mode: ${$} ${G.gray(Z.autoMode?"(tools run without asking)":"(confirm write/exec)")}`),!0}case"/cost":{let $=Z.stats;return console.log(` ${J.ai} ${G.blue(String($.input))}↑ input ${G.blue(String($.output))}↓ output ${G.gray(`(${Z.messageCount} msgs)`)}`),!0}case"/model":{let $=D.map((V)=>({label:`${V.name} ${Z.cfg.provider.model===V.id?G.green("●"):""}`,desc:`${V.provider} • ${V.speed}`}));$.push({label:"Custom model...",desc:"type model ID"});let z=await y("Select model",$);if(z<D.length){let V=D[z];Z.cfg.provider.model=V.id,Z.cfg.provider.type=V.provider;let X=F(),Y=V.provider==="openai"?process.env.OPENAI_API_KEY||X.keys?.openai||"":process.env.ANTHROPIC_API_KEY||X.keys?.anthropic||"";if(Y)Z.cfg.provider.apiKey=Y;if(!X.baseUrls?.[V.provider])Z.cfg.provider.baseUrl=V.provider==="openai"?"https://api.openai.com":"https://api.anthropic.com";P({activeModel:V.id}),console.log(` ${J.ok} Model: ${G.blue(V.name)}`)}else{let V=await U(` ${G.gray("model ID")} ${G.blue("❯")} `);if(V.trim())Z.cfg.provider.model=V.trim(),P({activeModel:V.trim()}),console.log(` ${J.ok} Model: ${G.blue(V.trim())}`)}return!0}case"/auth":{let $=await y("Provider",[{label:"Anthropic",desc:"Claude models"},{label:"OpenAI",desc:"GPT models"},{label:"Custom",desc:"Anthropic-compatible proxy"}]),V=["anthropic","openai","anthropic"][$],X=await U(` ${G.gray("API Key")} ${G.blue("❯")} `);if(!X.trim())return console.log(` ${J.warn} Cancelled`),!0;let Y={[V]:X.trim()},K={};if($===2){let q=await U(` ${G.gray("Base URL")} ${G.blue("❯")} `);if(q.trim())K[V]=q.trim()}if(P({keys:Y,baseUrls:K}),Z.cfg.provider.apiKey=X.trim(),Z.cfg.provider.type=V,K[V])Z.cfg.provider.baseUrl=K[V];return console.log(` ${J.ok} ${V} key saved to ~/.tenicli/config.json`),!0}case"/help":return console.log(`
|
|
68
|
+
${G.bold("Commands")}
|
|
69
|
+
${G.blue("/model")} Select AI model
|
|
70
|
+
${G.blue("/auth")} Set API key
|
|
71
|
+
${G.blue("/mode")} Toggle ask/auto ${G.gray("(confirm before write/exec)")}
|
|
72
|
+
${G.blue("/compact")} Summarize conversation ${G.gray("(save tokens)")}
|
|
73
|
+
${G.blue("/diff")} List files changed this session
|
|
74
|
+
${G.blue("/undo")} Revert last file write
|
|
75
|
+
${G.blue("/init")} Create TENICLI.md template
|
|
76
|
+
${G.blue("/clear")} New conversation
|
|
77
|
+
${G.blue("/cost")} Show token usage
|
|
78
|
+
${G.blue("/exit")} Quit
|
|
79
|
+
${G.gray("\\\\")} Continue on next line`),!0;default:return console.log(` ${J.warn} Unknown: ${Q.split(" ")[0]} — try /help`),!0}}async function SQ(){let Q=LQ(process.argv.slice(2)),Z=m();if(Q.model)Z.provider.model=Q.model;if(Q.baseUrl)Z.provider.baseUrl=Q.baseUrl;let $=new v(Z);if(Q.print&&Q.prompt){if(!Z.provider.apiKey)j("No API key. Run: teni then /auth"),process.exit(1);await $.send(Q.prompt),process.exit(0)}a();let z=D.find((X)=>X.id===Z.provider.model)?.name||Z.provider.model,V=$.autoMode?G.yellow("auto"):G.green("ask");if(console.log(` ${G.gray("model")} ${G.blue(z)} ${G.gray("mode")} ${V} ${G.gray("cwd")} ${G.cyan(Z.cwd)}`),!Z.provider.apiKey)console.log(`
|
|
80
|
+
${J.warn} ${G.yellow("No API key configured. Run /auth to set one.")}`);if(console.log(),Q.prompt){if(console.log(` ${J.prompt} ${Q.prompt}`),Z.provider.apiKey)await $.send(Q.prompt)}while(!0)try{let Y=(await c()).trim();if(!Y)continue;if(Y.startsWith("/")){await kQ(Y,$);continue}if(!$.cfg.provider.apiKey){console.log(` ${J.warn} ${G.yellow("No API key. Run /auth first.")}`);continue}await $.send(Y)}catch(X){if(X.message==="EOF")console.log(`
|
|
81
|
+
${G.gray("Bye!")} \uD83D\uDC4B
|
|
82
|
+
`),process.exit(0);j(X.message)}}SQ().catch((Q)=>{j(Q.message),process.exit(1)});var wQ=`# Project Instructions
|
|
83
|
+
|
|
84
|
+
## Overview
|
|
85
|
+
Describe your project here so the AI understands the context.
|
|
86
|
+
|
|
87
|
+
## Tech Stack
|
|
88
|
+
- Language:
|
|
89
|
+
- Framework:
|
|
90
|
+
- Database:
|
|
91
|
+
|
|
92
|
+
## Coding Rules
|
|
93
|
+
- Follow existing code style
|
|
94
|
+
- Write tests for new features
|
|
95
|
+
- Use descriptive variable names
|
|
96
|
+
|
|
97
|
+
## File Structure
|
|
98
|
+
Describe important files and directories.
|
|
99
|
+
|
|
100
|
+
## Notes
|
|
101
|
+
Any special instructions or constraints.
|
|
102
|
+
`;
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tenicli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight AI coding CLI — fast, compact, multi-provider",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"teni": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["dist/", "README.md", "LICENSE"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "bun run src/index.ts",
|
|
12
|
+
"build:npm": "bun build src/index.ts --outfile dist/index.js --target node --minify",
|
|
13
|
+
"build": "bun build --compile --minify src/index.ts --outfile teni",
|
|
14
|
+
"build:win": "bun build --compile --minify --target=bun-windows-x64 src/index.ts --outfile teni.exe",
|
|
15
|
+
"build:linux": "bun build --compile --minify --target=bun-linux-x64 src/index.ts --outfile teni",
|
|
16
|
+
"build:mac": "bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile teni",
|
|
17
|
+
"prepublishOnly": "bun run build:npm"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["ai", "cli", "coding", "agent", "anthropic", "openai", "terminal"],
|
|
20
|
+
"author": "Yan Tenica",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/Nhqvu2005/TeniCli.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/Nhqvu2005/TeniCli",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/bun": "latest",
|
|
33
|
+
"@types/figlet": "^1.7.0",
|
|
34
|
+
"figlet": "^1.11.0"
|
|
35
|
+
}
|
|
36
|
+
}
|