clawfast 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clawfast.cjs +578 -83
- package/package.json +1 -1
package/dist/clawfast.cjs
CHANGED
|
@@ -548,11 +548,11 @@ function fileDefines(filePath, key) {
|
|
|
548
548
|
return false;
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
|
-
function
|
|
551
|
+
function swapProviderKey(envVar, key) {
|
|
552
552
|
const local = import_node_path.default.resolve(process.cwd(), ".env.local");
|
|
553
|
-
const target = fileDefines(local,
|
|
554
|
-
setEnvVar(target,
|
|
555
|
-
process.env
|
|
553
|
+
const target = fileDefines(local, envVar) ? local : clawfastEnvPath();
|
|
554
|
+
setEnvVar(target, envVar, key);
|
|
555
|
+
process.env[envVar] = key;
|
|
556
556
|
return target;
|
|
557
557
|
}
|
|
558
558
|
async function testNvidiaKey(key) {
|
|
@@ -586,6 +586,27 @@ async function testNvidiaKey(key) {
|
|
|
586
586
|
};
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
|
+
async function testOpenRouterKey(key) {
|
|
590
|
+
try {
|
|
591
|
+
const res = await fetch("https://openrouter.ai/api/v1/key", {
|
|
592
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
593
|
+
});
|
|
594
|
+
if (res.ok) return { ok: true, status: res.status, detail: "OK" };
|
|
595
|
+
let detail = res.statusText || `HTTP ${res.status}`;
|
|
596
|
+
try {
|
|
597
|
+
const body = await res.json();
|
|
598
|
+
detail = body.error?.message || body.message || detail;
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
return { ok: false, status: res.status, detail };
|
|
602
|
+
} catch (err) {
|
|
603
|
+
return {
|
|
604
|
+
ok: false,
|
|
605
|
+
status: 0,
|
|
606
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|
|
589
610
|
function promptLine(question) {
|
|
590
611
|
const rl = import_node_readline.default.createInterface({
|
|
591
612
|
input: process.stdin,
|
|
@@ -634,7 +655,7 @@ ${C.dim}Para come\xE7ar, preciso de uma chave de modelo. Use a NVIDIA build (mod
|
|
|
634
655
|
);
|
|
635
656
|
return true;
|
|
636
657
|
}
|
|
637
|
-
var import_dotenv, import_node_os, import_node_path, import_node_fs, import_node_readline, clawfastHome, clawfastEnvPath, PROVIDER_KEYS, hasAnyProviderKey, C;
|
|
658
|
+
var import_dotenv, import_node_os, import_node_path, import_node_fs, import_node_readline, clawfastHome, clawfastEnvPath, PROVIDER_KEYS, hasAnyProviderKey, swapNvidiaKey, swapOpenRouterKey, C;
|
|
638
659
|
var init_config = __esm({
|
|
639
660
|
"src/config.ts"() {
|
|
640
661
|
"use strict";
|
|
@@ -645,8 +666,14 @@ var init_config = __esm({
|
|
|
645
666
|
import_node_readline = __toESM(require("node:readline"));
|
|
646
667
|
clawfastHome = () => process.env.CLAWFAST_HOME?.trim() || import_node_path.default.join(import_node_os.default.homedir(), ".clawfast");
|
|
647
668
|
clawfastEnvPath = () => import_node_path.default.join(clawfastHome(), ".env");
|
|
648
|
-
PROVIDER_KEYS = [
|
|
669
|
+
PROVIDER_KEYS = [
|
|
670
|
+
"NVIDIA_API_KEY",
|
|
671
|
+
"OPENAI_API_KEY",
|
|
672
|
+
"OPENROUTER_API_KEY"
|
|
673
|
+
];
|
|
649
674
|
hasAnyProviderKey = () => PROVIDER_KEYS.some((name25) => Boolean(process.env[name25]?.trim()));
|
|
675
|
+
swapNvidiaKey = (key) => swapProviderKey("NVIDIA_API_KEY", key);
|
|
676
|
+
swapOpenRouterKey = (key) => swapProviderKey("OPENROUTER_API_KEY", key);
|
|
650
677
|
C = {
|
|
651
678
|
reset: "\x1B[0m",
|
|
652
679
|
dim: "\x1B[90m",
|
|
@@ -662,7 +689,7 @@ var clawfastVersion, isDevVersion, isNewerVersion;
|
|
|
662
689
|
var init_version = __esm({
|
|
663
690
|
"src/version.ts"() {
|
|
664
691
|
"use strict";
|
|
665
|
-
clawfastVersion = () => true ? "2.
|
|
692
|
+
clawfastVersion = () => true ? "2.2.1" : devVersionFromPackageJson();
|
|
666
693
|
isDevVersion = () => clawfastVersion().includes("-dev");
|
|
667
694
|
isNewerVersion = (a, b) => {
|
|
668
695
|
const parse3 = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -45777,7 +45804,36 @@ function supportsMultimodalToolResults(modelName) {
|
|
|
45777
45804
|
const normalized = modelName.toLowerCase();
|
|
45778
45805
|
return normalized === "ask-model" || normalized.includes("gemini") || normalized.includes("google/") || isAnthropicModel(normalized) || normalized.includes("anthropic/") || normalized.includes("claude") || normalized.includes("openai") || normalized.includes("openai/") || normalized.includes("gpt-") || normalized.includes("o1") || normalized.includes("o3") || normalized.includes("o4") || normalized.includes("x-ai/") || normalized.includes("grok");
|
|
45779
45806
|
}
|
|
45780
|
-
|
|
45807
|
+
function resolveLanguageModel2(key) {
|
|
45808
|
+
if (isOpenRouterDynamicKey(key)) {
|
|
45809
|
+
return openrouter2(openRouterSlugFromKey(key));
|
|
45810
|
+
}
|
|
45811
|
+
return myProvider.languageModel(key);
|
|
45812
|
+
}
|
|
45813
|
+
async function listOpenRouterModels(signal) {
|
|
45814
|
+
const key = process.env.OPENROUTER_API_KEY?.trim();
|
|
45815
|
+
if (!key) throw new Error("OPENROUTER_API_KEY n\xE3o configurada");
|
|
45816
|
+
const res = await fetch("https://openrouter.ai/api/v1/models", {
|
|
45817
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
45818
|
+
signal
|
|
45819
|
+
});
|
|
45820
|
+
if (!res.ok) {
|
|
45821
|
+
throw new Error(`OpenRouter /models falhou: HTTP ${res.status}`);
|
|
45822
|
+
}
|
|
45823
|
+
const json3 = await res.json();
|
|
45824
|
+
const data = Array.isArray(json3.data) ? json3.data : [];
|
|
45825
|
+
return data.map((raw) => {
|
|
45826
|
+
if (!isRecord(raw) || typeof raw.id !== "string") return null;
|
|
45827
|
+
const pricing = isRecord(raw.pricing) ? raw.pricing : void 0;
|
|
45828
|
+
return {
|
|
45829
|
+
id: raw.id,
|
|
45830
|
+
name: typeof raw.name === "string" && raw.name.trim() ? raw.name : raw.id,
|
|
45831
|
+
contextLength: typeof raw.context_length === "number" ? raw.context_length : void 0,
|
|
45832
|
+
promptPrice: typeof pricing?.prompt === "string" ? pricing.prompt : void 0
|
|
45833
|
+
};
|
|
45834
|
+
}).filter((m) => m !== null).sort((a, b) => a.name.localeCompare(b.name));
|
|
45835
|
+
}
|
|
45836
|
+
var isRecord, isXaiModelSlug, isGeminiModelSlug, requestCanRouteToXai, requestCanRouteToGemini, hasOwnEncryptedContent, stripEncryptedContent, sanitizeOpenRouterRequestForXai, hasJsonRefKey, wrapToolContentIfGeminiRefSensitive, sanitizeOpenRouterRequestForGeminiFunctionResponses, patchKimiReasoningToolCalls, OPENROUTER_METADATA_HEADER, withOpenRouterMetadataHeader, openrouterPatchFetch, openrouter2, openai2, isNvidiaMistralModel, applyNvidiaMistralConfig, nvidiaPatchFetch, nvidia, deepseek, kimi, buildProviderMap, hasEnvValue, isDeepSeekEnabled, isKimiEnabled, CLI_MODEL_CHAIN, baseProviders, modelCutoffDates, modelDisplayNames, getModelDisplayName, getModelCutoffDate, myProvider, OPENROUTER_KEY_PREFIX, hasOpenRouterKey, isOpenRouterDynamicKey, openRouterSlugFromKey, openRouterKeyForSlug;
|
|
45781
45837
|
var init_providers = __esm({
|
|
45782
45838
|
"../lib/ai/providers.ts"() {
|
|
45783
45839
|
"use strict";
|
|
@@ -46119,6 +46175,11 @@ var init_providers = __esm({
|
|
|
46119
46175
|
myProvider = customProvider({
|
|
46120
46176
|
languageModels: baseProviders
|
|
46121
46177
|
});
|
|
46178
|
+
OPENROUTER_KEY_PREFIX = "openrouter:";
|
|
46179
|
+
hasOpenRouterKey = () => Boolean(process.env.OPENROUTER_API_KEY?.trim());
|
|
46180
|
+
isOpenRouterDynamicKey = (key) => key.startsWith(OPENROUTER_KEY_PREFIX);
|
|
46181
|
+
openRouterSlugFromKey = (key) => key.slice(OPENROUTER_KEY_PREFIX.length);
|
|
46182
|
+
openRouterKeyForSlug = (slug) => `${OPENROUTER_KEY_PREFIX}${slug}`;
|
|
46122
46183
|
}
|
|
46123
46184
|
});
|
|
46124
46185
|
|
|
@@ -70342,7 +70403,9 @@ var init_local_sandbox = __esm({
|
|
|
70342
70403
|
this.shellBin = shell2.shell;
|
|
70343
70404
|
this.shellFlag = shell2.shellFlag;
|
|
70344
70405
|
}
|
|
70345
|
-
const
|
|
70406
|
+
const explicit = opts?.workdir || process.env.CLI_WORKDIR;
|
|
70407
|
+
const base = explicit || import_node_path7.default.join(process.cwd(), "SPRIT");
|
|
70408
|
+
this.autoCreated = !explicit;
|
|
70346
70409
|
this.workdir = import_node_path7.default.resolve(base).replace(/\\/g, "/");
|
|
70347
70410
|
}
|
|
70348
70411
|
async init() {
|
|
@@ -70372,6 +70435,9 @@ var init_local_sandbox = __esm({
|
|
|
70372
70435
|
["recon_ports.py", true],
|
|
70373
70436
|
["recon_content.py", true],
|
|
70374
70437
|
["recon_intel.py", true],
|
|
70438
|
+
["recon_routes.py", true],
|
|
70439
|
+
["exploit_engine.py", true],
|
|
70440
|
+
["harden.py", true],
|
|
70375
70441
|
["console_recon.js", true],
|
|
70376
70442
|
["README.md", true],
|
|
70377
70443
|
["scope.txt", false]
|
|
@@ -70496,6 +70562,31 @@ EDITING SCRIPTS:
|
|
|
70496
70562
|
}
|
|
70497
70563
|
this.backgroundPids.clear();
|
|
70498
70564
|
}
|
|
70565
|
+
/**
|
|
70566
|
+
* Remove the auto-created SPRIT workspace on session exit so an analysis never
|
|
70567
|
+
* leaves scratch tooling behind in the user's project. The human-facing
|
|
70568
|
+
* deliverable (ANALISE_PROJETO_*.md / findings report) is written at the
|
|
70569
|
+
* PROJECT ROOT, OUTSIDE SPRIT — so nothing the user needs is lost.
|
|
70570
|
+
*
|
|
70571
|
+
* Strictly guarded — deletes ONLY when all hold:
|
|
70572
|
+
* - the workspace was auto-created (default <cwd>/SPRIT, no explicit override)
|
|
70573
|
+
* - its basename is exactly "SPRIT" (never a project root or arbitrary dir)
|
|
70574
|
+
* - the operator has not opted out via CLAWFAST_KEEP_SPRIT
|
|
70575
|
+
* Best-effort: any failure is swallowed; cleanup must never block shutdown.
|
|
70576
|
+
* Returns the removed path (for logging) or null when nothing was removed.
|
|
70577
|
+
*/
|
|
70578
|
+
async cleanupWorkspace() {
|
|
70579
|
+
if (!this.autoCreated) return null;
|
|
70580
|
+
const keep = (process.env.CLAWFAST_KEEP_SPRIT || "").trim().toLowerCase();
|
|
70581
|
+
if (keep === "1" || keep === "true" || keep === "yes") return null;
|
|
70582
|
+
if (import_node_path7.default.basename(this.workdir).toUpperCase() !== "SPRIT") return null;
|
|
70583
|
+
try {
|
|
70584
|
+
await import_node_fs8.promises.rm(this.workdir, { recursive: true, force: true });
|
|
70585
|
+
return this.workdir;
|
|
70586
|
+
} catch {
|
|
70587
|
+
return null;
|
|
70588
|
+
}
|
|
70589
|
+
}
|
|
70499
70590
|
/** True while a foreground command is running and can still accept stdin. */
|
|
70500
70591
|
isProcessRunning() {
|
|
70501
70592
|
const child = this.activeForegroundChild;
|
|
@@ -71090,10 +71181,16 @@ function detectAuditExitIntent(input) {
|
|
|
71090
71181
|
if (!text2) return false;
|
|
71091
71182
|
return AUDIT_EXIT.some((re2) => re2.test(text2));
|
|
71092
71183
|
}
|
|
71184
|
+
function detectPocVerifyIntent(input) {
|
|
71185
|
+
const text2 = input.trim();
|
|
71186
|
+
if (!text2) return false;
|
|
71187
|
+
return POC_VERIFY.some((re2) => re2.test(text2));
|
|
71188
|
+
}
|
|
71093
71189
|
function buildAuditSystemPrompt(opts) {
|
|
71094
71190
|
const root = opts.projectRoot.replace(/\\/g, "/");
|
|
71095
71191
|
const workdir = opts.workdir.replace(/\\/g, "/");
|
|
71096
71192
|
const isWin = (opts.platform ?? process.platform) === "win32";
|
|
71193
|
+
const verify = opts.mode === "verify";
|
|
71097
71194
|
const py = isWin ? "python" : "python3";
|
|
71098
71195
|
const shellNote = isWin ? "Voc\xEA est\xE1 em Windows. Use `python` (n\xE3o `python3`), aspas em caminhos com espa\xE7os, e `/` ou `\\` indiferente nos caminhos." : "Use `python3` quando `python` n\xE3o existir.";
|
|
71099
71196
|
const shellHint = opts.shellHint ?? shellNote;
|
|
@@ -71103,7 +71200,11 @@ function buildAuditSystemPrompt(opts) {
|
|
|
71103
71200
|
FILOSOFIA (estado da arte \u2014 h\xEDbrido, n\xE3o LLM puro):
|
|
71104
71201
|
Voc\xEA N\xC3O \xE9 um LLM que l\xEA o projeto inteiro linha a linha. Voc\xEA \xE9 um orquestrador que roda FERRAMENTAS DETERMIN\xCDSTICAS para o trabalho pesado (mapeamento, SAST, depend\xEAncias, segredos) e usa o RACIOC\xCDNIO DO MODELO s\xF3 para o que ferramenta gr\xE1tis nenhuma faz bem: **taint cross-module** (seguir o dado que cruza fronteira de fun\xE7\xE3o/arquivo) e **valida\xE7\xE3o** de cada achado lendo o c\xF3digo real. LLM puro infla recall mas explode falso-positivo; ferramenta pura perde sem\xE2ntica inter-procedural; o h\xEDbrido ganha. O Semgrep gr\xE1tis s\xF3 faz taint INTRA-procedural (dentro de uma fun\xE7\xE3o) \u2014 a propaga\xE7\xE3o que atravessa imports \xE9 exatamente o buraco que o seu racioc\xEDnio preenche.
|
|
71105
71202
|
|
|
71106
|
-
Miss\xE3o desta sess\xE3o: an\xE1lise extremamente precisa e profunda do projeto inteiro e entrega de **um relat\xF3rio** acion\xE1vel e honesto. Ferramentas do agente: \`run_terminal_cmd\`, \`file
|
|
71203
|
+
Miss\xE3o desta sess\xE3o: an\xE1lise extremamente precisa e profunda do projeto inteiro e entrega de **um relat\xF3rio** acion\xE1vel e honesto, com BUSCA cir\xFArgica (ferramentas estruturais sobre o c\xF3digo real) e PODER de verifica\xE7\xE3o (cada achado provado, n\xE3o suposto). Ferramentas do agente: \`run_terminal_cmd\`, \`file\` (com leitura por \`range\` \u2014 leia S\xD3 a janela exata da fun\xE7\xE3o, nunca o arquivo inteiro), \`findings\` (mem\xF3ria estruturada de vulnerabilidades \u2014 a fonte \xFAnica de verdade dos achados) e \`todo_write\`.
|
|
71204
|
+
|
|
71205
|
+
${verify ? `>>> SUB-MODO ATIVO: **VERIFICA\xC7\xC3O / PoC** <<<
|
|
71206
|
+
Voc\xEA foi acionado para PROVAR achados rodando-os de verdade (reproduzir a vulnerabilidade), n\xE3o s\xF3 lendo. Vale a regra relaxada da se\xE7\xE3o "SUB-MODO VERIFICA\xC7\xC3O" abaixo: voc\xEA PODE executar o c\xF3digo do projeto de um harness ISOLADO na workspace \u2014 mas continua SEM editar/criar/apagar nada dentro do projeto. Objetivo: virar cada \`[SUSPEITA]\` em \`[CONFIRMADO]\` com PoC reproduz\xEDvel e n\xE3o-destrutivo.` : `>>> MODO ATIVO: **AN\xC1LISE (somente leitura)** <<<
|
|
71207
|
+
Voc\xEA N\xC3O roda o projeto. Para PROVAR um achado executando um PoC, o operador precisa pedir explicitamente (ex.: "prove rodando", "rode o PoC") \u2014 a\xED entra o sub-modo de verifica\xE7\xE3o.`}
|
|
71107
71208
|
|
|
71108
71209
|
PROJETO SOB AN\xC1LISE (raiz): ${root}
|
|
71109
71210
|
WORKSPACE DE TRABALHO (ferramentas/sa\xEDdas intermedi\xE1rias): ${workdir}
|
|
@@ -71118,6 +71219,16 @@ Um relat\xF3rio de seguran\xE7a inventado \xE9 PIOR que nenhum \u2014 \xE9 perig
|
|
|
71118
71219
|
3. TODO caminho de arquivo no relat\xF3rio tem que ser um arquivo que voc\xEA CONFIRMOU existir neste projeto (apareceu no grafo/digest ou voc\xEA o leu). Nunca cite caminhos gen\xE9ricos de mem\xF3ria (ex.: \`src/services/userService.ts\`, \`lodash@x\`) sem t\xEA-los visto na sa\xEDda real.
|
|
71119
71220
|
4. Se voc\xEA ainda n\xE3o tem dados reais (digest vazio, ferramentas n\xE3o rodaram), N\xC3O escreva relat\xF3rio: diga exatamente o que falta e rode o passo que falta. Parar \xE9 melhor que inventar.
|
|
71120
71221
|
|
|
71222
|
+
================================================================
|
|
71223
|
+
BUSCA & LEITURA DE PRECIS\xC3O \u2014 seja cir\xFArgico, n\xE3o force bruta
|
|
71224
|
+
================================================================
|
|
71225
|
+
Sua pot\xEAncia vem de localizar com exatid\xE3o e ler s\xF3 o necess\xE1rio \u2014 n\xE3o de despejar arquivos no contexto. Escada de precis\xE3o, do mais barato/preciso ao mais caro:
|
|
71226
|
+
1. LOCALIZE primeiro, nunca leia \xE0s cegas: use o grafo (\`--rank\`/\`--focus\`/madge), o \xEDndice de s\xEDmbolos (ctags/tags.json) e a busca para achar o file:line exato.
|
|
71227
|
+
2. BUSCA ESTRUTURAL > textual: prefira \`ast-grep\` (casa por AST: sink real, n\xE3o coment\xE1rio/string) ; caia para \`rg -n\` (regex com n\xFAmero de linha) s\xF3 quando estrutural n\xE3o couber. Sempre com \`-n\` para ter a linha.
|
|
71228
|
+
3. LEITURA CIR\xDARGICA: depois de ter file:line, abra S\xD3 a janela da fun\xE7\xE3o com \`file\` action: read e \`range: [in\xEDcio, fim]\` (linhas 1-indexed). NUNCA leia um arquivo grande inteiro \u2014 isso estoura o contexto e dilui o sinal. Se precisar de mais contexto, expanda a janela em passos, n\xE3o o arquivo todo.
|
|
71229
|
+
4. SIGA O DADO, n\xE3o o arquivo: no taint (FASE 5), pule de defini\xE7\xE3o em defini\xE7\xE3o pelo grafo/tags lendo s\xF3 a janela de cada fun\xE7\xE3o no rastro source\u2192sink. \xC9 "abrir, seguir o import, conferir e voltar" \u2014 em janelas, n\xE3o em arquivos inteiros.
|
|
71230
|
+
5. Em projeto GRANDE/GIGANTE isto \xE9 OBRIGAT\xD3RIO: rode as buscas escopadas por diret\xF3rio dos top-ranqueados (FASE 0.5), n\xE3o sobre a raiz inteira.
|
|
71231
|
+
|
|
71121
71232
|
================================================================
|
|
71122
71233
|
AMBIENTE & SHELL \u2014 leia antes de rodar comandos
|
|
71123
71234
|
================================================================
|
|
@@ -71132,14 +71243,24 @@ AMBIENTE & SHELL \u2014 leia antes de rodar comandos
|
|
|
71132
71243
|
REGRA INVIOL\xC1VEL \u2014 SOMENTE LEITURA NO PROJETO
|
|
71133
71244
|
================================================================
|
|
71134
71245
|
1. Voc\xEA **NUNCA** modifica, edita, cria, renomeia, move ou apaga QUALQUER arquivo dentro do projeto (${root}) \u2014 nem para "corrigir", nem para testar.
|
|
71135
|
-
2. Voc\xEA **N\xC3O** roda comandos que alterem o projeto: nada de \`git\` que escreva (commit/checkout/reset/clean), build, formatadores, geradores, migrations, ou qualquer script do projeto, e nada de \`npm/pnpm/yarn install\` DENTRO do projeto (isso escreve node_modules/lockfile na pasta dele). Apenas LEIA o projeto.
|
|
71246
|
+
2. ${verify ? `EXECU\xC7\xC3O CONTROLADA (sub-modo verifica\xE7\xE3o ATIVO): voc\xEA PODE executar c\xF3digo SOMENTE para reproduzir um achado \u2014 subir uma inst\xE2ncia local ef\xEAmera do projeto, importar um m\xF3dulo dele a partir de um harness criado na workspace, ou chamar a fun\xE7\xE3o suspeita com input malicioso. SEMPRE de forma N\xC3O-DESTRUTIVA (ver doutrina de PoC abaixo) e SEM escrever NADA dentro do projeto: harness, logs e PoCs ficam em ${workdir}. CONTINUA PROIBIDO: \`git\` que escreve, migrations contra um banco real do usu\xE1rio, formatadores/geradores que reescrevem arquivos do projeto, e \`npm/pnpm/yarn install\` DENTRO do projeto (instale depend\xEAncias do harness na workspace).` : `Voc\xEA **N\xC3O** roda comandos que alterem o projeto NEM que executem o c\xF3digo dele: nada de \`git\` que escreva (commit/checkout/reset/clean), build, formatadores, geradores, migrations, rodar a app ou qualquer script do projeto, e nada de \`npm/pnpm/yarn install\` DENTRO do projeto (isso escreve node_modules/lockfile na pasta dele). Apenas LEIA o projeto. (Para PROVAR um achado rodando um PoC, o operador deve pedir explicitamente \u2014 isso liga o sub-modo de verifica\xE7\xE3o.)`}
|
|
71136
71247
|
EXCE\xC7\xC3O permitida: instalar as FERRAMENTAS DE AUDITORIA de forma GLOBAL/externa \u2014 \`npm install -g @ast-grep/cli madge\`, \`python -m pip install semgrep\`, \`scoop/winget/brew/go install ...\` \u2014 porque isso n\xE3o escreve nada dentro de ${root}. Rode esses instaladores a partir da workspace, nunca \`cd\` para dentro do projeto para instalar.
|
|
71137
71248
|
3. A **\xDANICA** escrita permitida no projeto \xE9 **um** arquivo de relat\xF3rio na RAIZ do projeto:
|
|
71138
71249
|
\`${root}/ANALISE_PROJETO_<timestamp>.md\`
|
|
71139
71250
|
Esse \xE9 o \xFAnico \`file\` action: write fora da workspace. Todo o resto (scripts auxiliares, dados intermedi\xE1rios, relat\xF3rios t\xE9cnicos) fica em ${workdir}.
|
|
71140
71251
|
4. Se o usu\xE1rio pedir para voc\xEA corrigir/alterar algo, **n\xE3o altere** \u2014 explique no relat\xF3rio como corrigir e diga que para aplicar mudan\xE7as ele deve sair do modo de an\xE1lise (ex.: "pode corrigir" / "modo normal").
|
|
71141
71252
|
5. Se precisar criar um script auxiliar (Python), crie-o em ${workdir} com a ferramenta \`file\`, nunca via redirecionamento de shell, e nunca dentro do projeto.
|
|
71142
|
-
|
|
71253
|
+
${verify ? `
|
|
71254
|
+
================================================================
|
|
71255
|
+
SUB-MODO VERIFICA\xC7\xC3O \u2014 DOUTRINA DE PoC (provar, n\xE3o supor)
|
|
71256
|
+
================================================================
|
|
71257
|
+
Aqui est\xE1 o PODER REAL: transformar suspeita em CONFIRMADO reproduzindo a falha \u2014 o que separa um SAST de uma prova. Disciplina obrigat\xF3ria:
|
|
71258
|
+
1. ALVO: pegue cada finding em \`suspected\` com caminho source\u2192sink plaus\xEDvel (FASE 5). Para cada um, monte o MENOR PoC que dispara o sink com input controlado.
|
|
71259
|
+
2. ISOLAMENTO: o harness (script/teste que importa o m\xF3dulo do projeto ou bate na app local) \xE9 criado em ${workdir} via \`file\`. Suba a app numa porta ef\xEAmera/local; se precisar de banco, use um SQLite/cont\xEAiner DESCART\xC1VEL na workspace \u2014 NUNCA o banco real do usu\xE1rio. Nada toca o projeto.
|
|
71260
|
+
3. N\xC3O-DESTRUTIVO (igual ao exploit_engine): prove o impacto sem abusar \u2014 nada de DROP/DELETE/UPDATE empilhado, nada de exfiltra\xE7\xE3o em massa, nada de DoS/flood. Para SQLi use boolean/time-based ou um erro; para LFI leia um arquivo-marcador in\xF3cuo; para cmdi um \`sleep\`/echo determin\xEDstico; para XSS um marcador refletido sem escape. Capture a EVID\xCANCIA concreta (o delta de tempo, o marcador refletido, a linha lida, o erro do banco).
|
|
71261
|
+
4. VEREDITO: disparou de forma reproduz\xEDvel \u2192 \`findings\` update \`status: "confirmed"\` colando o request/entrada exatos + a prova observada + o caminho do PoC em ${workdir}. N\xE3o reproduziu (sanitizado/inalcan\xE7\xE1vel) \u2192 \`status: "false_positive"\` com o porqu\xEA. Sem meio-termo inventado.
|
|
71262
|
+
5. LIMITE: o sub-modo verifica o PROJETO DO PR\xD3PRIO USU\xC1RIO, localmente. N\xE3o vire scanner ofensivo contra hosts externos \u2014 isso \xE9 o fluxo de recon/exploit_engine, fora daqui.
|
|
71263
|
+
` : ""}
|
|
71143
71264
|
================================================================
|
|
71144
71265
|
TOOLCHAIN \u2014 cada ferramenta vira uma "tool" no seu loop
|
|
71145
71266
|
================================================================
|
|
@@ -71160,6 +71281,17 @@ N\xC3O FA\xC7A (cada um destes quebrou em runs reais):
|
|
|
71160
71281
|
\u2022 N\xC3O repita uma instala\xE7\xE3o que falhou. 1 tentativa, falhou, fallback.
|
|
71161
71282
|
NUNCA invente que rodou uma ferramenta que n\xE3o existe \u2014 diga que caiu no fallback.
|
|
71162
71283
|
|
|
71284
|
+
FASE 0.5 \u2014 TRIAGEM DE PORTE (obrigat\xF3ria, ANTES de varrer nada):
|
|
71285
|
+
Projeto grande analisado \xE0s cegas vira relat\xF3rio raso. Me\xE7a primeiro e ATAQUE A SUPERF\xCDCIE CERTA:
|
|
71286
|
+
1. \`${py} ${toolkit} --rank "${root}"\` \u2014 imprime, num comando, o PORTE (PEQUENO/M\xC9DIO/GRANDE/GIGANTE), a ESTRAT\xC9GIA recomendada, os m\xF3dulos de maior FAN-IN (mais importados = maior raio de impacto), os PONTOS DE ENTRADA (rotas/controllers/handlers/auth) e os maiores arquivos. Leia a sa\xEDda \u2014 ela define seu plano.
|
|
71287
|
+
2. Adote a estrat\xE9gia do porte:
|
|
71288
|
+
\u2022 PEQUENO \u2192 exaustivo: varra tudo e siga todo taint.
|
|
71289
|
+
\u2022 M\xC9DIO \u2192 baseline ampla, mas siga taint priorizando entry points + top fan-in.
|
|
71290
|
+
\u2022 GRANDE \u2192 TRIAGEM DIRIGIDA: rode as ferramentas das FASES 2-4 em LOTES por diret\xF3rio/pacote (uma chamada por subpasta grande), nunca o projeto inteiro de uma vez; siga taint s\xF3 nos ~30 maiores fan-in + entry points.
|
|
71291
|
+
\u2022 GIGANTE \u2192 TRIAGEM AGRESSIVA: jamais um comando sobre o todo. Lote por top-level dir; priorize ESTRITAMENTE top fan-in + entry points + sinks de alto impacto. Declare no relat\xF3rio o que ficou fora desta passada (honestidade de cobertura).
|
|
71292
|
+
3. Registre o plano com \`todo_write\` (os arquivos/\xE1reas priorizados viram itens). Esse \xE9 o mapa da ca\xE7a.
|
|
71293
|
+
Regra de ouro de porte: a precis\xE3o vem de RANQUEAR e ir fundo no que importa \u2014 n\xE3o de varrer tudo raso. Em GRANDE/GIGANTE, profundidade nos top-ranqueados > largura no resto.
|
|
71294
|
+
|
|
71163
71295
|
================================================================
|
|
71164
71296
|
PIPELINE \u2014 fase por fase (ReAct: cada comando \xE9 uma tool)
|
|
71165
71297
|
================================================================
|
|
@@ -71205,14 +71337,19 @@ Voc\xEA redirecionou as sa\xEDdas pesadas para arquivos em audit-out/ \u2014 ent
|
|
|
71205
71337
|
\`${py} ${toolkit} --consolidate "${workdir}/audit-out"\`
|
|
71206
71338
|
Esse digest l\xEA semgrep.json, npm_audit.json, deps.json, os sinks/sources do ast-grep e o grafo do fallback, e imprime um resumo compacto com file:line REAIS. \xC9 a sua \xDANICA fonte de verdade para os achados das ferramentas. Se o digest vier vazio, as ferramentas n\xE3o produziram sa\xEDda \u2014 rode as fases 2-4 de novo ou use o fallback; N\xC3O invente.
|
|
71207
71339
|
Depois: deduplique e \u2014 REGRA DE OURO \u2014 **descarte todo achado que N\xC3O tem caminho source\u2192sink confirmado lendo o c\xF3digo real** (FASE 5). Sem isso o relat\xF3rio vira ru\xEDdo.
|
|
71208
|
-
|
|
71340
|
+
|
|
71341
|
+
FONTE \xDANICA DE VERDADE \u2014 a ferramenta \`findings\` (n\xE3o use notas soltas):
|
|
71342
|
+
- Assim que um candidato aparece (FASE 3-4 / digest), grave-o: \`findings\` action add com \`title\`, \`severity\`, \`status: "suspected"\`, e o racioc\xEDnio em \`evidence\` (inclua o file:line do sink).
|
|
71343
|
+
- Ao validar lendo o c\xF3digo (FASE 5): \`findings\` action update \u2192 \`status: "confirmed"\`, colando no \`evidence\` o CAMINHO source\u2192sink real (source \u2192 fun\xE7\xF5es/arquivos intermedi\xE1rios \u2192 sink) com os trechos lidos. Se n\xE3o reproduzir/sanitizou no caminho \u2192 \`status: "false_positive"\`.
|
|
71344
|
+
- O SCRATCHPAD de navega\xE7\xE3o (sources em aberto, taint a fechar) fica no \`todo_write\`; os ACHADOS ficam no \`findings\`. Itere at\xE9 fechar cada rastro.
|
|
71345
|
+
- O store persiste em \`${workdir}/findings/findings.json\` \u2014 \xE9 dele que sai o relat\xF3rio, ent\xE3o mant\xEA-lo fiel \xE9 o que torna o relat\xF3rio confi\xE1vel (precis\xE3o > volume).
|
|
71209
71346
|
|
|
71210
71347
|
Cobertura da ca\xE7a (confirme cada um lendo o c\xF3digo): inje\xE7\xE3o (SQL/comando/c\xF3digo/eval), XSS (sinks DOM/HTML), authn/authz fr\xE1gil, segredos/chaves embutidas (mascare no relat\xF3rio), TLS desabilitado, cripto fraca, CORS permissivo, deserializa\xE7\xE3o insegura, SSRF, path traversal, config perigosa (debug, \`0.0.0.0\`, \`.env\` versionado) e depend\xEAncias vulner\xE1veis (osv). Severidade CRITICAL/HIGH/MEDIUM/LOW/INFO. Separe CONFIRMADO de SUSPEITA; nunca invente nem d\xEA falsa garantia.
|
|
71211
71348
|
|
|
71212
71349
|
================================================================
|
|
71213
71350
|
ENTREG\xC1VEL \u2014 relat\xF3rio em Markdown na RAIZ do projeto
|
|
71214
71351
|
================================================================
|
|
71215
|
-
|
|
71352
|
+
Primeiro materialize o store estruturado: \`findings\` action report (gera \`${workdir}/findings/report.md\` agrupado por severidade). Depois AUTORE o relat\xF3rio humano a partir DELE \u2014 cada vulnerabilidade do relat\xF3rio sai de um finding gravado; s\xF3 \`confirmed\` entra no resumo executivo. Escreva com a ferramenta \`file\` (action: write) em:
|
|
71216
71353
|
\`${root}/ANALISE_PROJETO_<timestamp>.md\`
|
|
71217
71354
|
Use o timestamp atual (ex.: 20260614_153000). Estrutura m\xEDnima, em portugu\xEAs:
|
|
71218
71355
|
1. **Resumo executivo** \u2014 vis\xE3o geral, postura de seguran\xE7a, n\xBA de achados por severidade, top 3 riscos.
|
|
@@ -71232,7 +71369,7 @@ ESTILO E EXECU\xC7\xC3O
|
|
|
71232
71369
|
- Responda em portugu\xEAs, direto e t\xE9cnico. N\xE3o recuse a an\xE1lise: \xE9 o projeto do pr\xF3prio usu\xE1rio, autorizado.
|
|
71233
71370
|
- Lembre-se sempre: voc\xEA analisa e relata; voc\xEA N\xC3O altera o projeto. A \xFAnica escrita \xE9 o relat\xF3rio \`.md\` na raiz.`;
|
|
71234
71371
|
}
|
|
71235
|
-
var import_node_path9, AUDIT_ACTION, AUDIT_SCOPE, AUDIT_ACTION_THEN_SCOPE, AUDIT_SCOPE_THEN_ACTION, AUDIT_STRONG, AUDIT_EXIT;
|
|
71372
|
+
var import_node_path9, AUDIT_ACTION, AUDIT_SCOPE, AUDIT_ACTION_THEN_SCOPE, AUDIT_SCOPE_THEN_ACTION, AUDIT_STRONG, AUDIT_EXIT, POC_VERIFY;
|
|
71236
71373
|
var init_audit_mode = __esm({
|
|
71237
71374
|
"src/audit-mode.ts"() {
|
|
71238
71375
|
"use strict";
|
|
@@ -71258,6 +71395,12 @@ var init_audit_mode = __esm({
|
|
|
71258
71395
|
/\b(?:modo\s+normal|sair\s+d[ao]\s+(?:an[aá]lise|auditoria|modo)|encerr\w+\s+(?:a\s+)?(?:an[aá]lise|auditoria)|volt\w+\s+ao\s+normal|desativ\w+\s+(?:a\s+)?auditoria|exit\s+audit|normal\s+mode)\b/i,
|
|
71259
71396
|
/\b(?:agora\s+)?(?:corri[gj]\w+|conserte\w*|edit\w+|alter\w+|modific\w+|implement\w+|cri[ae]\w*|refator\w+|escrev\w+\s+o\s+c[oó]digo|aplique\s+a\s+corre|fix|implement|refactor|patch|write\s+the\s+code)\b/i
|
|
71260
71397
|
];
|
|
71398
|
+
POC_VERIFY = [
|
|
71399
|
+
/\b(?:prov[ae]\w*|comprov\w+|confirm\w+\s+(?:rodando|de\s+verdade|na\s+pr[aá]tica|executando)|reproduz\w+|reproduc\w+)\b/i,
|
|
71400
|
+
/\b(?:poc|p\.o\.c\.|prova\s+de\s+conceito|proof\s+of\s+concept|exploit\w*)\b/i,
|
|
71401
|
+
/\b(?:rod[ae]\w*|execut\w+|dispar\w+)\b[\s\S]{0,30}\b(?:exploit|poc|vulnerabilidad\w*|ataque|payload)\b/i,
|
|
71402
|
+
/\b(?:prove\s+it|verify\s+(?:by\s+)?running|run\s+the\s+(?:poc|exploit))\b/i
|
|
71403
|
+
];
|
|
71261
71404
|
}
|
|
71262
71405
|
});
|
|
71263
71406
|
|
|
@@ -71279,8 +71422,8 @@ function looksUnfinished(text2) {
|
|
|
71279
71422
|
if (DANGLING_TAIL_RE.test(tail)) return true;
|
|
71280
71423
|
return ACTION_ANNOUNCE_RE.test(lastSentenceOf(tail));
|
|
71281
71424
|
}
|
|
71282
|
-
function decideAutoContinue(finishReason, lastStepText, turnToolCalls, autoContinues) {
|
|
71283
|
-
if (autoContinues >=
|
|
71425
|
+
function decideAutoContinue(finishReason, lastStepText, turnToolCalls, autoContinues, maxAutoContinues = MAX_AUTO_CONTINUES) {
|
|
71426
|
+
if (autoContinues >= maxAutoContinues) return null;
|
|
71284
71427
|
if (finishReason === "length") {
|
|
71285
71428
|
return { nudge: true, reason: "resposta truncada (length)" };
|
|
71286
71429
|
}
|
|
@@ -71506,6 +71649,7 @@ async function createAgent() {
|
|
|
71506
71649
|
system += pythonOnlyPolicy();
|
|
71507
71650
|
system += deepReconPolicy(sandbox.getWorkdir());
|
|
71508
71651
|
system += reconPhasesPolicy(sandbox.getWorkdir());
|
|
71652
|
+
system += attackChainPolicy(sandbox.getWorkdir());
|
|
71509
71653
|
system += httpAndFindingsPolicy(sandbox.getWorkdir());
|
|
71510
71654
|
system += buildCliNotesSection();
|
|
71511
71655
|
system += buildSkillsIndexSection();
|
|
@@ -71528,6 +71672,7 @@ async function createAgent() {
|
|
|
71528
71672
|
}
|
|
71529
71673
|
const history = [];
|
|
71530
71674
|
let auditMode = false;
|
|
71675
|
+
let pocVerify = false;
|
|
71531
71676
|
const projectRoot = projectRootFromWorkdir(sandbox.getWorkdir());
|
|
71532
71677
|
function getActiveModelChain() {
|
|
71533
71678
|
return selectedModelKey ? [selectedModelKey] : CLI_MODEL_CHAIN;
|
|
@@ -71603,6 +71748,33 @@ async function createAgent() {
|
|
|
71603
71748
|
selection: getModelSelection()
|
|
71604
71749
|
};
|
|
71605
71750
|
}
|
|
71751
|
+
if (isOpenRouterDynamicKey(raw)) {
|
|
71752
|
+
if (!hasOpenRouterKey()) {
|
|
71753
|
+
return {
|
|
71754
|
+
ok: false,
|
|
71755
|
+
message: "OpenRouter indispon\xEDvel: defina OPENROUTER_API_KEY em .env.local (https://openrouter.ai/keys)",
|
|
71756
|
+
selection: getModelSelection()
|
|
71757
|
+
};
|
|
71758
|
+
}
|
|
71759
|
+
const slug = openRouterSlugFromKey(raw).trim();
|
|
71760
|
+
if (!slug) {
|
|
71761
|
+
return {
|
|
71762
|
+
ok: false,
|
|
71763
|
+
message: "informe o modelo OpenRouter (ex.: openrouter:openai/gpt-4o)",
|
|
71764
|
+
selection: getModelSelection()
|
|
71765
|
+
};
|
|
71766
|
+
}
|
|
71767
|
+
const key = openRouterKeyForSlug(slug);
|
|
71768
|
+
selectedModelKey = key;
|
|
71769
|
+
currentModelName = key;
|
|
71770
|
+
context2.modelName = key;
|
|
71771
|
+
await rebuildSystemPrompt(key);
|
|
71772
|
+
return {
|
|
71773
|
+
ok: true,
|
|
71774
|
+
message: `modelo fixado: ${labelFor(key)}`,
|
|
71775
|
+
selection: getModelSelection()
|
|
71776
|
+
};
|
|
71777
|
+
}
|
|
71606
71778
|
const choices = getModelChoices();
|
|
71607
71779
|
let match;
|
|
71608
71780
|
if (/^\d+$/.test(normalized)) {
|
|
@@ -71712,6 +71884,28 @@ ${seen}`);
|
|
|
71712
71884
|
}
|
|
71713
71885
|
return sections.join("\n\n");
|
|
71714
71886
|
}
|
|
71887
|
+
let lastKimiChatId = null;
|
|
71888
|
+
async function reportKimiSession() {
|
|
71889
|
+
const base = process.env.KIMI_BASE_URL?.trim();
|
|
71890
|
+
if (!base) return;
|
|
71891
|
+
try {
|
|
71892
|
+
const url2 = `${base.replace(/\/+$/, "")}/session`;
|
|
71893
|
+
const controller = new AbortController();
|
|
71894
|
+
const timer2 = setTimeout(() => controller.abort(), 1500);
|
|
71895
|
+
const res = await fetch(url2, { signal: controller.signal }).finally(
|
|
71896
|
+
() => clearTimeout(timer2)
|
|
71897
|
+
);
|
|
71898
|
+
if (!res.ok) return;
|
|
71899
|
+
const info = await res.json();
|
|
71900
|
+
if (!info?.active || !info.chatId) return;
|
|
71901
|
+
const isNew = info.chatId !== lastKimiChatId;
|
|
71902
|
+
lastKimiChatId = info.chatId;
|
|
71903
|
+
render.info(
|
|
71904
|
+
`\u25B8 kimi: ${isNew ? "conversa \xFAnica ativa" : "mesma conversa"} (turno ${info.turns ?? "?"}) \u2014 ${info.url ?? info.chatId}`
|
|
71905
|
+
);
|
|
71906
|
+
} catch {
|
|
71907
|
+
}
|
|
71908
|
+
}
|
|
71715
71909
|
async function send(userInput, signal) {
|
|
71716
71910
|
history.push({ role: "user", content: userInput });
|
|
71717
71911
|
if (!auditMode && detectProjectAuditIntent(userInput)) {
|
|
@@ -71721,14 +71915,22 @@ ${seen}`);
|
|
|
71721
71915
|
);
|
|
71722
71916
|
} else if (auditMode && detectAuditExitIntent(userInput)) {
|
|
71723
71917
|
auditMode = false;
|
|
71918
|
+
pocVerify = false;
|
|
71724
71919
|
render.info("\u25B8 modo normal reativado.");
|
|
71725
71920
|
}
|
|
71921
|
+
if (auditMode && !pocVerify && detectPocVerifyIntent(userInput)) {
|
|
71922
|
+
pocVerify = true;
|
|
71923
|
+
render.info(
|
|
71924
|
+
"\u25B8 sub-modo VERIFICA\xC7\xC3O/PoC ligado \u2014 pode executar c\xF3digo de um harness ISOLADO na SPRIT para provar achados (n\xE3o-destrutivo; projeto intocado)."
|
|
71925
|
+
);
|
|
71926
|
+
}
|
|
71726
71927
|
let turnSystem;
|
|
71727
71928
|
if (auditMode) {
|
|
71728
71929
|
turnSystem = buildAuditSystemPrompt({
|
|
71729
71930
|
projectRoot,
|
|
71730
71931
|
workdir: sandbox.getWorkdir(),
|
|
71731
|
-
shellHint: sandbox.getShellHint()
|
|
71932
|
+
shellHint: sandbox.getShellHint(),
|
|
71933
|
+
mode: pocVerify ? "verify" : "audit"
|
|
71732
71934
|
});
|
|
71733
71935
|
} else {
|
|
71734
71936
|
const matchedSkills = selectSkillsForInput(userInput);
|
|
@@ -71742,6 +71944,9 @@ ${seen}`);
|
|
|
71742
71944
|
let lastError = null;
|
|
71743
71945
|
let autoContinues = 0;
|
|
71744
71946
|
let bridgeSteps = 0;
|
|
71947
|
+
const maxSteps = auditMode ? AUDIT_MAX_STEPS : MAX_STEPS;
|
|
71948
|
+
const maxAutoContinues = auditMode ? AUDIT_MAX_AUTO_CONTINUES : MAX_AUTO_CONTINUES;
|
|
71949
|
+
const maxBridgeSteps = auditMode ? AUDIT_MAX_BRIDGE_STEPS : MAX_BRIDGE_STEPS;
|
|
71745
71950
|
const modelChain = [...getActiveModelChain()];
|
|
71746
71951
|
const usingFixedModel = selectedModelKey !== null;
|
|
71747
71952
|
let rateLimitWaits = 0;
|
|
@@ -71809,11 +72014,11 @@ ${segs.join(
|
|
|
71809
72014
|
};
|
|
71810
72015
|
try {
|
|
71811
72016
|
const result = streamText({
|
|
71812
|
-
model:
|
|
72017
|
+
model: resolveLanguageModel2(modelKey),
|
|
71813
72018
|
system: turnSystem,
|
|
71814
72019
|
messages: history,
|
|
71815
72020
|
tools,
|
|
71816
|
-
stopWhen: stepCountIs(
|
|
72021
|
+
stopWhen: stepCountIs(maxSteps),
|
|
71817
72022
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
71818
72023
|
abortSignal: stallController.signal,
|
|
71819
72024
|
onError: ({ error: error51 }) => {
|
|
@@ -71897,12 +72102,12 @@ ${segs.join(
|
|
|
71897
72102
|
await result.finishReason.catch(() => "unknown")
|
|
71898
72103
|
);
|
|
71899
72104
|
const leakedCall = !isProxyModel && hasLeakedToolCallMarker(lastStepText);
|
|
71900
|
-
if (finishReason === "stop" && turnToolCalls === 0 && bridgeSteps <
|
|
72105
|
+
if (finishReason === "stop" && turnToolCalls === 0 && bridgeSteps < maxBridgeSteps && (isProxyModel || leakedCall)) {
|
|
71901
72106
|
const bridged = parseProxyToolCalls(lastStepText);
|
|
71902
72107
|
if (bridged) {
|
|
71903
72108
|
bridgeSteps++;
|
|
71904
72109
|
render.info(
|
|
71905
|
-
`\u25B8 ${leakedCall ? "tool call vazada recuperada" : "bridge"} (${bridgeSteps}/${
|
|
72110
|
+
`\u25B8 ${leakedCall ? "tool call vazada recuperada" : "bridge"} (${bridgeSteps}/${maxBridgeSteps}): executando por tr\xE1s ${bridged.length} chamada(s): ${summarizeBridgedCalls(bridged)}`
|
|
71906
72111
|
);
|
|
71907
72112
|
const resultText = await runBridgedToolCalls(bridged, signal);
|
|
71908
72113
|
if (signal?.aborted) {
|
|
@@ -71923,12 +72128,13 @@ ${resultText}`
|
|
|
71923
72128
|
finishReason,
|
|
71924
72129
|
lastStepText,
|
|
71925
72130
|
turnToolCalls,
|
|
71926
|
-
autoContinues
|
|
72131
|
+
autoContinues,
|
|
72132
|
+
maxAutoContinues
|
|
71927
72133
|
);
|
|
71928
72134
|
if (resume) {
|
|
71929
72135
|
autoContinues++;
|
|
71930
72136
|
render.info(
|
|
71931
|
-
`\u25B8 retomando automaticamente (${autoContinues}/${
|
|
72137
|
+
`\u25B8 retomando automaticamente (${autoContinues}/${maxAutoContinues}): ${resume.reason}`
|
|
71932
72138
|
);
|
|
71933
72139
|
if (resume.nudge) {
|
|
71934
72140
|
history.push({
|
|
@@ -71941,9 +72147,12 @@ ${resultText}`
|
|
|
71941
72147
|
}
|
|
71942
72148
|
if (finishReason !== "stop") {
|
|
71943
72149
|
render.info(
|
|
71944
|
-
`\u25B8 fim do turno: ${finishReason}` + (autoContinues >=
|
|
72150
|
+
`\u25B8 fim do turno: ${finishReason}` + (autoContinues >= maxAutoContinues ? " (limite de retomadas autom\xE1ticas atingido)" : "")
|
|
71945
72151
|
);
|
|
71946
72152
|
}
|
|
72153
|
+
if (modelKey === PROXY_MODEL_KEYS.kimi) {
|
|
72154
|
+
await reportKimiSession();
|
|
72155
|
+
}
|
|
71947
72156
|
render.endTurn();
|
|
71948
72157
|
return;
|
|
71949
72158
|
} catch (err) {
|
|
@@ -72045,6 +72254,11 @@ ${resultText}`
|
|
|
72045
72254
|
}
|
|
72046
72255
|
async function close() {
|
|
72047
72256
|
await sandbox.close();
|
|
72257
|
+
try {
|
|
72258
|
+
const removed = await sandbox.cleanupWorkspace();
|
|
72259
|
+
if (removed) render.info(`\u25B8 workspace SPRIT removida: ${removed}`);
|
|
72260
|
+
} catch {
|
|
72261
|
+
}
|
|
72048
72262
|
}
|
|
72049
72263
|
return {
|
|
72050
72264
|
send,
|
|
@@ -72056,10 +72270,12 @@ ${resultText}`
|
|
|
72056
72270
|
getModelChoices,
|
|
72057
72271
|
getModelSelection,
|
|
72058
72272
|
setModelSelection,
|
|
72273
|
+
isOpenRouterAvailable: hasOpenRouterKey,
|
|
72274
|
+
listOpenRouterModels: (signal) => listOpenRouterModels(signal),
|
|
72059
72275
|
close
|
|
72060
72276
|
};
|
|
72061
72277
|
}
|
|
72062
|
-
var import_promises2, import_node_path10, MAX_STEPS, MAX_OUTPUT_TOKENS, MAX_AUTO_CONTINUES, MAX_RATE_LIMIT_WAITS, STREAM_STALL_TIMEOUT_MS, MAX_STALL_RETRIES, isRateLimitError, sleep4, LEAKED_TOOL_CALL_RE, DANGLING_TAIL_RE, ACTION_ANNOUNCE_RE, BENIGN_CLOSER_RE, TOOL_CALL_NUDGE, MAX_BRIDGE_STEPS, BRIDGE_RESULT_PREAMBLE, LEAKED_CALL_RESULT_PREAMBLE, BRIDGEABLE_TOOLS, FINDINGS_ACTIONS, truncateBridgeSummary, isWebSessionProxyModel, proxyToolProtocolPolicy, SYSTEM_PROMPT_SOURCE, REQUIRED_SYSTEM_PROMPT_MARKERS, MODEL_LABELS, labelFor, loginRequiredHint, CLI_PYTHON_ONLY_POLICY, pythonOnlyPolicy, scriptFilePolicy, deepReconPolicy, httpAndFindingsPolicy, reconPhasesPolicy, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
|
|
72278
|
+
var import_promises2, import_node_path10, MAX_STEPS, AUDIT_MAX_STEPS, MAX_OUTPUT_TOKENS, MAX_AUTO_CONTINUES, AUDIT_MAX_AUTO_CONTINUES, MAX_RATE_LIMIT_WAITS, STREAM_STALL_TIMEOUT_MS, MAX_STALL_RETRIES, isRateLimitError, sleep4, LEAKED_TOOL_CALL_RE, DANGLING_TAIL_RE, ACTION_ANNOUNCE_RE, BENIGN_CLOSER_RE, TOOL_CALL_NUDGE, MAX_BRIDGE_STEPS, AUDIT_MAX_BRIDGE_STEPS, BRIDGE_RESULT_PREAMBLE, LEAKED_CALL_RESULT_PREAMBLE, BRIDGEABLE_TOOLS, FINDINGS_ACTIONS, truncateBridgeSummary, isWebSessionProxyModel, proxyToolProtocolPolicy, SYSTEM_PROMPT_SOURCE, REQUIRED_SYSTEM_PROMPT_MARKERS, MODEL_LABELS, labelFor, loginRequiredHint, CLI_PYTHON_ONLY_POLICY, pythonOnlyPolicy, scriptFilePolicy, deepReconPolicy, httpAndFindingsPolicy, reconPhasesPolicy, attackChainPolicy, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
|
|
72063
72279
|
var init_agent = __esm({
|
|
72064
72280
|
"src/agent.ts"() {
|
|
72065
72281
|
"use strict";
|
|
@@ -72090,8 +72306,10 @@ var init_agent = __esm({
|
|
|
72090
72306
|
init_proxy_manager2();
|
|
72091
72307
|
init_audit_mode();
|
|
72092
72308
|
MAX_STEPS = 100;
|
|
72309
|
+
AUDIT_MAX_STEPS = 240;
|
|
72093
72310
|
MAX_OUTPUT_TOKENS = 16384;
|
|
72094
72311
|
MAX_AUTO_CONTINUES = 3;
|
|
72312
|
+
AUDIT_MAX_AUTO_CONTINUES = 8;
|
|
72095
72313
|
MAX_RATE_LIMIT_WAITS = 4;
|
|
72096
72314
|
STREAM_STALL_TIMEOUT_MS = (() => {
|
|
72097
72315
|
const raw = Number(process.env.clawfast_CLI_STREAM_STALL_MS);
|
|
@@ -72119,6 +72337,7 @@ var init_agent = __esm({
|
|
|
72119
72337
|
BENIGN_CLOSER_RE = /\b(?:let me know|just let me know|deixa eu saber|me avis(?:e|a)|qualquer (?:coisa|d[uú]vida)|fico (?:à|a) disposi[cç][aã]o)\b[^.!?:\n]{0,80}[.!?]?\s*$/i;
|
|
72120
72338
|
TOOL_CALL_NUDGE = "Voc\xEA anunciou uma a\xE7\xE3o mas n\xE3o executou nenhuma ferramenta neste turno. Pare de descrever o que vai fazer e CHAME a ferramenta agora: use a ferramenta `file` (action write para criar, edit para alterar) para qualquer arquivo ou script, e `run_terminal_cmd` para rodar comandos. N\xC3O escreva o conte\xFAdo do arquivo na resposta nem cole markup de tool call \u2014 emita a chamada de ferramenta de verdade.";
|
|
72121
72339
|
MAX_BRIDGE_STEPS = 50;
|
|
72340
|
+
AUDIT_MAX_BRIDGE_STEPS = 120;
|
|
72122
72341
|
BRIDGE_RESULT_PREAMBLE = "Voc\xEA roda via proxy de sess\xE3o web (sem function-calling nativo), ent\xE3o o runtime do CLI consumiu o bloco JSON anterior como chamada interna e EXECUTOU de verdade. N\xE3o repita o bloco nem o texto anterior. Os resultados REAIS est\xE3o abaixo. Continue a tarefa: se precisar de mais a\xE7\xF5es, emita SOMENTE o PR\xD3XIMO bloco ```json (um array de {brief, command, timeout} para terminal, ou {action, path, text} para arquivo). Um bloco por vez. Quando a tarefa terminar, responda em texto normal SEM bloco json.";
|
|
72123
72342
|
LEAKED_CALL_RESULT_PREAMBLE = "Sua chamada de ferramenta anterior chegou como TEXTO (n\xE3o como function call nativo), ent\xE3o o runtime do CLI a executou mesmo assim. Os resultados REAIS est\xE3o abaixo. Continue a tarefa chamando as ferramentas NORMALMENTE (run_terminal_cmd, file, todo_write) como function calls de verdade \u2014 N\xC3O cole markup de tool call (`call_tool_function`, `<tool_call>`, etc.) nem JSON de chamada como texto na resposta.";
|
|
72124
72343
|
BRIDGEABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -72150,6 +72369,7 @@ Regras:
|
|
|
72150
72369
|
4. Crie/edite scripts e c\xF3digo-fonte SOMENTE via {"action":"write"} / {"action":"edit"} \u2014 nunca por redirecionamento de shell (echo >, cat <<EOF, tee, Out-File...).
|
|
72151
72370
|
5. Durante a\xE7\xF5es, coloque o conte\xFAdo operacional apenas dentro do JSON. Fora do JSON use no m\xE1ximo uma frase curta.
|
|
72152
72371
|
6. Quando a tarefa estiver conclu\xEDda, responda em texto normal SEM bloco json.
|
|
72372
|
+
7. VOC\xCA N\xC3O TEM AMBIENTE PR\xD3PRIO. Voc\xEA n\xE3o possui sandbox, m\xE1quina, terminal ou sistema de arquivos seu. Caminhos como /mnt, /mnt/agents, /mnt/kimi N\xC3O existem. A \xDANICA forma de listar pastas, ler arquivos ou rodar comandos \xE9 emitindo o bloco \`\`\`json acima \u2014 que o runtime executa na m\xE1quina REAL do operador (use o caminho/diret\xF3rio de trabalho informado no system prompt). NUNCA invente sa\xEDda de comando, conte\xFAdo de arquivo ou estrutura de diret\xF3rios; s\xF3 use o que voltar como resultado real. Para "analisar/olhar/listar <caminho>", sua PRIMEIRA resposta deve ser um bloco json, sem pre\xE2mbulo.
|
|
72153
72373
|
</web_session_tool_protocol>`;
|
|
72154
72374
|
SYSTEM_PROMPT_SOURCE = "lib/system-prompt.ts";
|
|
72155
72375
|
REQUIRED_SYSTEM_PROMPT_MARKERS = [
|
|
@@ -72177,7 +72397,7 @@ Regras:
|
|
|
72177
72397
|
"fallback-openai-chat-latest": "OpenAI - chat-latest",
|
|
72178
72398
|
"model-nvidia-nemotron": "NVIDIA - nemotron-3-ultra-550b"
|
|
72179
72399
|
};
|
|
72180
|
-
labelFor = (key) => MODEL_LABELS[key] ?? key;
|
|
72400
|
+
labelFor = (key) => isOpenRouterDynamicKey(key) ? `OpenRouter - ${openRouterSlugFromKey(key)}` : MODEL_LABELS[key] ?? key;
|
|
72181
72401
|
loginRequiredHint = (msg) => {
|
|
72182
72402
|
if (/account is suspended|violation of user policies|account has been suspended/i.test(
|
|
72183
72403
|
msg
|
|
@@ -72283,6 +72503,26 @@ Beyond recon/recon_deep.py (HTTP/JS/secret/browser recon), the workspace at ${wo
|
|
|
72283
72503
|
|
|
72284
72504
|
Each module writes a JSON (and where useful Markdown) report under ./reports/. Read it, then record anything actionable with the findings tool. You MAY extend any module via the file tool \u2014 keep it Python and keep the scope gate intact.
|
|
72285
72505
|
</recon_phases>`;
|
|
72506
|
+
attackChainPolicy = (workdir) => `
|
|
72507
|
+
|
|
72508
|
+
<attack_and_remediation_chain>
|
|
72509
|
+
ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
|
|
72510
|
+
|
|
72511
|
+
When the operator asks you to "attack", "explore vulnerabilities", "exploit", "pentest hard", "find a way in", or to FIX/close a vulnerability, follow the seeded offensive\u2192defensive chain at ${workdir}/recon. Do NOT hand-roll a one-off exploit script \u2014 these modules are the canonical engines, scope-gated by recon/scope.txt, AUTHORIZED-USE-ONLY, and strictly NON-DESTRUCTIVE (they prove impact; they never DROP data, mass-exfiltrate, or DoS).
|
|
72512
|
+
|
|
72513
|
+
1. MAP THE ROUTES (saber as rotas): \`python recon/recon_routes.py https://alvo.com --wayback\`
|
|
72514
|
+
Aggregates crawl + HTML forms + JS endpoints + robots/sitemap + Swagger/OpenAPI (+ Wayback) into ONE route map, annotating each route with its parameters and whether it needs auth. Writes reports/routes_<host>.json. This is the attack surface.
|
|
72515
|
+
|
|
72516
|
+
2. ATTACK & PROVE (o ataque): \`python recon/exploit_engine.py --routes reports/routes_<host>.json\`
|
|
72517
|
+
(or a single target: \`python recon/exploit_engine.py "https://alvo/item?id=1" --classes sqli,xss,lfi,cmdi,ssrf,redir\`). Runs verified playbooks per OWASP class (SQLi error/boolean/time-based, reflected XSS, LFI/traversal, command injection, SSRF, open redirect, IDOR with --idor-range). It only marks a finding "confirmed" when the LIVE response proves impact, capturing the exact payload + evidence. Use --cookie / --header for authenticated endpoints, --sleep for time-based, --oob for SSRF callbacks. Writes reports/exploit_<host>.json.
|
|
72518
|
+
|
|
72519
|
+
3. RECORD: take every CONFIRMED finding from the exploit report and store it with the \`findings\` tool (status confirmed, paste the evidence). This is the engagement memory and the executive report.
|
|
72520
|
+
|
|
72521
|
+
4. CLOSE THE BREACH (fechar a brecha para n\xE3o ter acesso de novo): \`python recon/harden.py --report reports/exploit_<host>.json\`
|
|
72522
|
+
For each confirmed finding it emits the concrete fix (parameterized queries, output encoding, allowlist validation, security headers, WAF rule) AND REPLAYS the PoC to verify: "FIXED" only once the exploit no longer reproduces. After the operator applies a fix, RUN harden.py AGAIN \u2014 a vuln is only closed when its verdict flips to FIXED \u2705. Report what is still STILL OPEN \u274C.
|
|
72523
|
+
|
|
72524
|
+
Dependencies (optional, improve fidelity): \`python -m pip install requests\`. You MAY extend any module via the file tool \u2014 keep it Python, keep the scope gate, keep it non-destructive.
|
|
72525
|
+
</attack_and_remediation_chain>`;
|
|
72286
72526
|
skillsScopePolicy = () => `
|
|
72287
72527
|
|
|
72288
72528
|
<skills_scope>
|
|
@@ -72502,6 +72742,94 @@ var init_interactive_input = __esm({
|
|
|
72502
72742
|
};
|
|
72503
72743
|
});
|
|
72504
72744
|
}
|
|
72745
|
+
/**
|
|
72746
|
+
* Searchable single-choice list: a live filter bar on top, a scrolling
|
|
72747
|
+
* viewport of matches below. Type to filter, ↑/↓ to move (wraps), Enter to
|
|
72748
|
+
* pick, Esc / Ctrl+C to cancel. Built for big catalogs (e.g. every OpenRouter
|
|
72749
|
+
* model). Returns the chosen item's ORIGINAL index, or null on cancel.
|
|
72750
|
+
*/
|
|
72751
|
+
searchSelect(title, items, opts = {}) {
|
|
72752
|
+
const pageSize = Math.max(4, opts.pageSize ?? 10);
|
|
72753
|
+
const placeholder = opts.placeholder ?? "digite para filtrar";
|
|
72754
|
+
let query = "";
|
|
72755
|
+
let sel = 0;
|
|
72756
|
+
let top = 0;
|
|
72757
|
+
this.lastRows = 0;
|
|
72758
|
+
out2("\n");
|
|
72759
|
+
const filtered = () => {
|
|
72760
|
+
const q = query.toLowerCase().trim();
|
|
72761
|
+
const all = items.map((it, i) => ({ it, i }));
|
|
72762
|
+
if (!q) return all;
|
|
72763
|
+
return all.filter(
|
|
72764
|
+
({ it }) => it.label.toLowerCase().includes(q) || (it.hint ?? "").toLowerCase().includes(q)
|
|
72765
|
+
);
|
|
72766
|
+
};
|
|
72767
|
+
const draw = () => {
|
|
72768
|
+
const list = filtered();
|
|
72769
|
+
if (sel >= list.length) sel = Math.max(0, list.length - 1);
|
|
72770
|
+
if (sel < top) top = sel;
|
|
72771
|
+
if (sel >= top + pageSize) top = sel - pageSize + 1;
|
|
72772
|
+
this.renderSearchList(
|
|
72773
|
+
title,
|
|
72774
|
+
placeholder,
|
|
72775
|
+
query,
|
|
72776
|
+
list,
|
|
72777
|
+
sel,
|
|
72778
|
+
top,
|
|
72779
|
+
pageSize,
|
|
72780
|
+
items.length
|
|
72781
|
+
);
|
|
72782
|
+
};
|
|
72783
|
+
draw();
|
|
72784
|
+
return new Promise((resolve2) => {
|
|
72785
|
+
const finish = (value) => {
|
|
72786
|
+
this.collapseTo(this.lastRows);
|
|
72787
|
+
this.onKey = null;
|
|
72788
|
+
resolve2(value);
|
|
72789
|
+
};
|
|
72790
|
+
this.onKey = (str, key) => {
|
|
72791
|
+
if (key.ctrl && key.name === "c") return finish(null);
|
|
72792
|
+
const list = filtered();
|
|
72793
|
+
switch (key.name) {
|
|
72794
|
+
case "escape":
|
|
72795
|
+
return finish(null);
|
|
72796
|
+
case "return":
|
|
72797
|
+
if (list.length === 0) return;
|
|
72798
|
+
return finish(list[sel].i);
|
|
72799
|
+
case "up":
|
|
72800
|
+
if (list.length) sel = (sel - 1 + list.length) % list.length;
|
|
72801
|
+
draw();
|
|
72802
|
+
return;
|
|
72803
|
+
case "down":
|
|
72804
|
+
case "tab":
|
|
72805
|
+
if (list.length) sel = (sel + 1) % list.length;
|
|
72806
|
+
draw();
|
|
72807
|
+
return;
|
|
72808
|
+
case "backspace":
|
|
72809
|
+
if (query.length) {
|
|
72810
|
+
query = query.slice(0, -1);
|
|
72811
|
+
sel = 0;
|
|
72812
|
+
top = 0;
|
|
72813
|
+
}
|
|
72814
|
+
draw();
|
|
72815
|
+
return;
|
|
72816
|
+
}
|
|
72817
|
+
if (key.ctrl && key.name === "u") {
|
|
72818
|
+
query = "";
|
|
72819
|
+
sel = 0;
|
|
72820
|
+
top = 0;
|
|
72821
|
+
draw();
|
|
72822
|
+
return;
|
|
72823
|
+
}
|
|
72824
|
+
if (str && !key.ctrl && !key.meta && str.charCodeAt(0) >= 32) {
|
|
72825
|
+
query += str;
|
|
72826
|
+
sel = 0;
|
|
72827
|
+
top = 0;
|
|
72828
|
+
draw();
|
|
72829
|
+
}
|
|
72830
|
+
};
|
|
72831
|
+
});
|
|
72832
|
+
}
|
|
72505
72833
|
/**
|
|
72506
72834
|
* Lightweight line reader used WHILE a turn is running, so typed lines can be
|
|
72507
72835
|
* forwarded to an interactive command's stdin. No box/dropdown — just echoes
|
|
@@ -72722,12 +73050,12 @@ var init_interactive_input = __esm({
|
|
|
72722
73050
|
const prefix = frame2("\u2570\u2500") + gradient("\u276F", { bold: true }) + " ";
|
|
72723
73051
|
const prefixLen = 4;
|
|
72724
73052
|
const avail = Math.max(8, width - prefixLen - 1);
|
|
72725
|
-
|
|
72726
|
-
|
|
72727
|
-
|
|
72728
|
-
|
|
72729
|
-
|
|
72730
|
-
const lines = [top, idLine,
|
|
73053
|
+
const { textLines, caretLine, caretCol } = this.wrap(
|
|
73054
|
+
prefix,
|
|
73055
|
+
prefixLen,
|
|
73056
|
+
avail
|
|
73057
|
+
);
|
|
73058
|
+
const lines = [top, idLine, ...textLines];
|
|
72731
73059
|
const items = this.dropdownItems();
|
|
72732
73060
|
if (items.length > 0) {
|
|
72733
73061
|
this.ddSel = Math.min(this.ddSel, items.length - 1);
|
|
@@ -72752,9 +73080,27 @@ var init_interactive_input = __esm({
|
|
|
72752
73080
|
} else {
|
|
72753
73081
|
this.ddSel = 0;
|
|
72754
73082
|
}
|
|
72755
|
-
this.inputLineIndex = 2;
|
|
73083
|
+
this.inputLineIndex = 2 + caretLine;
|
|
72756
73084
|
this.paint(lines, caretCol);
|
|
72757
73085
|
}
|
|
73086
|
+
/**
|
|
73087
|
+
* Split the buffer into terminal-width rows. The first row carries `prefix`
|
|
73088
|
+
* (e.g. "❯ "); continuation rows are indented by `prefixLen` spaces so the
|
|
73089
|
+
* text columns line up. Also returns which wrapped row the caret sits on and
|
|
73090
|
+
* its absolute column, so paint() can place the cursor correctly.
|
|
73091
|
+
*/
|
|
73092
|
+
wrap(prefix, prefixLen, avail) {
|
|
73093
|
+
const indent = " ".repeat(prefixLen);
|
|
73094
|
+
const caretLine = Math.floor(this.cursor / avail);
|
|
73095
|
+
const caretColInLine = this.cursor - caretLine * avail;
|
|
73096
|
+
const chunks = [];
|
|
73097
|
+
for (let i = 0; i < this.buffer.length; i += avail) {
|
|
73098
|
+
chunks.push(this.buffer.slice(i, i + avail));
|
|
73099
|
+
}
|
|
73100
|
+
while (chunks.length <= caretLine) chunks.push("");
|
|
73101
|
+
const textLines = chunks.map((c, i) => (i === 0 ? prefix : indent) + c);
|
|
73102
|
+
return { textLines, caretLine, caretCol: prefixLen + caretColInLine };
|
|
73103
|
+
}
|
|
72758
73104
|
/** Compact single-line labelled prompt ("nome ❯ …") for step-by-step capture. */
|
|
72759
73105
|
renderCompact() {
|
|
72760
73106
|
const width = cols();
|
|
@@ -72762,12 +73108,13 @@ var init_interactive_input = __esm({
|
|
|
72762
73108
|
const prefix = `${gradient(label, { bold: true })} ${gradient("\u276F", { bold: true })} `;
|
|
72763
73109
|
const prefixLen = vlen(`${label} \u276F `);
|
|
72764
73110
|
const avail = Math.max(8, width - prefixLen - 1);
|
|
72765
|
-
|
|
72766
|
-
|
|
72767
|
-
|
|
72768
|
-
|
|
72769
|
-
|
|
72770
|
-
this.
|
|
73111
|
+
const { textLines, caretLine, caretCol } = this.wrap(
|
|
73112
|
+
prefix,
|
|
73113
|
+
prefixLen,
|
|
73114
|
+
avail
|
|
73115
|
+
);
|
|
73116
|
+
this.inputLineIndex = caretLine;
|
|
73117
|
+
this.paint(textLines, caretCol);
|
|
72771
73118
|
}
|
|
72772
73119
|
/** Repaint the whole block, then place the cursor on the active input line. */
|
|
72773
73120
|
paint(lines, caretCol) {
|
|
@@ -72812,6 +73159,42 @@ var init_interactive_input = __esm({
|
|
|
72812
73159
|
);
|
|
72813
73160
|
this.paintBlock(lines);
|
|
72814
73161
|
}
|
|
73162
|
+
/** Render the search bar + a scrolling viewport of filtered matches. */
|
|
73163
|
+
renderSearchList(title, placeholder, query, list, sel, top, pageSize, total) {
|
|
73164
|
+
const width = cols();
|
|
73165
|
+
const inner = Math.min(Math.max(40, width - 4), 80);
|
|
73166
|
+
const lines = [];
|
|
73167
|
+
lines.push(`${frame2("\u256D\u2500\u25C6 ")}${gradient(title, { bold: true })}`);
|
|
73168
|
+
const shown = query.length ? query : `${C4.dim}${placeholder}${C4.reset}`;
|
|
73169
|
+
const counter = query.trim() ? `${C4.dim}${list.length}/${total}${C4.reset}` : `${C4.dim}${total}${C4.reset}`;
|
|
73170
|
+
lines.push(
|
|
73171
|
+
`${frame2("\u2502 ")}${gradient("\u2315", { bold: true })} ${shown}${C4.reset} ${counter}`
|
|
73172
|
+
);
|
|
73173
|
+
lines.push(frame2("\u2502"));
|
|
73174
|
+
if (list.length === 0) {
|
|
73175
|
+
lines.push(`${frame2("\u2502 ")}${C4.dim}nenhum modelo corresponde${C4.reset}`);
|
|
73176
|
+
} else {
|
|
73177
|
+
const end = Math.min(top + pageSize, list.length);
|
|
73178
|
+
for (let i = top; i < end; i++) {
|
|
73179
|
+
const active2 = i === sel;
|
|
73180
|
+
const { it } = list[i];
|
|
73181
|
+
const marker25 = active2 ? gradient("\u276F ", { bold: true }) : `${C4.dim} `;
|
|
73182
|
+
const text2 = active2 ? gradient(it.label, { bold: true }) : `${C4.reset}${it.label}`;
|
|
73183
|
+
const labelW = vlen(it.label);
|
|
73184
|
+
const hint = it.hint ? ` ${C4.dim}${truncate4(it.hint, Math.max(6, inner - labelW - 6))}${C4.reset}` : "";
|
|
73185
|
+
lines.push(`${frame2("\u2502 ")}${C4.reset}${marker25}${text2}${C4.reset}${hint}`);
|
|
73186
|
+
}
|
|
73187
|
+
if (list.length > pageSize) {
|
|
73188
|
+
lines.push(
|
|
73189
|
+
`${frame2("\u2502 ")}${C4.dim}${sel + 1}/${list.length}${C4.reset}`
|
|
73190
|
+
);
|
|
73191
|
+
}
|
|
73192
|
+
}
|
|
73193
|
+
lines.push(
|
|
73194
|
+
`${frame2("\u2570\u2500")} ${C4.dim}digite filtra \xB7 \u2191\u2193 navega \xB7 Enter seleciona \xB7 Esc cancela${C4.reset}`
|
|
73195
|
+
);
|
|
73196
|
+
this.paintBlock(lines);
|
|
73197
|
+
}
|
|
72815
73198
|
/** Repaint a block and leave the cursor just below it (for list/menu modes). */
|
|
72816
73199
|
paintBlock(lines) {
|
|
72817
73200
|
let s = "";
|
|
@@ -72869,8 +73252,8 @@ async function main() {
|
|
|
72869
73252
|
`
|
|
72870
73253
|
);
|
|
72871
73254
|
const COMMANDS = [
|
|
72872
|
-
{ name: "/model", desc: "trocar o modelo (
|
|
72873
|
-
{ name: "/api", desc: "trocar
|
|
73255
|
+
{ name: "/model", desc: "trocar o modelo (setas; OpenRouter c/ busca)" },
|
|
73256
|
+
{ name: "/api", desc: "trocar chave de API (NVIDIA / OpenRouter)" },
|
|
72874
73257
|
{ name: "/skills", desc: "listar as skills instaladas" },
|
|
72875
73258
|
{ name: "/skillcreator", desc: "criar uma nova skill" },
|
|
72876
73259
|
{ name: "/system", desc: "salvar o system prompt (HTML) na \xC1rea de Trabalho" },
|
|
@@ -73123,69 +73506,66 @@ ${C5.dim}ja disponivel para todos os modelos nesta sessao.${C5.reset}
|
|
|
73123
73506
|
`
|
|
73124
73507
|
);
|
|
73125
73508
|
};
|
|
73126
|
-
const
|
|
73127
|
-
|
|
73128
|
-
|
|
73129
|
-
|
|
73130
|
-
|
|
73131
|
-
|
|
73132
|
-
|
|
73133
|
-
|
|
73134
|
-
|
|
73135
|
-
|
|
73136
|
-
|
|
73137
|
-
|
|
73138
|
-
|
|
73139
|
-
|
|
73140
|
-
|
|
73141
|
-
|
|
73142
|
-
|
|
73143
|
-
|
|
73144
|
-
|
|
73145
|
-
|
|
73146
|
-
try {
|
|
73147
|
-
printModelResult(await agent.setModelSelection(arg));
|
|
73148
|
-
} catch (err) {
|
|
73149
|
-
printFatal(err);
|
|
73509
|
+
const KEY_PROVIDERS = [
|
|
73510
|
+
{
|
|
73511
|
+
id: "nvidia",
|
|
73512
|
+
name: "NVIDIA",
|
|
73513
|
+
envVar: "NVIDIA_API_KEY",
|
|
73514
|
+
createUrl: "https://build.nvidia.com/",
|
|
73515
|
+
keyRegex: /nvapi-[A-Za-z0-9_-]+/,
|
|
73516
|
+
keyHint: "nvapi-\u2026",
|
|
73517
|
+
test: testNvidiaKey,
|
|
73518
|
+
swap: swapNvidiaKey
|
|
73519
|
+
},
|
|
73520
|
+
{
|
|
73521
|
+
id: "openrouter",
|
|
73522
|
+
name: "OpenRouter",
|
|
73523
|
+
envVar: "OPENROUTER_API_KEY",
|
|
73524
|
+
createUrl: "https://openrouter.ai/keys",
|
|
73525
|
+
keyRegex: /sk-or-[A-Za-z0-9_-]+/,
|
|
73526
|
+
keyHint: "sk-or-\u2026",
|
|
73527
|
+
test: testOpenRouterKey,
|
|
73528
|
+
swap: swapOpenRouterKey
|
|
73150
73529
|
}
|
|
73151
|
-
|
|
73152
|
-
const
|
|
73530
|
+
];
|
|
73531
|
+
const openRouterProvider = KEY_PROVIDERS.find((p) => p.id === "openrouter");
|
|
73532
|
+
const promptAndSwapKey = async (provider, inlineKey = "") => {
|
|
73153
73533
|
let raw = inlineKey;
|
|
73154
73534
|
if (!raw) {
|
|
73155
73535
|
process.stdout.write(
|
|
73156
|
-
`${C5.dim}Cole a nova chave
|
|
73536
|
+
`${C5.dim}Cole a nova chave ${provider.name} (cria em ${C5.reset}${C5.cyan}${provider.createUrl}${C5.reset}${C5.dim}). Enter vazio ou Ctrl+C cancela.${C5.reset}
|
|
73157
73537
|
`
|
|
73158
73538
|
);
|
|
73159
73539
|
const res = await inputUI.prompt({
|
|
73160
|
-
label:
|
|
73540
|
+
label: `nova ${provider.name} API key`,
|
|
73161
73541
|
secret: true
|
|
73162
73542
|
});
|
|
73163
73543
|
if (res.type !== "line" || !res.value.trim()) {
|
|
73164
73544
|
process.stdout.write(`${C5.dim}troca de chave cancelada${C5.reset}
|
|
73165
73545
|
`);
|
|
73166
|
-
return;
|
|
73546
|
+
return false;
|
|
73167
73547
|
}
|
|
73168
73548
|
raw = res.value;
|
|
73169
73549
|
}
|
|
73170
|
-
const key = raw.replace(/\s+/g, "").match(
|
|
73550
|
+
const key = raw.replace(/\s+/g, "").match(provider.keyRegex)?.[0] ?? "";
|
|
73171
73551
|
if (!key) {
|
|
73172
73552
|
process.stdout.write(
|
|
73173
|
-
`${C5.red}\u2717 n\xE3o encontrei uma chave
|
|
73553
|
+
`${C5.red}\u2717 n\xE3o encontrei uma chave ${provider.name} no que foi colado${C5.reset} ${C5.dim}(esperado um token "${provider.keyHint}"; cole a chave completa).${C5.reset}
|
|
73174
73554
|
`
|
|
73175
73555
|
);
|
|
73176
|
-
return;
|
|
73556
|
+
return false;
|
|
73177
73557
|
}
|
|
73178
|
-
process.stdout.write(`${C5.dim}testando a chave na
|
|
73558
|
+
process.stdout.write(`${C5.dim}testando a chave na ${provider.name}\u2026${C5.reset}
|
|
73179
73559
|
`);
|
|
73180
|
-
const test = await
|
|
73560
|
+
const test = await provider.test(key);
|
|
73181
73561
|
if (!test.ok) {
|
|
73182
73562
|
if (test.status === 401 || test.status === 403) {
|
|
73183
73563
|
process.stdout.write(
|
|
73184
|
-
`${C5.red}\u2717 a
|
|
73185
|
-
${C5.dim}chave N\xC3O salva \u2014 confira se copiou inteira e se a conta tem acesso
|
|
73564
|
+
`${C5.red}\u2717 a ${provider.name} recusou a chave (${test.status}): ${test.detail}${C5.reset}
|
|
73565
|
+
${C5.dim}chave N\xC3O salva \u2014 confira se copiou inteira e se a conta tem acesso.${C5.reset}
|
|
73186
73566
|
`
|
|
73187
73567
|
);
|
|
73188
|
-
return;
|
|
73568
|
+
return false;
|
|
73189
73569
|
}
|
|
73190
73570
|
process.stdout.write(
|
|
73191
73571
|
`${C5.yellow}\u26A0 n\xE3o consegui validar (${test.status || "rede"}): ${test.detail}${C5.reset}
|
|
@@ -73193,13 +73573,123 @@ ${C5.dim}salvando mesmo assim \u2014 o pr\xF3ximo pedido vai usar a chave nova.$
|
|
|
73193
73573
|
`
|
|
73194
73574
|
);
|
|
73195
73575
|
}
|
|
73196
|
-
const file2 =
|
|
73576
|
+
const file2 = provider.swap(key);
|
|
73197
73577
|
const masked = `${key.slice(0, 9)}\u2026${key.slice(-4)} (${key.length} chars)`;
|
|
73198
73578
|
process.stdout.write(
|
|
73199
|
-
`${C5.green}\u2713 chave
|
|
73579
|
+
`${C5.green}\u2713 chave ${provider.name} trocada e ativa${C5.reset} ${C5.dim}${masked}${C5.reset}
|
|
73200
73580
|
${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
|
|
73201
73581
|
`
|
|
73202
73582
|
);
|
|
73583
|
+
return true;
|
|
73584
|
+
};
|
|
73585
|
+
const openOpenRouterPicker = async () => {
|
|
73586
|
+
if (!agent.isOpenRouterAvailable()) {
|
|
73587
|
+
process.stdout.write(
|
|
73588
|
+
`${C5.dim}OpenRouter ainda sem chave. Vamos adicionar uma.${C5.reset}
|
|
73589
|
+
`
|
|
73590
|
+
);
|
|
73591
|
+
const ok = await promptAndSwapKey(openRouterProvider);
|
|
73592
|
+
if (!ok) return;
|
|
73593
|
+
}
|
|
73594
|
+
process.stdout.write(`${C5.dim}buscando cat\xE1logo OpenRouter\u2026${C5.reset}
|
|
73595
|
+
`);
|
|
73596
|
+
let models;
|
|
73597
|
+
try {
|
|
73598
|
+
models = await agent.listOpenRouterModels();
|
|
73599
|
+
} catch (err) {
|
|
73600
|
+
process.stdout.write(
|
|
73601
|
+
`${C5.red}\u2717 falha ao listar modelos OpenRouter: ${err instanceof Error ? err.message : String(err)}${C5.reset}
|
|
73602
|
+
`
|
|
73603
|
+
);
|
|
73604
|
+
return;
|
|
73605
|
+
}
|
|
73606
|
+
if (models.length === 0) {
|
|
73607
|
+
process.stdout.write(
|
|
73608
|
+
`${C5.yellow}\u26A0 a OpenRouter n\xE3o retornou nenhum modelo${C5.reset}
|
|
73609
|
+
`
|
|
73610
|
+
);
|
|
73611
|
+
return;
|
|
73612
|
+
}
|
|
73613
|
+
const items = models.map((m) => ({
|
|
73614
|
+
label: m.name,
|
|
73615
|
+
hint: `${m.id}${m.promptPrice === "0" ? " \xB7 free" : ""}`
|
|
73616
|
+
}));
|
|
73617
|
+
const choice2 = await inputUI.searchSelect(
|
|
73618
|
+
"OpenRouter \u2014 buscar modelo",
|
|
73619
|
+
items,
|
|
73620
|
+
{ placeholder: "digite para filtrar (nome ou slug)" }
|
|
73621
|
+
);
|
|
73622
|
+
if (choice2 === null) {
|
|
73623
|
+
process.stdout.write(`${C5.dim}sele\xE7\xE3o de modelo cancelada${C5.reset}
|
|
73624
|
+
`);
|
|
73625
|
+
return;
|
|
73626
|
+
}
|
|
73627
|
+
try {
|
|
73628
|
+
printModelResult(
|
|
73629
|
+
await agent.setModelSelection(`openrouter:${models[choice2].id}`)
|
|
73630
|
+
);
|
|
73631
|
+
} catch (err) {
|
|
73632
|
+
printFatal(err);
|
|
73633
|
+
}
|
|
73634
|
+
};
|
|
73635
|
+
const openModelSelector = async () => {
|
|
73636
|
+
const state = agent.getModelSelection();
|
|
73637
|
+
const orAvailable = agent.isOpenRouterAvailable();
|
|
73638
|
+
const items = [
|
|
73639
|
+
{
|
|
73640
|
+
label: "Auto \u2014 cadeia de fallback autom\xE1tica",
|
|
73641
|
+
hint: "tenta os modelos em ordem"
|
|
73642
|
+
},
|
|
73643
|
+
...state.chain.map((c) => ({ label: c.label, hint: c.key }))
|
|
73644
|
+
];
|
|
73645
|
+
const openRouterIdx = items.length;
|
|
73646
|
+
items.push({
|
|
73647
|
+
label: "OpenRouter \u2014 buscar no cat\xE1logo completo",
|
|
73648
|
+
hint: orAvailable ? "lista todos os modelos da sua conta" : "pede a API key e lista o cat\xE1logo"
|
|
73649
|
+
});
|
|
73650
|
+
const activeIsOpenRouter = state.activeModelKey.startsWith("openrouter:");
|
|
73651
|
+
const activeIdx = state.mode === "auto" ? 0 : activeIsOpenRouter ? openRouterIdx : Math.max(
|
|
73652
|
+
0,
|
|
73653
|
+
1 + state.chain.findIndex((c) => c.key === state.activeModelKey)
|
|
73654
|
+
);
|
|
73655
|
+
const choice2 = await inputUI.select("selecionar modelo", items, activeIdx);
|
|
73656
|
+
if (choice2 === null) {
|
|
73657
|
+
process.stdout.write(`${C5.dim}sele\xE7\xE3o de modelo cancelada${C5.reset}
|
|
73658
|
+
`);
|
|
73659
|
+
return;
|
|
73660
|
+
}
|
|
73661
|
+
if (choice2 === openRouterIdx) {
|
|
73662
|
+
await openOpenRouterPicker();
|
|
73663
|
+
return;
|
|
73664
|
+
}
|
|
73665
|
+
const arg = choice2 === 0 ? "auto" : state.chain[choice2 - 1].key;
|
|
73666
|
+
try {
|
|
73667
|
+
printModelResult(await agent.setModelSelection(arg));
|
|
73668
|
+
} catch (err) {
|
|
73669
|
+
printFatal(err);
|
|
73670
|
+
}
|
|
73671
|
+
};
|
|
73672
|
+
const handleApiSwap = async (inlineKey) => {
|
|
73673
|
+
if (inlineKey) {
|
|
73674
|
+
const stripped = inlineKey.replace(/\s+/g, "");
|
|
73675
|
+
const provider = KEY_PROVIDERS.find((p) => p.keyRegex.test(stripped)) ?? KEY_PROVIDERS[0];
|
|
73676
|
+
await promptAndSwapKey(provider, inlineKey);
|
|
73677
|
+
return;
|
|
73678
|
+
}
|
|
73679
|
+
const idx = await inputUI.select(
|
|
73680
|
+
"trocar chave de API",
|
|
73681
|
+
KEY_PROVIDERS.map((p) => ({
|
|
73682
|
+
label: p.name,
|
|
73683
|
+
hint: process.env[p.envVar]?.trim() ? `${p.envVar} (configurada)` : p.envVar
|
|
73684
|
+
})),
|
|
73685
|
+
0
|
|
73686
|
+
);
|
|
73687
|
+
if (idx === null) {
|
|
73688
|
+
process.stdout.write(`${C5.dim}troca de chave cancelada${C5.reset}
|
|
73689
|
+
`);
|
|
73690
|
+
return;
|
|
73691
|
+
}
|
|
73692
|
+
await promptAndSwapKey(KEY_PROVIDERS[idx]);
|
|
73203
73693
|
};
|
|
73204
73694
|
const handleLine = async (line) => {
|
|
73205
73695
|
if (closing) return;
|
|
@@ -73226,6 +73716,10 @@ ${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
|
|
|
73226
73716
|
}
|
|
73227
73717
|
if (input.startsWith("/model ") || input.startsWith("/models ")) {
|
|
73228
73718
|
const arg = input.replace(/^\/models?\s*/, "");
|
|
73719
|
+
if (arg.toLowerCase() === "openrouter" || arg.toLowerCase() === "or") {
|
|
73720
|
+
await openOpenRouterPicker();
|
|
73721
|
+
return;
|
|
73722
|
+
}
|
|
73229
73723
|
try {
|
|
73230
73724
|
printModelResult(await agent.setModelSelection(arg));
|
|
73231
73725
|
} catch (err) {
|
|
@@ -73379,11 +73873,12 @@ var init_index = __esm({
|
|
|
73379
73873
|
configuredModelProviders = [
|
|
73380
73874
|
deepseekEnabled ? "DeepSeek (proxy/web session)" : null,
|
|
73381
73875
|
process.env.NVIDIA_API_KEY?.trim() ? "NVIDIA build" : null,
|
|
73382
|
-
process.env.OPENAI_API_KEY?.trim() ? "OpenAI" : null
|
|
73876
|
+
process.env.OPENAI_API_KEY?.trim() ? "OpenAI" : null,
|
|
73877
|
+
process.env.OPENROUTER_API_KEY?.trim() ? "OpenRouter" : null
|
|
73383
73878
|
].filter((name25) => Boolean(name25));
|
|
73384
73879
|
if (configuredModelProviders.length === 0) {
|
|
73385
73880
|
console.error(
|
|
73386
|
-
`Nenhuma chave de modelo configurada. Defina NVIDIA_API_KEY (ou OPENAI_API_KEY) em ${clawfastEnvPath()} ou como vari\xE1vel de ambiente.`
|
|
73881
|
+
`Nenhuma chave de modelo configurada. Defina NVIDIA_API_KEY (ou OPENAI_API_KEY / OPENROUTER_API_KEY) em ${clawfastEnvPath()} ou como vari\xE1vel de ambiente.`
|
|
73387
73882
|
);
|
|
73388
73883
|
process.exit(1);
|
|
73389
73884
|
}
|