@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.
Files changed (102) hide show
  1. package/README.md +287 -0
  2. package/bin/clai.mjs +2 -0
  3. package/dist/agent/runner.d.ts +12 -0
  4. package/dist/agent/runner.js +249 -0
  5. package/dist/agent/runner.js.map +1 -0
  6. package/dist/commands/doctor.d.ts +1 -0
  7. package/dist/commands/doctor.js +29 -0
  8. package/dist/commands/doctor.js.map +1 -0
  9. package/dist/commands/providers.d.ts +13 -0
  10. package/dist/commands/providers.js +137 -0
  11. package/dist/commands/providers.js.map +1 -0
  12. package/dist/commands/update.d.ts +5 -0
  13. package/dist/commands/update.js +123 -0
  14. package/dist/commands/update.js.map +1 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +172 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/llm/anthropic.d.ts +2 -0
  19. package/dist/llm/anthropic.js +127 -0
  20. package/dist/llm/anthropic.js.map +1 -0
  21. package/dist/llm/gemini.d.ts +2 -0
  22. package/dist/llm/gemini.js +109 -0
  23. package/dist/llm/gemini.js.map +1 -0
  24. package/dist/llm/groq.d.ts +2 -0
  25. package/dist/llm/groq.js +49 -0
  26. package/dist/llm/groq.js.map +1 -0
  27. package/dist/llm/http.d.ts +35 -0
  28. package/dist/llm/http.js +112 -0
  29. package/dist/llm/http.js.map +1 -0
  30. package/dist/llm/ollama.d.ts +2 -0
  31. package/dist/llm/ollama.js +95 -0
  32. package/dist/llm/ollama.js.map +1 -0
  33. package/dist/llm/openai.d.ts +2 -0
  34. package/dist/llm/openai.js +49 -0
  35. package/dist/llm/openai.js.map +1 -0
  36. package/dist/llm/openrouter.d.ts +2 -0
  37. package/dist/llm/openrouter.js +55 -0
  38. package/dist/llm/openrouter.js.map +1 -0
  39. package/dist/llm/provider.d.ts +23 -0
  40. package/dist/llm/provider.js +58 -0
  41. package/dist/llm/provider.js.map +1 -0
  42. package/dist/llm/router.d.ts +8 -0
  43. package/dist/llm/router.js +103 -0
  44. package/dist/llm/router.js.map +1 -0
  45. package/dist/modes/agent.d.ts +17 -0
  46. package/dist/modes/agent.js +6 -0
  47. package/dist/modes/agent.js.map +1 -0
  48. package/dist/modes/ask.d.ts +8 -0
  49. package/dist/modes/ask.js +46 -0
  50. package/dist/modes/ask.js.map +1 -0
  51. package/dist/os/detect.d.ts +10 -0
  52. package/dist/os/detect.js +17 -0
  53. package/dist/os/detect.js.map +1 -0
  54. package/dist/os/pkgmgr.d.ts +6 -0
  55. package/dist/os/pkgmgr.js +32 -0
  56. package/dist/os/pkgmgr.js.map +1 -0
  57. package/dist/prompts/index.d.ts +2 -0
  58. package/dist/prompts/index.js +60 -0
  59. package/dist/prompts/index.js.map +1 -0
  60. package/dist/repl.d.ts +7 -0
  61. package/dist/repl.js +216 -0
  62. package/dist/repl.js.map +1 -0
  63. package/dist/safety/classifier.d.ts +8 -0
  64. package/dist/safety/classifier.js +118 -0
  65. package/dist/safety/classifier.js.map +1 -0
  66. package/dist/safety/patterns.d.ts +5 -0
  67. package/dist/safety/patterns.js +45 -0
  68. package/dist/safety/patterns.js.map +1 -0
  69. package/dist/store/config.d.ts +20 -0
  70. package/dist/store/config.js +46 -0
  71. package/dist/store/config.js.map +1 -0
  72. package/dist/store/history.d.ts +24 -0
  73. package/dist/store/history.js +145 -0
  74. package/dist/store/history.js.map +1 -0
  75. package/dist/store/keys.d.ts +10 -0
  76. package/dist/store/keys.js +115 -0
  77. package/dist/store/keys.js.map +1 -0
  78. package/dist/store/logs.d.ts +2 -0
  79. package/dist/store/logs.js +31 -0
  80. package/dist/store/logs.js.map +1 -0
  81. package/dist/store/project.d.ts +2 -0
  82. package/dist/store/project.js +14 -0
  83. package/dist/store/project.js.map +1 -0
  84. package/dist/tools/fs.d.ts +5 -0
  85. package/dist/tools/fs.js +82 -0
  86. package/dist/tools/fs.js.map +1 -0
  87. package/dist/tools/http.d.ts +6 -0
  88. package/dist/tools/http.js +14 -0
  89. package/dist/tools/http.js.map +1 -0
  90. package/dist/tools/registry.d.ts +5 -0
  91. package/dist/tools/registry.js +79 -0
  92. package/dist/tools/registry.js.map +1 -0
  93. package/dist/tools/shell.d.ts +7 -0
  94. package/dist/tools/shell.js +16 -0
  95. package/dist/tools/shell.js.map +1 -0
  96. package/dist/types.d.ts +40 -0
  97. package/dist/types.js +9 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/ui/banner.d.ts +12 -0
  100. package/dist/ui/banner.js +55 -0
  101. package/dist/ui/banner.js.map +1 -0
  102. 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,2 @@
