@pentoshi/clai 4.3.0 → 4.5.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 +45 -7
- package/dist/agent/runner.js +12 -13
- package/dist/agent/runner.js.map +1 -1
- package/dist/app/commands/catalog.js +4 -5
- package/dist/app/commands/catalog.js.map +1 -1
- package/dist/app/commands/registry.js +0 -1
- package/dist/app/commands/registry.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +3 -0
- package/dist/app/controllers/session-controller.js +11 -25
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/session-persistence.d.ts +4 -1
- package/dist/app/controllers/session-persistence.js +23 -5
- package/dist/app/controllers/session-persistence.js.map +1 -1
- package/dist/app/controllers/session-responder.d.ts +1 -0
- package/dist/app/controllers/session-responder.js +8 -0
- package/dist/app/controllers/session-responder.js.map +1 -1
- package/dist/app/controllers/session-usage-ledger.d.ts +63 -0
- package/dist/app/controllers/session-usage-ledger.js +232 -0
- package/dist/app/controllers/session-usage-ledger.js.map +1 -0
- package/dist/classic/app/ClassicApp.js +2 -1
- package/dist/classic/app/ClassicApp.js.map +1 -1
- package/dist/classic/chrome/toast-rows.d.ts +1 -0
- package/dist/classic/chrome/toast-rows.js +18 -0
- package/dist/classic/chrome/toast-rows.js.map +1 -1
- package/dist/commands/update-install.d.ts +2 -0
- package/dist/commands/update-install.js +47 -4
- package/dist/commands/update-install.js.map +1 -1
- package/dist/commands/update.js +34 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/llm/capabilities.d.ts +2 -1
- package/dist/llm/capabilities.js +13 -95
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/custom-provider-profile.js +2 -2
- package/dist/llm/custom-provider-profile.js.map +1 -1
- package/dist/llm/http.js +33 -0
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/key-rotation.d.ts +1 -0
- package/dist/llm/key-rotation.js +8 -0
- package/dist/llm/key-rotation.js.map +1 -1
- package/dist/llm/provider-profile-layers.d.ts +1 -0
- package/dist/llm/provider-profile-layers.js +44 -15
- package/dist/llm/provider-profile-layers.js.map +1 -1
- package/dist/llm/provider-profile.d.ts +1 -0
- package/dist/llm/provider-profile.js +5 -0
- package/dist/llm/provider-profile.js.map +1 -1
- package/dist/llm/provider-profiles.js +9 -6
- package/dist/llm/provider-profiles.js.map +1 -1
- package/dist/llm/provider.js +3 -3
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/reasoning-capability.d.ts +3 -0
- package/dist/llm/reasoning-capability.js +96 -0
- package/dist/llm/reasoning-capability.js.map +1 -0
- package/dist/llm/stream-terminal.js +2 -1
- package/dist/llm/stream-terminal.js.map +1 -1
- package/dist/llm/system-messages.d.ts +1 -0
- package/dist/llm/system-messages.js +7 -0
- package/dist/llm/system-messages.js.map +1 -1
- package/dist/store/history.d.ts +1 -0
- package/dist/store/history.js +7 -0
- package/dist/store/history.js.map +1 -1
- package/dist/store/scope.d.ts +8 -0
- package/dist/store/scope.js +138 -1
- package/dist/store/scope.js.map +1 -1
- package/dist/tools/interactive-session-tools.js +5 -5
- package/dist/tools/interactive-session-tools.js.map +1 -1
- package/dist/tools/registry.js +2 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/ui-core/bootstrap/composition-root.js +8 -2
- package/dist/ui-core/bootstrap/composition-root.js.map +1 -1
- package/dist/ui-core/commands/command-handlers.js +2 -1
- package/dist/ui-core/commands/command-handlers.js.map +1 -1
- package/dist/ui-core/commands/config-commands.js +17 -14
- package/dist/ui-core/commands/config-commands.js.map +1 -1
- package/dist/ui-core/commands/picker-commands.js +3 -3
- package/dist/ui-core/commands/picker-commands.js.map +1 -1
- package/dist/ui-core/commands/session-commands.d.ts +1 -0
- package/dist/ui-core/commands/session-commands.js +20 -0
- package/dist/ui-core/commands/session-commands.js.map +1 -1
- package/dist/ui-core/rendering/format-help.js +2 -2
- package/dist/ui-core/rendering/format-help.js.map +1 -1
- package/dist/ui-core/rendering/format-usage.d.ts +6 -0
- package/dist/ui-core/rendering/format-usage.js +116 -0
- package/dist/ui-core/rendering/format-usage.js.map +1 -0
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Two things make it practical for everyday use:
|
|
|
13
13
|
|
|
14
14
|
## Highlights
|
|
15
15
|
|
|
16
|
-
- **Free-tier first.** 18 providers wired in, 6 cloud free tiers + local Ollama. The default provider is the **keyless Free** gateway (`free-
|
|
16
|
+
- **Free-tier first.** 18 providers wired in, 6 cloud free tiers + local Ollama. The default provider is the **keyless Free** gateway (`free-2/kilo-auto/free`) so a fresh install runs at no cost with zero setup — no API key required.
|
|
17
17
|
- **Multi-key smart switching.** Up to 10 keys per provider with a *sticky* active key and circular rotation on rate-limit / auth / quota / transient / 5xx / empty-response errors. Disable any key or endpoint row to skip it without deleting it. Optional cross-provider fallback and a free-only filter.
|
|
18
18
|
- **Bring your own endpoint.** Deploy Kimi K3 (or Qwen / DeepSeek / GLM / GPT-OSS / your own fine-tune) to a [Modal](#modal--run-kimi-k3-on-your-own-endpoint-on-30month-of-free-credit) endpoint and drive it from clai on **$30/month of free compute credit**. Modal, Lightning AI and TokenRouter each keep a list of up to 10 base URLs with a sticky active one, so several deployments live side by side and you switch with one command — same editor, same ★ active row as keys.
|
|
19
19
|
- **Scope-based pentesting.** Opt-in engagement scope with authorized/excluded targets, allowed phases, rate and concurrency ceilings, redirect and DNS-rebinding escape detection, and out-of-scope flagging — designed for authorized pentests and bug-bounty programs.
|
|
@@ -31,17 +31,17 @@ Two things make it practical for everyday use:
|
|
|
31
31
|
```sh
|
|
32
32
|
brew tap pentoshi007/clai && brew install clai
|
|
33
33
|
# or
|
|
34
|
-
curl -fsSL https://
|
|
34
|
+
curl -fsSL https://downloads.clai.aniketpandey.website/install/install.sh | sh
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
### Linux
|
|
38
38
|
```sh
|
|
39
|
-
curl -fsSL https://
|
|
39
|
+
curl -fsSL https://downloads.clai.aniketpandey.website/install/install.sh | sh
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
### Windows
|
|
43
43
|
```powershell
|
|
44
|
-
irm https://
|
|
44
|
+
irm https://downloads.clai.aniketpandey.website/install/install.ps1 | iex
|
|
45
45
|
# or
|
|
46
46
|
scoop bucket add clai https://github.com/pentoshi007/clai && scoop install clai
|
|
47
47
|
```
|
|
@@ -93,6 +93,44 @@ The full-screen TUI uses mouse tracking and hover events via OpenTUI's Zig FFI r
|
|
|
93
93
|
|
|
94
94
|
---
|
|
95
95
|
|
|
96
|
+
## Fast downloads (Cloudflare R2 mirror)
|
|
97
|
+
|
|
98
|
+
Release binaries are mirrored to a **Cloudflare R2** bucket served through Cloudflare's global CDN — significantly faster than GitHub's release hosting in most regions, and free for this project's traffic (R2 has **zero egress fees**).
|
|
99
|
+
|
|
100
|
+
Mirror layout at `https://downloads.clai.aniketpandey.website`:
|
|
101
|
+
|
|
102
|
+
| Path | Contents |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `/vX.Y.Z/` | Binaries + `.sha256` sidecars for release vX.Y.Z |
|
|
105
|
+
| `/latest/` | Same files, refreshed on every release |
|
|
106
|
+
| `/install/` | `install.sh` / `install.ps1` used by the one-liners above |
|
|
107
|
+
| `/version.json` | Latest released version (update-check fallback) |
|
|
108
|
+
|
|
109
|
+
**Security model:** installers and `clai update` try the R2 mirror first for speed and fall back to GitHub Releases automatically. The SHA256 checksum is fetched from GitHub Releases (falling back to the mirror) and verified locally before anything is installed — the trust anchor stays with GitHub, so a bad mirror can only slow you down, never feed you tampered bytes. `CLAI_SKIP_CHECKSUM=1` still bypasses verification, at your own risk. If the mirror is unreachable but raw.githubusercontent.com works, grab the installer from `https://raw.githubusercontent.com/pentoshi007/clai/main/install/` instead — it makes the same mirror-first/fallback choices when it runs.
|
|
110
|
+
|
|
111
|
+
User knobs:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
CLAI_DOWNLOAD_BASE=https://your-mirror.example
|
|
115
|
+
CLAI_NO_MIRROR=1
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Maintainer setup (one-time, ~10 minutes)
|
|
119
|
+
|
|
120
|
+
1. **Create the bucket.** Cloudflare dashboard → R2 → Create bucket, e.g. `clai-releases`. Any location hint works.
|
|
121
|
+
2. **Make it publicly readable.** Bucket → Settings → Public access:
|
|
122
|
+
- **Option A — `r2.dev` (zero DNS config):** enable the managed "r2.dev URL" → you get `https://pub-<hash>.r2.dev`.
|
|
123
|
+
- **Option B — custom domain (the default everywhere in this repo):** add `downloads.clai.aniketpandey.website` as a custom domain (requires the zone's DNS on Cloudflare). Served over the edge cache with free SSL.
|
|
124
|
+
If you end up on a different hostname, update `DEFAULT_MIRROR_BASE` in `src/commands/update-install.ts`, the default base in `install/install.sh` and `install/install.ps1`, and the URLs in `manifests/` — or export `CLAI_DOWNLOAD_BASE` until a release ships the change.
|
|
125
|
+
3. **Create an API token.** R2 → Manage R2 API Tokens → Create API token: permission **Object Read & Write**, scoped to the `clai-releases` bucket only. Keep the **Access Key ID**, **Secret Access Key**, and the **Account ID** shown on the token page.
|
|
126
|
+
4. **Add repo secrets** (GitHub repo → Settings → Secrets and variables → Actions → Secrets): `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`.
|
|
127
|
+
5. **Add repo variable** (Variables tab): `R2_PUBLIC_URL` = your public base URL, e.g. `https://downloads.clai.aniketpandey.website`, no trailing slash.
|
|
128
|
+
6. **Done.** On every `v*.*.*` tag, the `publish-r2` job in `.github/workflows/release.yml` uploads binaries + checksums to `/vX.Y.Z/` and `/latest/`, refreshes `/install/` and `/version.json`, then re-downloads every asset through the public URL and re-verifies its SHA256 against the sidecars. Missing secrets → the job skips without failing the release; missing `R2_PUBLIC_URL` → only the public-URL verification step is skipped.
|
|
129
|
+
|
|
130
|
+
**Cost:** the R2 free tier covers 10 GB-months of storage, 1M Class A and 10M Class B operations per month, and egress is always free — assets served via the custom domain ride the edge cache, so this stays $0 at clai's scale.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
96
134
|
|
|
97
135
|
## Quick start
|
|
98
136
|
|
|
@@ -136,7 +174,7 @@ This is the core of clai's design: assemble capacity from free tiers, then survi
|
|
|
136
174
|
|
|
137
175
|
| Provider | Default model | Tier | Env var |
|
|
138
176
|
|----------|---------------|------|---------|
|
|
139
|
-
| Free (keyless) | `free-
|
|
177
|
+
| Free (keyless) | `free-2/kilo-auto/free` | Free · keyless | — (no key needed) |
|
|
140
178
|
| NVIDIA NIM | `openai/gpt-oss-20b` | Free | `NVIDIA_API_KEY` |
|
|
141
179
|
| Google Gemini | `gemini-3.5-flash` | Free | `GEMINI_API_KEY` |
|
|
142
180
|
| OpenRouter | `meta-llama/llama-3.3-70b-instruct:free` | Free | `OPENROUTER_API_KEY` |
|
|
@@ -159,7 +197,7 @@ Several "paid" providers also expose limited free allowances — the tier label
|
|
|
159
197
|
|
|
160
198
|
#### Free — keyless, zero setup
|
|
161
199
|
|
|
162
|
-
clai's default provider needs **no account, no API key, and no config**. It bundles **two keyless gateways** under one provider, namespaced by source so overlapping model names never collide: `free-1/…` ids come from the primary gateway and `free-2/…` ids from the [Kilo gateway](https://api.kilo.ai/api/gateway). Both serve rotating catalogs of free OpenAI-compatible models — `free-
|
|
200
|
+
clai's default provider needs **no account, no API key, and no config**. It bundles **two keyless gateways** under one provider, namespaced by source so overlapping model names never collide: `free-1/…` ids come from the primary gateway and `free-2/…` ids from the [Kilo gateway](https://api.kilo.ai/api/gateway). Both serve rotating catalogs of free OpenAI-compatible models — `free-2/kilo-auto/free` (default), `free-1/mimo-v2.5-free`, `free-1/hy3-free`, `free-1/x-preview-f-free`, `free-2/stepfun/step-3.7-flash:free`, `free-2/nvidia/nemotron-3-ultra-550b-a55b:free` — with SSE streaming, native tool calling, `/effort` reasoning levels, and `reasoning_content` thinking folded into the normal `/think` block.
|
|
163
201
|
|
|
164
202
|
```sh
|
|
165
203
|
clai # already on free — nothing to set up
|
|
@@ -412,7 +450,7 @@ Tool cards show the command/input clearly, with a live elapsed timer next to the
|
|
|
412
450
|
|---------|------|
|
|
413
451
|
| `/ask` · `/agent` · `/plan` | Switch mode (plan = design-then-approve) |
|
|
414
452
|
| `/implement` · `/discard` | Approve+execute or drop the current plan |
|
|
415
|
-
| `/model [name\|#]` · `/provider [name]`
|
|
453
|
+
| `/model [name\|#]` · `/provider [name]` | Pick model / switch provider |
|
|
416
454
|
| `/set [provider]` · `/unset [provider]` · `/keys` · `/info [provider]` | Manage API keys, endpoint URLs, and view provider setup / pricing info |
|
|
417
455
|
| `/effort [level]` · `/reasoning [level]` | Thinking / reasoning effort |
|
|
418
456
|
| `/freeonly [on\|off]` · `/fallback [on\|off]` | Free-only filter · cross-provider fallback |
|
package/dist/agent/runner.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { streamWithProvider } from "../llm/router.js";
|
|
3
|
+
import { isProviderFailureStatus } from "../llm/key-rotation.js";
|
|
4
|
+
import { REQUEST_CONTEXT_PREFIX, upsertRequestContextMessage, } from "../llm/system-messages.js";
|
|
3
5
|
import { operationUsageFromError } from "../llm/operation-ledger.js";
|
|
4
6
|
import { contextAttemptFromOperationUsage } from "../llm/context-snapshot.js";
|
|
5
7
|
import { modelContextWindow } from "../llm/token-usage.js";
|
|
@@ -50,7 +52,7 @@ import { accountAssembledRequest, RequestOverLimitError, } from "./request-accou
|
|
|
50
52
|
import { autoCompactTriggerTokens, dedupeToolContextOutput, freeTierGuardNotices, getReliabilityPolicy, resolveStepMaxTokens, } from "./reliability-policy.js";
|
|
51
53
|
import { auditLog } from "../store/logs.js";
|
|
52
54
|
import { loadProjectContext } from "../store/project.js";
|
|
53
|
-
import {
|
|
55
|
+
import { loadScopeForSession, isScopeActive } from "../store/scope.js";
|
|
54
56
|
import { ensureProviderConfigured } from "../commands/providers.js";
|
|
55
57
|
import { createThinkingStreamParser, rememberThinking, stripThinking, } from "../ui/thinking.js";
|
|
56
58
|
import { hasReasoningMarker } from "../llm/reasoning-marker.js";
|
|
@@ -640,7 +642,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
640
642
|
systemSections.push(pentestNoLocalServerDirective());
|
|
641
643
|
}
|
|
642
644
|
{
|
|
643
|
-
const engScope = await
|
|
645
|
+
const engScope = await loadScopeForSession(session.sessionId).catch(() => undefined);
|
|
644
646
|
const scopeBlock = scopeContextMessage(engScope);
|
|
645
647
|
if (scopeBlock && (pentestSession || pentestLikeTurn) && !idleOrSocialPrompt) {
|
|
646
648
|
systemSections.push(scopeBlock);
|
|
@@ -733,14 +735,12 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
733
735
|
if (options.images && options.images.length > 0) {
|
|
734
736
|
userMessage.images = options.images;
|
|
735
737
|
}
|
|
738
|
+
const requestContextMessage = `${REQUEST_CONTEXT_PREFIX}\n${requestContext}`;
|
|
736
739
|
const messages = [
|
|
737
740
|
{ role: "system", content: fullSystemPrompt },
|
|
738
741
|
...(options.history ?? []),
|
|
739
|
-
// Keep all per-request authority after prior history. This preserves the
|
|
740
|
-
// longest shared prefix for APC providers while single-system dialects
|
|
741
|
-
// retain it in place as a marked user turn.
|
|
742
|
-
{ role: "system", content: `REQUEST CONTEXT\n${requestContext}` },
|
|
743
742
|
userMessage,
|
|
743
|
+
{ role: "system", content: requestContextMessage },
|
|
744
744
|
];
|
|
745
745
|
liveMessages = messages;
|
|
746
746
|
if (activePlan) {
|
|
@@ -1063,6 +1063,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1063
1063
|
: undefined,
|
|
1064
1064
|
};
|
|
1065
1065
|
snap.nextHint = inferNextHint(snap);
|
|
1066
|
+
upsertRequestContextMessage(messages, requestContextMessage);
|
|
1066
1067
|
// One live plan copy, refreshed at the same protocol-safe points as
|
|
1067
1068
|
// SESSION STATE so advancing tasks are never contradicted by a stale copy.
|
|
1068
1069
|
if (p) {
|
|
@@ -1567,7 +1568,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1567
1568
|
};
|
|
1568
1569
|
}
|
|
1569
1570
|
}
|
|
1570
|
-
const scope = await
|
|
1571
|
+
const scope = await loadScopeForSession(session.sessionId);
|
|
1571
1572
|
const decision = classifyToolCall(call, { scope });
|
|
1572
1573
|
await auditLog("tool.classified", {
|
|
1573
1574
|
call,
|
|
@@ -3261,11 +3262,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3261
3262
|
}, {
|
|
3262
3263
|
onStatus: (status) => {
|
|
3263
3264
|
writeStatus(status);
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
writeNotice("warn", status.trim());
|
|
3268
|
-
}
|
|
3265
|
+
const full = status.replace(/\s+/g, " ").trim();
|
|
3266
|
+
if (isProviderFailureStatus(full))
|
|
3267
|
+
writeNotice("warn", full);
|
|
3269
3268
|
},
|
|
3270
3269
|
onStreamEvent: (event) => {
|
|
3271
3270
|
if (event.type !== "reasoning_delta")
|
|
@@ -4421,7 +4420,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4421
4420
|
.join("\n\n");
|
|
4422
4421
|
pushAssistantHistory(standardizedContent, completion);
|
|
4423
4422
|
}
|
|
4424
|
-
const scopeForBatch = await
|
|
4423
|
+
const scopeForBatch = await loadScopeForSession(session.sessionId).catch(() => undefined);
|
|
4425
4424
|
const isParallelSafe = (c) => {
|
|
4426
4425
|
if (c.name === "pentest.recon" ||
|
|
4427
4426
|
c.name === "net.context" ||
|