@pentoshi/clai 0.2.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 +287 -0
- package/bin/clai.mjs +2 -0
- package/dist/agent/runner.d.ts +12 -0
- package/dist/agent/runner.js +249 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +29 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/providers.d.ts +13 -0
- package/dist/commands/providers.js +137 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/commands/update.d.ts +5 -0
- package/dist/commands/update.js +123 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +2 -0
- package/dist/llm/anthropic.js +127 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/gemini.d.ts +2 -0
- package/dist/llm/gemini.js +109 -0
- package/dist/llm/gemini.js.map +1 -0
- package/dist/llm/groq.d.ts +2 -0
- package/dist/llm/groq.js +49 -0
- package/dist/llm/groq.js.map +1 -0
- package/dist/llm/http.d.ts +35 -0
- package/dist/llm/http.js +112 -0
- package/dist/llm/http.js.map +1 -0
- package/dist/llm/ollama.d.ts +2 -0
- package/dist/llm/ollama.js +95 -0
- package/dist/llm/ollama.js.map +1 -0
- package/dist/llm/openai.d.ts +2 -0
- package/dist/llm/openai.js +49 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/openrouter.d.ts +2 -0
- package/dist/llm/openrouter.js +55 -0
- package/dist/llm/openrouter.js.map +1 -0
- package/dist/llm/provider.d.ts +23 -0
- package/dist/llm/provider.js +58 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/llm/router.d.ts +8 -0
- package/dist/llm/router.js +103 -0
- package/dist/llm/router.js.map +1 -0
- package/dist/modes/agent.d.ts +17 -0
- package/dist/modes/agent.js +6 -0
- package/dist/modes/agent.js.map +1 -0
- package/dist/modes/ask.d.ts +8 -0
- package/dist/modes/ask.js +46 -0
- package/dist/modes/ask.js.map +1 -0
- package/dist/os/detect.d.ts +10 -0
- package/dist/os/detect.js +17 -0
- package/dist/os/detect.js.map +1 -0
- package/dist/os/pkgmgr.d.ts +6 -0
- package/dist/os/pkgmgr.js +32 -0
- package/dist/os/pkgmgr.js.map +1 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.js +60 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/repl.d.ts +7 -0
- package/dist/repl.js +216 -0
- package/dist/repl.js.map +1 -0
- package/dist/safety/classifier.d.ts +8 -0
- package/dist/safety/classifier.js +118 -0
- package/dist/safety/classifier.js.map +1 -0
- package/dist/safety/patterns.d.ts +5 -0
- package/dist/safety/patterns.js +45 -0
- package/dist/safety/patterns.js.map +1 -0
- package/dist/store/config.d.ts +20 -0
- package/dist/store/config.js +46 -0
- package/dist/store/config.js.map +1 -0
- package/dist/store/history.d.ts +24 -0
- package/dist/store/history.js +145 -0
- package/dist/store/history.js.map +1 -0
- package/dist/store/keys.d.ts +10 -0
- package/dist/store/keys.js +115 -0
- package/dist/store/keys.js.map +1 -0
- package/dist/store/logs.d.ts +2 -0
- package/dist/store/logs.js +31 -0
- package/dist/store/logs.js.map +1 -0
- package/dist/store/project.d.ts +2 -0
- package/dist/store/project.js +14 -0
- package/dist/store/project.js.map +1 -0
- package/dist/tools/fs.d.ts +5 -0
- package/dist/tools/fs.js +82 -0
- package/dist/tools/fs.js.map +1 -0
- package/dist/tools/http.d.ts +6 -0
- package/dist/tools/http.js +14 -0
- package/dist/tools/http.js.map +1 -0
- package/dist/tools/registry.d.ts +5 -0
- package/dist/tools/registry.js +79 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/shell.d.ts +7 -0
- package/dist/tools/shell.js +16 -0
- package/dist/tools/shell.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/banner.d.ts +12 -0
- package/dist/ui/banner.js +55 -0
- package/dist/ui/banner.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# clai
|
|
2
|
+
|
|
3
|
+
> A fast, cross-platform AI CLI assistant with `/ask` and `/agent` modes for general shell tasks, file operations, and cybersecurity / pentesting workflows. Free to build, free to run.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **`/ask` mode** — Read-only. AI explains, gives commands & step-by-step guidance, but does NOT execute anything.
|
|
8
|
+
- **`/agent` mode** — Agentic. AI plans, then executes shell commands, edits files, installs missing tools, parses output, and continues until the goal is met.
|
|
9
|
+
- **6 LLM providers** — Groq, Google Gemini, OpenRouter, OpenAI, Anthropic, and Ollama (local). All with streaming support.
|
|
10
|
+
- **10 built-in tools** — `shell.exec`, `fs.read`, `fs.write`, `fs.list`, `fs.search`, `pkg.install`, `net.scan`, `http.fetch`, `sysinfo`, `pentest.recon`.
|
|
11
|
+
- **Safety gate** — 3-tier classifier (`safe` / `confirm` / `block`) with destructive pattern detection, public IP scan blocking, and exfiltration guards.
|
|
12
|
+
- **Cross-platform** — macOS, Linux, and Windows. Detects OS-native package managers (brew, apt, dnf, pacman, winget, choco).
|
|
13
|
+
- **Pentest-aware** — nmap, nikto, sqlmap, gobuster, ffuf, hydra, masscan, whois, dig, netcat, tshark integration with authorization gates.
|
|
14
|
+
- **Persistent history** — SQLite with JSONL fallback. Automatic key redaction in logs.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
# npm (any platform — requires Node.js ≥ 20)
|
|
20
|
+
npm i -g @pentoshi/clai
|
|
21
|
+
|
|
22
|
+
# macOS (Homebrew)
|
|
23
|
+
brew tap pentoshi007/clai
|
|
24
|
+
brew install clai
|
|
25
|
+
|
|
26
|
+
# Linux / macOS (curl)
|
|
27
|
+
curl -fsSL https://raw.githubusercontent.com/pentoshi007/clai/main/install/install.sh | sh
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
# Windows (PowerShell)
|
|
32
|
+
irm https://raw.githubusercontent.com/pentoshi007/clai/main/install/install.ps1 | iex
|
|
33
|
+
|
|
34
|
+
# Windows (Scoop)
|
|
35
|
+
scoop bucket add clai https://github.com/pentoshi007/clai
|
|
36
|
+
scoop install clai
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
# From source
|
|
41
|
+
git clone https://github.com/pentoshi007/clai.git
|
|
42
|
+
cd clai && npm install && npm run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
# Open interactive REPL
|
|
49
|
+
clai
|
|
50
|
+
|
|
51
|
+
# One-shot ask mode
|
|
52
|
+
clai --mode ask "create a python venv and install requests"
|
|
53
|
+
|
|
54
|
+
# One-shot agent mode
|
|
55
|
+
clai --mode agent "find all PDFs larger than 10MB in ~/Documents"
|
|
56
|
+
|
|
57
|
+
# With auto-confirm for agent mode
|
|
58
|
+
clai -y "list the 10 largest files in my home directory"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Provider Setup
|
|
62
|
+
|
|
63
|
+
clai supports 6 LLM providers with free tiers:
|
|
64
|
+
|
|
65
|
+
| Provider | Default Model | Free? | API Key Prefix |
|
|
66
|
+
|-------------|----------------------------------------------|-------|----------------|
|
|
67
|
+
| Groq | `llama-3.3-70b-versatile` | ✓ | `gsk_` |
|
|
68
|
+
| Gemini | `gemini-2.0-flash` | ✓ | `AIza` |
|
|
69
|
+
| OpenRouter | `meta-llama/llama-3.3-70b-instruct:free` | ✓ | `sk-or-` |
|
|
70
|
+
| OpenAI | `gpt-4o-mini` | — | `sk-` |
|
|
71
|
+
| Anthropic | `claude-3-5-haiku-latest` | — | `sk-ant-` |
|
|
72
|
+
| Ollama | `llama3.1:8b` | ✓ | (local URL) |
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
# Store an API key
|
|
76
|
+
clai set groq gsk_xxxxxxxxxxxxxxxx
|
|
77
|
+
|
|
78
|
+
# Import from environment variable
|
|
79
|
+
clai set gemini --from-env GEMINI_API_KEY
|
|
80
|
+
|
|
81
|
+
# Read from stdin (safer for shell history)
|
|
82
|
+
echo "gsk_xxx" | clai set groq --stdin
|
|
83
|
+
|
|
84
|
+
# Set Ollama endpoint
|
|
85
|
+
clai set ollama --url http://localhost:11434
|
|
86
|
+
|
|
87
|
+
# List configured providers (keys masked)
|
|
88
|
+
clai keys
|
|
89
|
+
|
|
90
|
+
# Switch active provider
|
|
91
|
+
clai use groq
|
|
92
|
+
|
|
93
|
+
# Interactive provider picker
|
|
94
|
+
clai provider
|
|
95
|
+
|
|
96
|
+
# Remove a key
|
|
97
|
+
clai unset groq
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Environment Variable Overrides
|
|
101
|
+
|
|
102
|
+
Runtime env vars override stored keys:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
export GROQ_API_KEY=gsk_...
|
|
106
|
+
export GEMINI_API_KEY=AIza...
|
|
107
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
108
|
+
export OPENAI_API_KEY=sk-...
|
|
109
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
110
|
+
export OLLAMA_HOST=http://localhost:11434
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## REPL Commands
|
|
114
|
+
|
|
115
|
+
| Command | Action |
|
|
116
|
+
|-------------------------|----------------------------------------------------|
|
|
117
|
+
| `/ask` | Switch to ask mode |
|
|
118
|
+
| `/agent` | Switch to agent mode |
|
|
119
|
+
| `/model <name>` | Switch LLM model |
|
|
120
|
+
| `/provider [name]` | Switch provider or open interactive picker |
|
|
121
|
+
| `/use <provider>` | Alias for `/provider <name>` |
|
|
122
|
+
| `/set <provider> [key]` | Store API key (masked input if key omitted) |
|
|
123
|
+
| `/unset <provider>` | Remove stored key |
|
|
124
|
+
| `/keys` | List configured providers, masked |
|
|
125
|
+
| `/clear` | Clear conversation context |
|
|
126
|
+
| `/history` | Show past sessions |
|
|
127
|
+
| `/save <name>` | Save current session |
|
|
128
|
+
| `/cwd <path>` | Change working directory |
|
|
129
|
+
| `/allow <tool>` | Whitelist a tool for the session |
|
|
130
|
+
| `/exit` | Quit |
|
|
131
|
+
| `/help` | List commands |
|
|
132
|
+
|
|
133
|
+
## Built-in Tools (Agent Mode)
|
|
134
|
+
|
|
135
|
+
| Tool | Description | Risk Level |
|
|
136
|
+
|------------------|--------------------------------------------------------------------|------------|
|
|
137
|
+
| `shell.exec` | Run shell commands via execa (120s timeout, streams output) | smart* |
|
|
138
|
+
| `fs.read` | Read files (sandboxed to approved roots) | safe |
|
|
139
|
+
| `fs.write` | Write files (sandboxed) | confirm |
|
|
140
|
+
| `fs.list` | List directory contents | safe |
|
|
141
|
+
| `fs.search` | Search files with ripgrep (falls back to grep) | safe |
|
|
142
|
+
| `pkg.install` | Install packages via detected OS package manager | confirm |
|
|
143
|
+
| `net.scan` | Nmap wrapper for port scanning | confirm |
|
|
144
|
+
| `http.fetch` | HTTP GET/POST with response size limits | safe |
|
|
145
|
+
| `sysinfo` | OS, architecture, shell, and working directory info | safe |
|
|
146
|
+
| `pentest.recon` | Composite: whois + dig + nmap top-100 ports | confirm |
|
|
147
|
+
|
|
148
|
+
## Safety Gate
|
|
149
|
+
|
|
150
|
+
Every tool call passes through a 3-tier classifier:
|
|
151
|
+
|
|
152
|
+
- **`safe`** — Auto-run (read-only fs, sysinfo, http.fetch, read-only shell commands like `curl`, `ls`, `whoami`, `ifconfig`, recon tools like `gobuster`, `dirb`)
|
|
153
|
+
- **`confirm`** — User prompt (mutating shell commands, fs.write, pkg.install, net.scan)
|
|
154
|
+
- **`block`** — Refuse with explanation (`rm -rf /`, fork bombs, public IP scans without authorization, exfiltration patterns)
|
|
155
|
+
|
|
156
|
+
### Pentest Authorization
|
|
157
|
+
|
|
158
|
+
Security tools require a one-time acknowledgment:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
clai authorize-pentest AGREE
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Public IP scanning is blocked unless the target is private (RFC 1918) or the user explicitly confirms ownership.
|
|
165
|
+
|
|
166
|
+
## Diagnostics
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
# Check system info, provider configuration, and available tools
|
|
170
|
+
clai doctor
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Outputs:
|
|
174
|
+
- OS, shell, architecture
|
|
175
|
+
- Config and history file paths
|
|
176
|
+
- Provider key status
|
|
177
|
+
- Available pentest tools with install commands for missing ones
|
|
178
|
+
|
|
179
|
+
## Per-Project Context
|
|
180
|
+
|
|
181
|
+
Create a `.clai/context.md` file in your project root to automatically inject project context into every prompt:
|
|
182
|
+
|
|
183
|
+
```md
|
|
184
|
+
This is a Node.js project using Express and PostgreSQL.
|
|
185
|
+
The API server runs on port 3000.
|
|
186
|
+
Database migrations are in the `migrations/` directory.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
Configuration is stored at `~/.config/clai/config.json` (varies by OS):
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
# Print config path and current settings
|
|
195
|
+
clai config
|
|
196
|
+
|
|
197
|
+
# Set default mode
|
|
198
|
+
clai mode agent
|
|
199
|
+
|
|
200
|
+
# Set model for current provider
|
|
201
|
+
clai model llama-3.3-70b-versatile
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
# Install dependencies
|
|
208
|
+
npm install
|
|
209
|
+
|
|
210
|
+
# Run in development mode
|
|
211
|
+
npm run dev
|
|
212
|
+
|
|
213
|
+
# Type check
|
|
214
|
+
npm run typecheck
|
|
215
|
+
|
|
216
|
+
# Build TypeScript
|
|
217
|
+
npm run build
|
|
218
|
+
|
|
219
|
+
# Run tests
|
|
220
|
+
npm test
|
|
221
|
+
|
|
222
|
+
# Build native binaries (requires Bun)
|
|
223
|
+
npm run compile
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Architecture
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
clai/
|
|
230
|
+
├─ src/
|
|
231
|
+
│ ├─ index.ts # CLI entry, argv parsing via commander
|
|
232
|
+
│ ├─ repl.ts # Interactive REPL with readline
|
|
233
|
+
│ ├─ modes/
|
|
234
|
+
│ │ ├─ ask.ts # Read-only mode (no tool execution)
|
|
235
|
+
│ │ └─ agent.ts # Agentic mode (tool execution)
|
|
236
|
+
│ ├─ agent/
|
|
237
|
+
│ │ └─ runner.ts # Agent loop: LLM → parse → classify → execute → loop
|
|
238
|
+
│ ├─ llm/
|
|
239
|
+
│ │ ├─ provider.ts # Provider interface & utilities
|
|
240
|
+
│ │ ├─ router.ts # Provider selection & fallback chain
|
|
241
|
+
│ │ ├─ http.ts # OpenAI-compatible HTTP client
|
|
242
|
+
│ │ ├─ groq.ts # Groq provider (streaming)
|
|
243
|
+
│ │ ├─ gemini.ts # Gemini provider (streaming)
|
|
244
|
+
│ │ ├─ ollama.ts # Ollama provider (streaming)
|
|
245
|
+
│ │ ├─ openai.ts # OpenAI provider (streaming)
|
|
246
|
+
│ │ ├─ anthropic.ts # Anthropic provider (streaming)
|
|
247
|
+
│ │ └─ openrouter.ts # OpenRouter provider (streaming)
|
|
248
|
+
│ ├─ tools/
|
|
249
|
+
│ │ ├─ registry.ts # Tool dispatch table
|
|
250
|
+
│ │ ├─ shell.ts # shell.exec via execa
|
|
251
|
+
│ │ ├─ fs.ts # Sandboxed file operations
|
|
252
|
+
│ │ └─ http.ts # HTTP fetch tool
|
|
253
|
+
│ ├─ safety/
|
|
254
|
+
│ │ ├─ classifier.ts # 3-tier risk classification
|
|
255
|
+
│ │ └─ patterns.ts # Destructive & exfiltration regexes
|
|
256
|
+
│ ├─ os/
|
|
257
|
+
│ │ ├─ detect.ts # OS/arch/shell detection
|
|
258
|
+
│ │ └─ pkgmgr.ts # Package manager detection
|
|
259
|
+
│ ├─ store/
|
|
260
|
+
│ │ ├─ config.ts # Persistent config via `conf`
|
|
261
|
+
│ │ ├─ history.ts # SQLite sessions + JSONL fallback
|
|
262
|
+
│ │ ├─ keys.ts # Keychain + fallback key storage
|
|
263
|
+
│ │ ├─ logs.ts # Audit log with rotation
|
|
264
|
+
│ │ └─ project.ts # Per-project context loader
|
|
265
|
+
│ ├─ commands/
|
|
266
|
+
│ │ ├─ doctor.ts # System diagnostics
|
|
267
|
+
│ │ └─ providers.ts # Provider management commands
|
|
268
|
+
│ └─ prompts/
|
|
269
|
+
│ ├─ index.ts # Prompt template renderer
|
|
270
|
+
│ ├─ system.ask.md # Ask mode system prompt
|
|
271
|
+
│ └─ system.agent.md # Agent mode system prompt
|
|
272
|
+
├─ bin/clai # Shebang launcher
|
|
273
|
+
├─ scripts/build.ts # Bun compile per target
|
|
274
|
+
├─ .github/workflows/
|
|
275
|
+
│ └─ release.yml # CI: build + publish binaries
|
|
276
|
+
├─ manifests/
|
|
277
|
+
│ ├─ homebrew/clai.rb # Homebrew formula
|
|
278
|
+
│ └─ scoop/clai.json # Scoop manifest
|
|
279
|
+
├─ install/install.sh # curl installer
|
|
280
|
+
├─ package.json
|
|
281
|
+
├─ tsconfig.json
|
|
282
|
+
└─ README.md
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
package/bin/clai.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatMessage, ProviderId, ToolCall, ToolResult } from "../types.js";
|
|
2
|
+
export interface AgentRunOptions {
|
|
3
|
+
provider?: ProviderId | undefined;
|
|
4
|
+
model?: string | undefined;
|
|
5
|
+
history?: ChatMessage[] | undefined;
|
|
6
|
+
autoConfirm?: boolean | undefined;
|
|
7
|
+
maxSteps?: number | undefined;
|
|
8
|
+
onToolStart?: ((call: ToolCall) => void) | undefined;
|
|
9
|
+
onToolResult?: ((call: ToolCall, result: ToolResult) => void) | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseToolCall(text: string): ToolCall | undefined;
|
|
12
|
+
export declare function runAgentLoop(prompt: string, options?: AgentRunOptions): Promise<string>;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { streamWithProvider } from "../llm/router.js";
|
|
4
|
+
import { renderAgentSystemPrompt } from "../prompts/index.js";
|
|
5
|
+
import { getConfig, updateConfig } from "../store/config.js";
|
|
6
|
+
import { classifyToolCall, isPentestToolCall } from "../safety/classifier.js";
|
|
7
|
+
import { availableToolNames, runToolCall } from "../tools/registry.js";
|
|
8
|
+
import { auditLog } from "../store/logs.js";
|
|
9
|
+
import { loadProjectContext } from "../store/project.js";
|
|
10
|
+
import { ensureProviderConfigured } from "../commands/providers.js";
|
|
11
|
+
function tryParseCall(raw) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(raw.trim());
|
|
14
|
+
if (typeof parsed.name === "string" &&
|
|
15
|
+
parsed.args &&
|
|
16
|
+
typeof parsed.args === "object") {
|
|
17
|
+
return {
|
|
18
|
+
name: parsed.name,
|
|
19
|
+
args: parsed.args,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// not valid JSON
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
export function parseToolCall(text) {
|
|
29
|
+
// 1. ```tool ... ``` (standard format)
|
|
30
|
+
const fenced = text.match(/```tool\s*\n?([\s\S]*?)```/i);
|
|
31
|
+
if (fenced?.[1]) {
|
|
32
|
+
const call = tryParseCall(fenced[1]);
|
|
33
|
+
if (call)
|
|
34
|
+
return call;
|
|
35
|
+
}
|
|
36
|
+
// 2. <tool_call>...</tool_call>
|
|
37
|
+
const xml = text.match(/<tool_call>([\s\S]*?)<\/tool_call>/i);
|
|
38
|
+
if (xml?.[1]) {
|
|
39
|
+
const call = tryParseCall(xml[1]);
|
|
40
|
+
if (call)
|
|
41
|
+
return call;
|
|
42
|
+
}
|
|
43
|
+
// 3. ### tool / ## tool / # tool heading + JSON
|
|
44
|
+
const heading = text.match(/#{1,3}\s*tool\s*\n\s*(\{[\s\S]*\})/i);
|
|
45
|
+
if (heading?.[1]) {
|
|
46
|
+
const call = tryParseCall(heading[1]);
|
|
47
|
+
if (call)
|
|
48
|
+
return call;
|
|
49
|
+
}
|
|
50
|
+
// 4. **tool** heading + JSON
|
|
51
|
+
const bold = text.match(/\*\*tool\*\*\s*\n\s*(\{[\s\S]*\})/i);
|
|
52
|
+
if (bold?.[1]) {
|
|
53
|
+
const call = tryParseCall(bold[1]);
|
|
54
|
+
if (call)
|
|
55
|
+
return call;
|
|
56
|
+
}
|
|
57
|
+
// 5. Any fenced block (```json, ```, etc.) containing name+args
|
|
58
|
+
const anyFenced = text.match(/```\w*\s*\n?([\s\S]*?)```/);
|
|
59
|
+
if (anyFenced?.[1]) {
|
|
60
|
+
const call = tryParseCall(anyFenced[1]);
|
|
61
|
+
if (call)
|
|
62
|
+
return call;
|
|
63
|
+
}
|
|
64
|
+
// 6. Trailing JSON object with "name" and "args"
|
|
65
|
+
const trailingJson = text.match(/(\{"name"\s*:\s*"[^"]+"\s*,\s*"args"\s*:\s*\{[\s\S]*?\}\s*\})\s*$/);
|
|
66
|
+
if (trailingJson?.[1]) {
|
|
67
|
+
const call = tryParseCall(trailingJson[1]);
|
|
68
|
+
if (call)
|
|
69
|
+
return call;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
/** Extract the text before the tool call block for display purposes */
|
|
74
|
+
function textBeforeToolCall(text) {
|
|
75
|
+
const patterns = [
|
|
76
|
+
/```tool\s*\n?[\s\S]*?```/i,
|
|
77
|
+
/<tool_call>[\s\S]*?<\/tool_call>/i,
|
|
78
|
+
/#{1,3}\s*tool\s*\n\s*\{[\s\S]*\}/i,
|
|
79
|
+
/\*\*tool\*\*\s*\n\s*\{[\s\S]*\}/i,
|
|
80
|
+
/```\w*\s*\n?\{[\s\S]*?"name"[\s\S]*?\}[\s\S]*?```/,
|
|
81
|
+
/\{"name"\s*:\s*"[^"]+"\s*,\s*"args"\s*:\s*\{[\s\S]*?\}\s*\}\s*$/,
|
|
82
|
+
];
|
|
83
|
+
for (const pattern of patterns) {
|
|
84
|
+
const idx = text.search(pattern);
|
|
85
|
+
if (idx >= 0) {
|
|
86
|
+
return text.slice(0, idx).trim();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return text.trim();
|
|
90
|
+
}
|
|
91
|
+
function formatToolArgs(call) {
|
|
92
|
+
if (call.name === "shell.exec")
|
|
93
|
+
return String(call.args.command ?? "");
|
|
94
|
+
if (call.name === "net.scan")
|
|
95
|
+
return `${call.args.target ?? ""}${call.args.ports ? ` -p ${call.args.ports}` : ""}`;
|
|
96
|
+
if (call.name === "pentest.recon")
|
|
97
|
+
return String(call.args.target ?? "");
|
|
98
|
+
if (call.name === "fs.read" || call.name === "fs.write")
|
|
99
|
+
return String(call.args.path ?? "");
|
|
100
|
+
if (call.name === "fs.search")
|
|
101
|
+
return String(call.args.pattern ?? "");
|
|
102
|
+
if (call.name === "http.fetch")
|
|
103
|
+
return String(call.args.url ?? "");
|
|
104
|
+
if (call.name === "pkg.install")
|
|
105
|
+
return String(call.args.tool ?? "");
|
|
106
|
+
if (call.name === "fs.list")
|
|
107
|
+
return String(call.args.path ?? process.cwd());
|
|
108
|
+
return JSON.stringify(call.args);
|
|
109
|
+
}
|
|
110
|
+
async function ensurePentestAuthorization(call, autoConfirm) {
|
|
111
|
+
const config = getConfig();
|
|
112
|
+
if (!isPentestToolCall(call) || config.pentestAuthorized)
|
|
113
|
+
return true;
|
|
114
|
+
if (autoConfirm) {
|
|
115
|
+
updateConfig({ pentestAuthorized: true });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
const ok = await confirm({
|
|
119
|
+
message: chalk.red("clai only assists with security testing on systems you own or have written permission to test. Confirm?"),
|
|
120
|
+
default: false,
|
|
121
|
+
});
|
|
122
|
+
if (!ok)
|
|
123
|
+
return false;
|
|
124
|
+
updateConfig({ pentestAuthorized: true });
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
async function confirmToolExecution(call, autoConfirm) {
|
|
128
|
+
const config = getConfig();
|
|
129
|
+
if (autoConfirm || config.allowAlwaysTools.includes(call.name))
|
|
130
|
+
return true;
|
|
131
|
+
return confirm({
|
|
132
|
+
message: chalk.yellow(` run ${call.name}: ${formatToolArgs(call)}?`),
|
|
133
|
+
default: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export async function runAgentLoop(prompt, options = {}) {
|
|
137
|
+
const config = getConfig();
|
|
138
|
+
const maxSteps = options.maxSteps ?? 25;
|
|
139
|
+
const projectContext = await loadProjectContext();
|
|
140
|
+
const systemPrompt = renderAgentSystemPrompt(availableToolNames().join(", "));
|
|
141
|
+
const fullSystemPrompt = projectContext
|
|
142
|
+
? `${systemPrompt}\n\nProject context from .clai/context.md:\n${projectContext}`
|
|
143
|
+
: systemPrompt;
|
|
144
|
+
const messages = [
|
|
145
|
+
{ role: "system", content: fullSystemPrompt },
|
|
146
|
+
...(options.history ?? []),
|
|
147
|
+
{ role: "user", content: prompt },
|
|
148
|
+
];
|
|
149
|
+
let provider = options.provider ?? config.defaultProvider;
|
|
150
|
+
await ensureProviderConfigured(provider);
|
|
151
|
+
let model = options.model ?? config.defaultModel;
|
|
152
|
+
let lastAnswer = "";
|
|
153
|
+
for (let step = 0; step < maxSteps; step += 1) {
|
|
154
|
+
// Stream LLM response to stdout
|
|
155
|
+
let streamed = false;
|
|
156
|
+
const completion = await streamWithProvider({
|
|
157
|
+
provider,
|
|
158
|
+
model,
|
|
159
|
+
messages,
|
|
160
|
+
temperature: 0.2,
|
|
161
|
+
maxTokens: 2_000,
|
|
162
|
+
}, (token) => {
|
|
163
|
+
// Buffer — we'll print selectively after we have the full response
|
|
164
|
+
// For now, don't stream directly since we need to strip tool call JSON
|
|
165
|
+
streamed = true;
|
|
166
|
+
});
|
|
167
|
+
provider = completion.provider;
|
|
168
|
+
model = completion.model;
|
|
169
|
+
const call = parseToolCall(completion.text);
|
|
170
|
+
if (!call) {
|
|
171
|
+
// Final answer — print it
|
|
172
|
+
process.stdout.write(completion.text);
|
|
173
|
+
process.stdout.write("\n");
|
|
174
|
+
await auditLog("agent.final", { provider, model, steps: step + 1 });
|
|
175
|
+
lastAnswer = completion.text;
|
|
176
|
+
return lastAnswer;
|
|
177
|
+
}
|
|
178
|
+
// Print only the thinking text, not the raw tool call JSON
|
|
179
|
+
const thinking = textBeforeToolCall(completion.text);
|
|
180
|
+
if (thinking) {
|
|
181
|
+
process.stdout.write(chalk.dim(thinking) + "\n");
|
|
182
|
+
}
|
|
183
|
+
messages.push({ role: "assistant", content: completion.text });
|
|
184
|
+
const decision = classifyToolCall(call);
|
|
185
|
+
await auditLog("tool.classified", { call, decision });
|
|
186
|
+
// Show tool call
|
|
187
|
+
process.stdout.write(chalk.cyan(` ▶ ${call.name}`) + chalk.gray(` ${formatToolArgs(call)}`) + "\n");
|
|
188
|
+
if (decision.level === "block") {
|
|
189
|
+
process.stdout.write(chalk.red(` ✗ blocked: ${decision.reason}`) + "\n");
|
|
190
|
+
lastAnswer = `Blocked: ${call.name} — ${decision.reason}`;
|
|
191
|
+
return lastAnswer;
|
|
192
|
+
}
|
|
193
|
+
// Pentest authorization — if user confirms this, skip the per-tool confirm
|
|
194
|
+
let pentestJustConfirmed = false;
|
|
195
|
+
const needsPentestAuth = isPentestToolCall(call) && !getConfig().pentestAuthorized;
|
|
196
|
+
const authorized = await ensurePentestAuthorization(call, Boolean(options.autoConfirm));
|
|
197
|
+
if (!authorized) {
|
|
198
|
+
lastAnswer = "Pentest authorization not confirmed.";
|
|
199
|
+
process.stdout.write(chalk.red(` ✗ ${lastAnswer}`) + "\n");
|
|
200
|
+
return lastAnswer;
|
|
201
|
+
}
|
|
202
|
+
if (needsPentestAuth) {
|
|
203
|
+
pentestJustConfirmed = true;
|
|
204
|
+
}
|
|
205
|
+
// Confirm if needed (safe tools auto-execute, pentest-auth'd tools skip)
|
|
206
|
+
if (decision.level === "confirm" && !pentestJustConfirmed) {
|
|
207
|
+
const ok = await confirmToolExecution(call, Boolean(options.autoConfirm));
|
|
208
|
+
if (!ok) {
|
|
209
|
+
lastAnswer = "Cancelled.";
|
|
210
|
+
process.stdout.write(chalk.yellow(` ✗ cancelled`) + "\n");
|
|
211
|
+
return lastAnswer;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Execute tool
|
|
215
|
+
options.onToolStart?.(call);
|
|
216
|
+
const result = await runToolCall(call);
|
|
217
|
+
options.onToolResult?.(call, result);
|
|
218
|
+
await auditLog("tool.result", {
|
|
219
|
+
call,
|
|
220
|
+
ok: result.ok,
|
|
221
|
+
exitCode: result.exitCode,
|
|
222
|
+
output: result.output.slice(0, 4_000),
|
|
223
|
+
});
|
|
224
|
+
// Print tool result
|
|
225
|
+
const statusIcon = result.ok ? chalk.green(" ✓") : chalk.red(" ✗");
|
|
226
|
+
process.stdout.write(statusIcon + "\n");
|
|
227
|
+
const output = result.output.trim();
|
|
228
|
+
if (output) {
|
|
229
|
+
const displayMax = 3_000;
|
|
230
|
+
const displayText = output.length > displayMax
|
|
231
|
+
? output.slice(0, displayMax) + chalk.dim("\n ... (truncated)")
|
|
232
|
+
: output;
|
|
233
|
+
process.stdout.write(chalk.gray(displayText) + "\n");
|
|
234
|
+
}
|
|
235
|
+
// Truncate output for LLM context to avoid blowing token limits
|
|
236
|
+
const contextMax = 4_000;
|
|
237
|
+
const contextOutput = output.length > contextMax
|
|
238
|
+
? output.slice(0, contextMax) + `\n... (output truncated — ${output.length} chars total, showing first ${contextMax})`
|
|
239
|
+
: output;
|
|
240
|
+
messages.push({
|
|
241
|
+
role: "tool",
|
|
242
|
+
content: `Tool ${call.name} result (exit=${result.exitCode ?? 0}, ok=${result.ok}):\n${contextOutput}`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
lastAnswer = `Stopped after ${maxSteps} steps.`;
|
|
246
|
+
process.stdout.write(chalk.yellow(lastAnswer) + "\n");
|
|
247
|
+
return lastAnswer;
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctor(): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { commandAvailable, detectPackageManager } from '../os/pkgmgr.js';
|
|
3
|
+
import { detectSystem } from '../os/detect.js';
|
|
4
|
+
import { getConfigPath } from '../store/config.js';
|
|
5
|
+
import { getHistoryPath } from '../store/history.js';
|
|
6
|
+
import { printProviderKeys } from './providers.js';
|
|
7
|
+
const pentestTools = ['nmap', 'nikto', 'sqlmap', 'gobuster', 'ffuf', 'hydra', 'masscan', 'whois', 'dig', 'nc', 'tshark'];
|
|
8
|
+
export async function runDoctor() {
|
|
9
|
+
const system = detectSystem();
|
|
10
|
+
const pkgmgr = await detectPackageManager();
|
|
11
|
+
console.log(chalk.bold('clai doctor'));
|
|
12
|
+
console.log(`OS: ${system.osName} ${system.release} ${system.arch}`);
|
|
13
|
+
console.log(`Shell: ${system.shell}`);
|
|
14
|
+
console.log(`CWD: ${system.cwd}`);
|
|
15
|
+
console.log(`Config: ${getConfigPath()}`);
|
|
16
|
+
console.log(`History: ${getHistoryPath()}`);
|
|
17
|
+
console.log(`Package manager: ${pkgmgr.id}`);
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(chalk.bold('Providers'));
|
|
20
|
+
await printProviderKeys();
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(chalk.bold('Tools'));
|
|
23
|
+
for (const tool of pentestTools) {
|
|
24
|
+
const available = await commandAvailable(tool);
|
|
25
|
+
const fix = available ? '' : ` · install: ${pkgmgr.installCommand(tool)}`;
|
|
26
|
+
console.log(`${available ? chalk.green('✓') : chalk.red('✗')} ${tool}${fix}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAEzH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,WAAW,aAAa,EAAE,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,YAAY,cAAc,EAAE,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IACrC,MAAM,iBAAiB,EAAE,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1E,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProviderId } from "../types.js";
|
|
2
|
+
export interface SetKeyOptions {
|
|
3
|
+
fromEnv?: string | undefined;
|
|
4
|
+
stdin?: boolean | undefined;
|
|
5
|
+
url?: string | undefined;
|
|
6
|
+
skipPing?: boolean | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function setProviderKey(providerValue: string, keyArg: string | undefined, options: SetKeyOptions): Promise<void>;
|
|
9
|
+
export declare function unsetProviderKey(providerValue: string): Promise<void>;
|
|
10
|
+
export declare function printProviderKeys(): Promise<void>;
|
|
11
|
+
export declare function ensureProviderConfigured(provider: ProviderId): Promise<void>;
|
|
12
|
+
export declare function useProvider(providerValue: string): Promise<void>;
|
|
13
|
+
export declare function providerSwitcher(providerValue?: string | undefined): Promise<void>;
|