1
+ #!/usr/bin/env node
2
+ await import('../dist/index.js');
@@ -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":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAYpE,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAsB,CAAC;QAC3D,IACE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,MAAM,CAAC,IAAI;YACX,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAC/B,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,MAAM,CAAC,IAA+B;aAC7C,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,uCAAuC;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACzD,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxB,CAAC;IAED,gCAAgC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAC9D,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxB,CAAC;IAED,gDAAgD;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAClE,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxB,CAAC;IAED,6BAA6B;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAC9D,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxB,CAAC;IAED,gEAAgE;IAChE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC1D,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxB,CAAC;IAED,iDAAiD;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACrG,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,uEAAuE;AACvE,SAAS,kBAAkB,CAAC,IAAY;IACtC,MAAM,QAAQ,GAAG;QACf,2BAA2B;QAC3B,mCAAmC;QACnC,mCAAmC;QACnC,kCAAkC;QAClC,mDAAmD;QACnD,iEAAiE;KAClE,CAAC;IACF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC;AAED,SAAS,cAAc,CAAC,IAAc;IACpC,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACvE,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACnH,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACzE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC7F,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;IACnE,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACrE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5E,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,0BAA0B,CACvC,IAAc,EACd,WAAoB;IAEpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAEtE,IAAI,WAAW,EAAE,CAAC;QAChB,YAAY,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,yGAAyG,CAAC;QAC7H,OAAO,EAAE,KAAK;KACf,CAAC,CAAC;IACH,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACtB,YAAY,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,IAAc,EACd,WAAoB;IAEpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,WAAW,IAAI,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE5E,OAAO,OAAO,CAAC;QACb,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;QACrE,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,UAA2B,EAAE;IAE7B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;IACxC,MAAM,cAAc,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAClD,MAAM,YAAY,GAAG,uBAAuB,CAAC,kBAAkB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9E,MAAM,gBAAgB,GAAG,cAAc;QACrC,CAAC,CAAC,GAAG,YAAY,+CAA+C,cAAc,EAAE;QAChF,CAAC,CAAC,YAAY,CAAC;IACjB,MAAM,QAAQ,GAAkB;QAC9B,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE;QAC7C,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QAC1B,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;KAClC,CAAC;IAEF,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,eAAe,CAAC;IAC1D,MAAM,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC,YAAY,CAAC;IACjD,IAAI,UAAU,GAAG,EAAE,CAAC;IAEpB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;QAC9C,gCAAgC;QAChC,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,UAAU,GAAG,MAAM,kBAAkB,CACzC;YACE,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW,EAAE,GAAG;YAChB,SAAS,EAAE,KAAK;SACjB,EACD,CAAC,KAAK,EAAE,EAAE;YACR,mEAAmE;YACnE,uEAAuE;YACvE,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC,CACF,CAAC;QACF,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;QAC/B,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;QAEzB,MAAM,IAAI,GAAG,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,0BAA0B;YAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,MAAM,QAAQ,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;YACpE,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC;YAC7B,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,2DAA2D;QAC3D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;QACnD,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,QAAQ,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEtD,iBAAiB;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QAErG,IAAI,QAAQ,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAC1E,UAAU,GAAG,YAAY,IAAI,CAAC,IAAI,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC1D,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,2EAA2E;QAC3E,IAAI,oBAAoB,GAAG,KAAK,CAAC;QACjC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,iBAAiB,CAAC;QACnF,MAAM,UAAU,GAAG,MAAM,0BAA0B,CACjD,IAAI,EACJ,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAC7B,CAAC;QACF,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,UAAU,GAAG,sCAAsC,CAAC;YACpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAC5D,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,IAAI,gBAAgB,EAAE,CAAC;YACrB,oBAAoB,GAAG,IAAI,CAAC;QAC9B,CAAC;QAED,yEAAyE;QACzE,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC1D,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;YAC1E,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,UAAU,GAAG,YAAY,CAAC;gBAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;gBAC3D,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAED,eAAe;QACf,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,QAAQ,CAAC,aAAa,EAAE;YAC5B,IAAI;YACJ,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;SACtC,CAAC,CAAC;QAEH,oBAAoB;QACpB,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,KAAK,CAAC;YACzB,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,GAAG,UAAU;gBAC5C,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC;gBAChE,CAAC,CAAC,MAAM,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC;QACvD,CAAC;QAED,gEAAgE;QAChE,MAAM,UAAU,GAAG,KAAK,CAAC;QACzB,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,GAAG,UAAU;YAC9C,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,6BAA6B,MAAM,CAAC,MAAM,+BAA+B,UAAU,GAAG;YACtH,CAAC,CAAC,MAAM,CAAC;QACX,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,iBAAiB,MAAM,CAAC,QAAQ,IAAI,CAAC,QAAQ,MAAM,CAAC,EAAE,OAAO,aAAa,EAAE;SACvG,CAAC,CAAC;IACL,CAAC;IAED,UAAU,GAAG,iBAAiB,QAAQ,SAAS,CAAC;IAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -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>;