clawfast 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clawfast.cjs +462 -67
- 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.0" : 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
|
|
|
@@ -70372,6 +70433,9 @@ var init_local_sandbox = __esm({
|
|
|
70372
70433
|
["recon_ports.py", true],
|
|
70373
70434
|
["recon_content.py", true],
|
|
70374
70435
|
["recon_intel.py", true],
|
|
70436
|
+
["recon_routes.py", true],
|
|
70437
|
+
["exploit_engine.py", true],
|
|
70438
|
+
["harden.py", true],
|
|
70375
70439
|
["console_recon.js", true],
|
|
70376
70440
|
["README.md", true],
|
|
70377
70441
|
["scope.txt", false]
|
|
@@ -71506,6 +71570,7 @@ async function createAgent() {
|
|
|
71506
71570
|
system += pythonOnlyPolicy();
|
|
71507
71571
|
system += deepReconPolicy(sandbox.getWorkdir());
|
|
71508
71572
|
system += reconPhasesPolicy(sandbox.getWorkdir());
|
|
71573
|
+
system += attackChainPolicy(sandbox.getWorkdir());
|
|
71509
71574
|
system += httpAndFindingsPolicy(sandbox.getWorkdir());
|
|
71510
71575
|
system += buildCliNotesSection();
|
|
71511
71576
|
system += buildSkillsIndexSection();
|
|
@@ -71603,6 +71668,33 @@ async function createAgent() {
|
|
|
71603
71668
|
selection: getModelSelection()
|
|
71604
71669
|
};
|
|
71605
71670
|
}
|
|
71671
|
+
if (isOpenRouterDynamicKey(raw)) {
|
|
71672
|
+
if (!hasOpenRouterKey()) {
|
|
71673
|
+
return {
|
|
71674
|
+
ok: false,
|
|
71675
|
+
message: "OpenRouter indispon\xEDvel: defina OPENROUTER_API_KEY em .env.local (https://openrouter.ai/keys)",
|
|
71676
|
+
selection: getModelSelection()
|
|
71677
|
+
};
|
|
71678
|
+
}
|
|
71679
|
+
const slug = openRouterSlugFromKey(raw).trim();
|
|
71680
|
+
if (!slug) {
|
|
71681
|
+
return {
|
|
71682
|
+
ok: false,
|
|
71683
|
+
message: "informe o modelo OpenRouter (ex.: openrouter:openai/gpt-4o)",
|
|
71684
|
+
selection: getModelSelection()
|
|
71685
|
+
};
|
|
71686
|
+
}
|
|
71687
|
+
const key = openRouterKeyForSlug(slug);
|
|
71688
|
+
selectedModelKey = key;
|
|
71689
|
+
currentModelName = key;
|
|
71690
|
+
context2.modelName = key;
|
|
71691
|
+
await rebuildSystemPrompt(key);
|
|
71692
|
+
return {
|
|
71693
|
+
ok: true,
|
|
71694
|
+
message: `modelo fixado: ${labelFor(key)}`,
|
|
71695
|
+
selection: getModelSelection()
|
|
71696
|
+
};
|
|
71697
|
+
}
|
|
71606
71698
|
const choices = getModelChoices();
|
|
71607
71699
|
let match;
|
|
71608
71700
|
if (/^\d+$/.test(normalized)) {
|
|
@@ -71712,6 +71804,28 @@ ${seen}`);
|
|
|
71712
71804
|
}
|
|
71713
71805
|
return sections.join("\n\n");
|
|
71714
71806
|
}
|
|
71807
|
+
let lastKimiChatId = null;
|
|
71808
|
+
async function reportKimiSession() {
|
|
71809
|
+
const base = process.env.KIMI_BASE_URL?.trim();
|
|
71810
|
+
if (!base) return;
|
|
71811
|
+
try {
|
|
71812
|
+
const url2 = `${base.replace(/\/+$/, "")}/session`;
|
|
71813
|
+
const controller = new AbortController();
|
|
71814
|
+
const timer2 = setTimeout(() => controller.abort(), 1500);
|
|
71815
|
+
const res = await fetch(url2, { signal: controller.signal }).finally(
|
|
71816
|
+
() => clearTimeout(timer2)
|
|
71817
|
+
);
|
|
71818
|
+
if (!res.ok) return;
|
|
71819
|
+
const info = await res.json();
|
|
71820
|
+
if (!info?.active || !info.chatId) return;
|
|
71821
|
+
const isNew = info.chatId !== lastKimiChatId;
|
|
71822
|
+
lastKimiChatId = info.chatId;
|
|
71823
|
+
render.info(
|
|
71824
|
+
`\u25B8 kimi: ${isNew ? "conversa \xFAnica ativa" : "mesma conversa"} (turno ${info.turns ?? "?"}) \u2014 ${info.url ?? info.chatId}`
|
|
71825
|
+
);
|
|
71826
|
+
} catch {
|
|
71827
|
+
}
|
|
71828
|
+
}
|
|
71715
71829
|
async function send(userInput, signal) {
|
|
71716
71830
|
history.push({ role: "user", content: userInput });
|
|
71717
71831
|
if (!auditMode && detectProjectAuditIntent(userInput)) {
|
|
@@ -71809,7 +71923,7 @@ ${segs.join(
|
|
|
71809
71923
|
};
|
|
71810
71924
|
try {
|
|
71811
71925
|
const result = streamText({
|
|
71812
|
-
model:
|
|
71926
|
+
model: resolveLanguageModel2(modelKey),
|
|
71813
71927
|
system: turnSystem,
|
|
71814
71928
|
messages: history,
|
|
71815
71929
|
tools,
|
|
@@ -71944,6 +72058,9 @@ ${resultText}`
|
|
|
71944
72058
|
`\u25B8 fim do turno: ${finishReason}` + (autoContinues >= MAX_AUTO_CONTINUES ? " (limite de retomadas autom\xE1ticas atingido)" : "")
|
|
71945
72059
|
);
|
|
71946
72060
|
}
|
|
72061
|
+
if (modelKey === PROXY_MODEL_KEYS.kimi) {
|
|
72062
|
+
await reportKimiSession();
|
|
72063
|
+
}
|
|
71947
72064
|
render.endTurn();
|
|
71948
72065
|
return;
|
|
71949
72066
|
} catch (err) {
|
|
@@ -72056,10 +72173,12 @@ ${resultText}`
|
|
|
72056
72173
|
getModelChoices,
|
|
72057
72174
|
getModelSelection,
|
|
72058
72175
|
setModelSelection,
|
|
72176
|
+
isOpenRouterAvailable: hasOpenRouterKey,
|
|
72177
|
+
listOpenRouterModels: (signal) => listOpenRouterModels(signal),
|
|
72059
72178
|
close
|
|
72060
72179
|
};
|
|
72061
72180
|
}
|
|
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;
|
|
72181
|
+
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, attackChainPolicy, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
|
|
72063
72182
|
var init_agent = __esm({
|
|
72064
72183
|
"src/agent.ts"() {
|
|
72065
72184
|
"use strict";
|
|
@@ -72150,6 +72269,7 @@ Regras:
|
|
|
72150
72269
|
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
72270
|
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
72271
|
6. Quando a tarefa estiver conclu\xEDda, responda em texto normal SEM bloco json.
|
|
72272
|
+
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
72273
|
</web_session_tool_protocol>`;
|
|
72154
72274
|
SYSTEM_PROMPT_SOURCE = "lib/system-prompt.ts";
|
|
72155
72275
|
REQUIRED_SYSTEM_PROMPT_MARKERS = [
|
|
@@ -72177,7 +72297,7 @@ Regras:
|
|
|
72177
72297
|
"fallback-openai-chat-latest": "OpenAI - chat-latest",
|
|
72178
72298
|
"model-nvidia-nemotron": "NVIDIA - nemotron-3-ultra-550b"
|
|
72179
72299
|
};
|
|
72180
|
-
labelFor = (key) => MODEL_LABELS[key] ?? key;
|
|
72300
|
+
labelFor = (key) => isOpenRouterDynamicKey(key) ? `OpenRouter - ${openRouterSlugFromKey(key)}` : MODEL_LABELS[key] ?? key;
|
|
72181
72301
|
loginRequiredHint = (msg) => {
|
|
72182
72302
|
if (/account is suspended|violation of user policies|account has been suspended/i.test(
|
|
72183
72303
|
msg
|
|
@@ -72283,6 +72403,26 @@ Beyond recon/recon_deep.py (HTTP/JS/secret/browser recon), the workspace at ${wo
|
|
|
72283
72403
|
|
|
72284
72404
|
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
72405
|
</recon_phases>`;
|
|
72406
|
+
attackChainPolicy = (workdir) => `
|
|
72407
|
+
|
|
72408
|
+
<attack_and_remediation_chain>
|
|
72409
|
+
ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
|
|
72410
|
+
|
|
72411
|
+
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).
|
|
72412
|
+
|
|
72413
|
+
1. MAP THE ROUTES (saber as rotas): \`python recon/recon_routes.py https://alvo.com --wayback\`
|
|
72414
|
+
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.
|
|
72415
|
+
|
|
72416
|
+
2. ATTACK & PROVE (o ataque): \`python recon/exploit_engine.py --routes reports/routes_<host>.json\`
|
|
72417
|
+
(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.
|
|
72418
|
+
|
|
72419
|
+
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.
|
|
72420
|
+
|
|
72421
|
+
4. CLOSE THE BREACH (fechar a brecha para n\xE3o ter acesso de novo): \`python recon/harden.py --report reports/exploit_<host>.json\`
|
|
72422
|
+
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.
|
|
72423
|
+
|
|
72424
|
+
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.
|
|
72425
|
+
</attack_and_remediation_chain>`;
|
|
72286
72426
|
skillsScopePolicy = () => `
|
|
72287
72427
|
|
|
72288
72428
|
<skills_scope>
|
|
@@ -72502,6 +72642,94 @@ var init_interactive_input = __esm({
|
|
|
72502
72642
|
};
|
|
72503
72643
|
});
|
|
72504
72644
|
}
|
|
72645
|
+
/**
|
|
72646
|
+
* Searchable single-choice list: a live filter bar on top, a scrolling
|
|
72647
|
+
* viewport of matches below. Type to filter, ↑/↓ to move (wraps), Enter to
|
|
72648
|
+
* pick, Esc / Ctrl+C to cancel. Built for big catalogs (e.g. every OpenRouter
|
|
72649
|
+
* model). Returns the chosen item's ORIGINAL index, or null on cancel.
|
|
72650
|
+
*/
|
|
72651
|
+
searchSelect(title, items, opts = {}) {
|
|
72652
|
+
const pageSize = Math.max(4, opts.pageSize ?? 10);
|
|
72653
|
+
const placeholder = opts.placeholder ?? "digite para filtrar";
|
|
72654
|
+
let query = "";
|
|
72655
|
+
let sel = 0;
|
|
72656
|
+
let top = 0;
|
|
72657
|
+
this.lastRows = 0;
|
|
72658
|
+
out2("\n");
|
|
72659
|
+
const filtered = () => {
|
|
72660
|
+
const q = query.toLowerCase().trim();
|
|
72661
|
+
const all = items.map((it, i) => ({ it, i }));
|
|
72662
|
+
if (!q) return all;
|
|
72663
|
+
return all.filter(
|
|
72664
|
+
({ it }) => it.label.toLowerCase().includes(q) || (it.hint ?? "").toLowerCase().includes(q)
|
|
72665
|
+
);
|
|
72666
|
+
};
|
|
72667
|
+
const draw = () => {
|
|
72668
|
+
const list = filtered();
|
|
72669
|
+
if (sel >= list.length) sel = Math.max(0, list.length - 1);
|
|
72670
|
+
if (sel < top) top = sel;
|
|
72671
|
+
if (sel >= top + pageSize) top = sel - pageSize + 1;
|
|
72672
|
+
this.renderSearchList(
|
|
72673
|
+
title,
|
|
72674
|
+
placeholder,
|
|
72675
|
+
query,
|
|
72676
|
+
list,
|
|
72677
|
+
sel,
|
|
72678
|
+
top,
|
|
72679
|
+
pageSize,
|
|
72680
|
+
items.length
|
|
72681
|
+
);
|
|
72682
|
+
};
|
|
72683
|
+
draw();
|
|
72684
|
+
return new Promise((resolve2) => {
|
|
72685
|
+
const finish = (value) => {
|
|
72686
|
+
this.collapseTo(this.lastRows);
|
|
72687
|
+
this.onKey = null;
|
|
72688
|
+
resolve2(value);
|
|
72689
|
+
};
|
|
72690
|
+
this.onKey = (str, key) => {
|
|
72691
|
+
if (key.ctrl && key.name === "c") return finish(null);
|
|
72692
|
+
const list = filtered();
|
|
72693
|
+
switch (key.name) {
|
|
72694
|
+
case "escape":
|
|
72695
|
+
return finish(null);
|
|
72696
|
+
case "return":
|
|
72697
|
+
if (list.length === 0) return;
|
|
72698
|
+
return finish(list[sel].i);
|
|
72699
|
+
case "up":
|
|
72700
|
+
if (list.length) sel = (sel - 1 + list.length) % list.length;
|
|
72701
|
+
draw();
|
|
72702
|
+
return;
|
|
72703
|
+
case "down":
|
|
72704
|
+
case "tab":
|
|
72705
|
+
if (list.length) sel = (sel + 1) % list.length;
|
|
72706
|
+
draw();
|
|
72707
|
+
return;
|
|
72708
|
+
case "backspace":
|
|
72709
|
+
if (query.length) {
|
|
72710
|
+
query = query.slice(0, -1);
|
|
72711
|
+
sel = 0;
|
|
72712
|
+
top = 0;
|
|
72713
|
+
}
|
|
72714
|
+
draw();
|
|
72715
|
+
return;
|
|
72716
|
+
}
|
|
72717
|
+
if (key.ctrl && key.name === "u") {
|
|
72718
|
+
query = "";
|
|
72719
|
+
sel = 0;
|
|
72720
|
+
top = 0;
|
|
72721
|
+
draw();
|
|
72722
|
+
return;
|
|
72723
|
+
}
|
|
72724
|
+
if (str && !key.ctrl && !key.meta && str.charCodeAt(0) >= 32) {
|
|
72725
|
+
query += str;
|
|
72726
|
+
sel = 0;
|
|
72727
|
+
top = 0;
|
|
72728
|
+
draw();
|
|
72729
|
+
}
|
|
72730
|
+
};
|
|
72731
|
+
});
|
|
72732
|
+
}
|
|
72505
72733
|
/**
|
|
72506
72734
|
* Lightweight line reader used WHILE a turn is running, so typed lines can be
|
|
72507
72735
|
* forwarded to an interactive command's stdin. No box/dropdown — just echoes
|
|
@@ -72722,12 +72950,12 @@ var init_interactive_input = __esm({
|
|
|
72722
72950
|
const prefix = frame2("\u2570\u2500") + gradient("\u276F", { bold: true }) + " ";
|
|
72723
72951
|
const prefixLen = 4;
|
|
72724
72952
|
const avail = Math.max(8, width - prefixLen - 1);
|
|
72725
|
-
|
|
72726
|
-
|
|
72727
|
-
|
|
72728
|
-
|
|
72729
|
-
|
|
72730
|
-
const lines = [top, idLine,
|
|
72953
|
+
const { textLines, caretLine, caretCol } = this.wrap(
|
|
72954
|
+
prefix,
|
|
72955
|
+
prefixLen,
|
|
72956
|
+
avail
|
|
72957
|
+
);
|
|
72958
|
+
const lines = [top, idLine, ...textLines];
|
|
72731
72959
|
const items = this.dropdownItems();
|
|
72732
72960
|
if (items.length > 0) {
|
|
72733
72961
|
this.ddSel = Math.min(this.ddSel, items.length - 1);
|
|
@@ -72752,9 +72980,27 @@ var init_interactive_input = __esm({
|
|
|
72752
72980
|
} else {
|
|
72753
72981
|
this.ddSel = 0;
|
|
72754
72982
|
}
|
|
72755
|
-
this.inputLineIndex = 2;
|
|
72983
|
+
this.inputLineIndex = 2 + caretLine;
|
|
72756
72984
|
this.paint(lines, caretCol);
|
|
72757
72985
|
}
|
|
72986
|
+
/**
|
|
72987
|
+
* Split the buffer into terminal-width rows. The first row carries `prefix`
|
|
72988
|
+
* (e.g. "❯ "); continuation rows are indented by `prefixLen` spaces so the
|
|
72989
|
+
* text columns line up. Also returns which wrapped row the caret sits on and
|
|
72990
|
+
* its absolute column, so paint() can place the cursor correctly.
|
|
72991
|
+
*/
|
|
72992
|
+
wrap(prefix, prefixLen, avail) {
|
|
72993
|
+
const indent = " ".repeat(prefixLen);
|
|
72994
|
+
const caretLine = Math.floor(this.cursor / avail);
|
|
72995
|
+
const caretColInLine = this.cursor - caretLine * avail;
|
|
72996
|
+
const chunks = [];
|
|
72997
|
+
for (let i = 0; i < this.buffer.length; i += avail) {
|
|
72998
|
+
chunks.push(this.buffer.slice(i, i + avail));
|
|
72999
|
+
}
|
|
73000
|
+
while (chunks.length <= caretLine) chunks.push("");
|
|
73001
|
+
const textLines = chunks.map((c, i) => (i === 0 ? prefix : indent) + c);
|
|
73002
|
+
return { textLines, caretLine, caretCol: prefixLen + caretColInLine };
|
|
73003
|
+
}
|
|
72758
73004
|
/** Compact single-line labelled prompt ("nome ❯ …") for step-by-step capture. */
|
|
72759
73005
|
renderCompact() {
|
|
72760
73006
|
const width = cols();
|
|
@@ -72762,12 +73008,13 @@ var init_interactive_input = __esm({
|
|
|
72762
73008
|
const prefix = `${gradient(label, { bold: true })} ${gradient("\u276F", { bold: true })} `;
|
|
72763
73009
|
const prefixLen = vlen(`${label} \u276F `);
|
|
72764
73010
|
const avail = Math.max(8, width - prefixLen - 1);
|
|
72765
|
-
|
|
72766
|
-
|
|
72767
|
-
|
|
72768
|
-
|
|
72769
|
-
|
|
72770
|
-
this.
|
|
73011
|
+
const { textLines, caretLine, caretCol } = this.wrap(
|
|
73012
|
+
prefix,
|
|
73013
|
+
prefixLen,
|
|
73014
|
+
avail
|
|
73015
|
+
);
|
|
73016
|
+
this.inputLineIndex = caretLine;
|
|
73017
|
+
this.paint(textLines, caretCol);
|
|
72771
73018
|
}
|
|
72772
73019
|
/** Repaint the whole block, then place the cursor on the active input line. */
|
|
72773
73020
|
paint(lines, caretCol) {
|
|
@@ -72812,6 +73059,42 @@ var init_interactive_input = __esm({
|
|
|
72812
73059
|
);
|
|
72813
73060
|
this.paintBlock(lines);
|
|
72814
73061
|
}
|
|
73062
|
+
/** Render the search bar + a scrolling viewport of filtered matches. */
|
|
73063
|
+
renderSearchList(title, placeholder, query, list, sel, top, pageSize, total) {
|
|
73064
|
+
const width = cols();
|
|
73065
|
+
const inner = Math.min(Math.max(40, width - 4), 80);
|
|
73066
|
+
const lines = [];
|
|
73067
|
+
lines.push(`${frame2("\u256D\u2500\u25C6 ")}${gradient(title, { bold: true })}`);
|
|
73068
|
+
const shown = query.length ? query : `${C4.dim}${placeholder}${C4.reset}`;
|
|
73069
|
+
const counter = query.trim() ? `${C4.dim}${list.length}/${total}${C4.reset}` : `${C4.dim}${total}${C4.reset}`;
|
|
73070
|
+
lines.push(
|
|
73071
|
+
`${frame2("\u2502 ")}${gradient("\u2315", { bold: true })} ${shown}${C4.reset} ${counter}`
|
|
73072
|
+
);
|
|
73073
|
+
lines.push(frame2("\u2502"));
|
|
73074
|
+
if (list.length === 0) {
|
|
73075
|
+
lines.push(`${frame2("\u2502 ")}${C4.dim}nenhum modelo corresponde${C4.reset}`);
|
|
73076
|
+
} else {
|
|
73077
|
+
const end = Math.min(top + pageSize, list.length);
|
|
73078
|
+
for (let i = top; i < end; i++) {
|
|
73079
|
+
const active2 = i === sel;
|
|
73080
|
+
const { it } = list[i];
|
|
73081
|
+
const marker25 = active2 ? gradient("\u276F ", { bold: true }) : `${C4.dim} `;
|
|
73082
|
+
const text2 = active2 ? gradient(it.label, { bold: true }) : `${C4.reset}${it.label}`;
|
|
73083
|
+
const labelW = vlen(it.label);
|
|
73084
|
+
const hint = it.hint ? ` ${C4.dim}${truncate4(it.hint, Math.max(6, inner - labelW - 6))}${C4.reset}` : "";
|
|
73085
|
+
lines.push(`${frame2("\u2502 ")}${C4.reset}${marker25}${text2}${C4.reset}${hint}`);
|
|
73086
|
+
}
|
|
73087
|
+
if (list.length > pageSize) {
|
|
73088
|
+
lines.push(
|
|
73089
|
+
`${frame2("\u2502 ")}${C4.dim}${sel + 1}/${list.length}${C4.reset}`
|
|
73090
|
+
);
|
|
73091
|
+
}
|
|
73092
|
+
}
|
|
73093
|
+
lines.push(
|
|
73094
|
+
`${frame2("\u2570\u2500")} ${C4.dim}digite filtra \xB7 \u2191\u2193 navega \xB7 Enter seleciona \xB7 Esc cancela${C4.reset}`
|
|
73095
|
+
);
|
|
73096
|
+
this.paintBlock(lines);
|
|
73097
|
+
}
|
|
72815
73098
|
/** Repaint a block and leave the cursor just below it (for list/menu modes). */
|
|
72816
73099
|
paintBlock(lines) {
|
|
72817
73100
|
let s = "";
|
|
@@ -72869,8 +73152,8 @@ async function main() {
|
|
|
72869
73152
|
`
|
|
72870
73153
|
);
|
|
72871
73154
|
const COMMANDS = [
|
|
72872
|
-
{ name: "/model", desc: "trocar o modelo (
|
|
72873
|
-
{ name: "/api", desc: "trocar
|
|
73155
|
+
{ name: "/model", desc: "trocar o modelo (setas; OpenRouter c/ busca)" },
|
|
73156
|
+
{ name: "/api", desc: "trocar chave de API (NVIDIA / OpenRouter)" },
|
|
72874
73157
|
{ name: "/skills", desc: "listar as skills instaladas" },
|
|
72875
73158
|
{ name: "/skillcreator", desc: "criar uma nova skill" },
|
|
72876
73159
|
{ name: "/system", desc: "salvar o system prompt (HTML) na \xC1rea de Trabalho" },
|
|
@@ -73123,69 +73406,66 @@ ${C5.dim}ja disponivel para todos os modelos nesta sessao.${C5.reset}
|
|
|
73123
73406
|
`
|
|
73124
73407
|
);
|
|
73125
73408
|
};
|
|
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);
|
|
73409
|
+
const KEY_PROVIDERS = [
|
|
73410
|
+
{
|
|
73411
|
+
id: "nvidia",
|
|
73412
|
+
name: "NVIDIA",
|
|
73413
|
+
envVar: "NVIDIA_API_KEY",
|
|
73414
|
+
createUrl: "https://build.nvidia.com/",
|
|
73415
|
+
keyRegex: /nvapi-[A-Za-z0-9_-]+/,
|
|
73416
|
+
keyHint: "nvapi-\u2026",
|
|
73417
|
+
test: testNvidiaKey,
|
|
73418
|
+
swap: swapNvidiaKey
|
|
73419
|
+
},
|
|
73420
|
+
{
|
|
73421
|
+
id: "openrouter",
|
|
73422
|
+
name: "OpenRouter",
|
|
73423
|
+
envVar: "OPENROUTER_API_KEY",
|
|
73424
|
+
createUrl: "https://openrouter.ai/keys",
|
|
73425
|
+
keyRegex: /sk-or-[A-Za-z0-9_-]+/,
|
|
73426
|
+
keyHint: "sk-or-\u2026",
|
|
73427
|
+
test: testOpenRouterKey,
|
|
73428
|
+
swap: swapOpenRouterKey
|
|
73150
73429
|
}
|
|
73151
|
-
|
|
73152
|
-
const
|
|
73430
|
+
];
|
|
73431
|
+
const openRouterProvider = KEY_PROVIDERS.find((p) => p.id === "openrouter");
|
|
73432
|
+
const promptAndSwapKey = async (provider, inlineKey = "") => {
|
|
73153
73433
|
let raw = inlineKey;
|
|
73154
73434
|
if (!raw) {
|
|
73155
73435
|
process.stdout.write(
|
|
73156
|
-
`${C5.dim}Cole a nova chave
|
|
73436
|
+
`${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
73437
|
`
|
|
73158
73438
|
);
|
|
73159
73439
|
const res = await inputUI.prompt({
|
|
73160
|
-
label:
|
|
73440
|
+
label: `nova ${provider.name} API key`,
|
|
73161
73441
|
secret: true
|
|
73162
73442
|
});
|
|
73163
73443
|
if (res.type !== "line" || !res.value.trim()) {
|
|
73164
73444
|
process.stdout.write(`${C5.dim}troca de chave cancelada${C5.reset}
|
|
73165
73445
|
`);
|
|
73166
|
-
return;
|
|
73446
|
+
return false;
|
|
73167
73447
|
}
|
|
73168
73448
|
raw = res.value;
|
|
73169
73449
|
}
|
|
73170
|
-
const key = raw.replace(/\s+/g, "").match(
|
|
73450
|
+
const key = raw.replace(/\s+/g, "").match(provider.keyRegex)?.[0] ?? "";
|
|
73171
73451
|
if (!key) {
|
|
73172
73452
|
process.stdout.write(
|
|
73173
|
-
`${C5.red}\u2717 n\xE3o encontrei uma chave
|
|
73453
|
+
`${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
73454
|
`
|
|
73175
73455
|
);
|
|
73176
|
-
return;
|
|
73456
|
+
return false;
|
|
73177
73457
|
}
|
|
73178
|
-
process.stdout.write(`${C5.dim}testando a chave na
|
|
73458
|
+
process.stdout.write(`${C5.dim}testando a chave na ${provider.name}\u2026${C5.reset}
|
|
73179
73459
|
`);
|
|
73180
|
-
const test = await
|
|
73460
|
+
const test = await provider.test(key);
|
|
73181
73461
|
if (!test.ok) {
|
|
73182
73462
|
if (test.status === 401 || test.status === 403) {
|
|
73183
73463
|
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
|
|
73464
|
+
`${C5.red}\u2717 a ${provider.name} recusou a chave (${test.status}): ${test.detail}${C5.reset}
|
|
73465
|
+
${C5.dim}chave N\xC3O salva \u2014 confira se copiou inteira e se a conta tem acesso.${C5.reset}
|
|
73186
73466
|
`
|
|
73187
73467
|
);
|
|
73188
|
-
return;
|
|
73468
|
+
return false;
|
|
73189
73469
|
}
|
|
73190
73470
|
process.stdout.write(
|
|
73191
73471
|
`${C5.yellow}\u26A0 n\xE3o consegui validar (${test.status || "rede"}): ${test.detail}${C5.reset}
|
|
@@ -73193,13 +73473,123 @@ ${C5.dim}salvando mesmo assim \u2014 o pr\xF3ximo pedido vai usar a chave nova.$
|
|
|
73193
73473
|
`
|
|
73194
73474
|
);
|
|
73195
73475
|
}
|
|
73196
|
-
const file2 =
|
|
73476
|
+
const file2 = provider.swap(key);
|
|
73197
73477
|
const masked = `${key.slice(0, 9)}\u2026${key.slice(-4)} (${key.length} chars)`;
|
|
73198
73478
|
process.stdout.write(
|
|
73199
|
-
`${C5.green}\u2713 chave
|
|
73479
|
+
`${C5.green}\u2713 chave ${provider.name} trocada e ativa${C5.reset} ${C5.dim}${masked}${C5.reset}
|
|
73200
73480
|
${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
|
|
73201
73481
|
`
|
|
73202
73482
|
);
|
|
73483
|
+
return true;
|
|
73484
|
+
};
|
|
73485
|
+
const openOpenRouterPicker = async () => {
|
|
73486
|
+
if (!agent.isOpenRouterAvailable()) {
|
|
73487
|
+
process.stdout.write(
|
|
73488
|
+
`${C5.dim}OpenRouter ainda sem chave. Vamos adicionar uma.${C5.reset}
|
|
73489
|
+
`
|
|
73490
|
+
);
|
|
73491
|
+
const ok = await promptAndSwapKey(openRouterProvider);
|
|
73492
|
+
if (!ok) return;
|
|
73493
|
+
}
|
|
73494
|
+
process.stdout.write(`${C5.dim}buscando cat\xE1logo OpenRouter\u2026${C5.reset}
|
|
73495
|
+
`);
|
|
73496
|
+
let models;
|
|
73497
|
+
try {
|
|
73498
|
+
models = await agent.listOpenRouterModels();
|
|
73499
|
+
} catch (err) {
|
|
73500
|
+
process.stdout.write(
|
|
73501
|
+
`${C5.red}\u2717 falha ao listar modelos OpenRouter: ${err instanceof Error ? err.message : String(err)}${C5.reset}
|
|
73502
|
+
`
|
|
73503
|
+
);
|
|
73504
|
+
return;
|
|
73505
|
+
}
|
|
73506
|
+
if (models.length === 0) {
|
|
73507
|
+
process.stdout.write(
|
|
73508
|
+
`${C5.yellow}\u26A0 a OpenRouter n\xE3o retornou nenhum modelo${C5.reset}
|
|
73509
|
+
`
|
|
73510
|
+
);
|
|
73511
|
+
return;
|
|
73512
|
+
}
|
|
73513
|
+
const items = models.map((m) => ({
|
|
73514
|
+
label: m.name,
|
|
73515
|
+
hint: `${m.id}${m.promptPrice === "0" ? " \xB7 free" : ""}`
|
|
73516
|
+
}));
|
|
73517
|
+
const choice2 = await inputUI.searchSelect(
|
|
73518
|
+
"OpenRouter \u2014 buscar modelo",
|
|
73519
|
+
items,
|
|
73520
|
+
{ placeholder: "digite para filtrar (nome ou slug)" }
|
|
73521
|
+
);
|
|
73522
|
+
if (choice2 === null) {
|
|
73523
|
+
process.stdout.write(`${C5.dim}sele\xE7\xE3o de modelo cancelada${C5.reset}
|
|
73524
|
+
`);
|
|
73525
|
+
return;
|
|
73526
|
+
}
|
|
73527
|
+
try {
|
|
73528
|
+
printModelResult(
|
|
73529
|
+
await agent.setModelSelection(`openrouter:${models[choice2].id}`)
|
|
73530
|
+
);
|
|
73531
|
+
} catch (err) {
|
|
73532
|
+
printFatal(err);
|
|
73533
|
+
}
|
|
73534
|
+
};
|
|
73535
|
+
const openModelSelector = async () => {
|
|
73536
|
+
const state = agent.getModelSelection();
|
|
73537
|
+
const orAvailable = agent.isOpenRouterAvailable();
|
|
73538
|
+
const items = [
|
|
73539
|
+
{
|
|
73540
|
+
label: "Auto \u2014 cadeia de fallback autom\xE1tica",
|
|
73541
|
+
hint: "tenta os modelos em ordem"
|
|
73542
|
+
},
|
|
73543
|
+
...state.chain.map((c) => ({ label: c.label, hint: c.key }))
|
|
73544
|
+
];
|
|
73545
|
+
const openRouterIdx = items.length;
|
|
73546
|
+
items.push({
|
|
73547
|
+
label: "OpenRouter \u2014 buscar no cat\xE1logo completo",
|
|
73548
|
+
hint: orAvailable ? "lista todos os modelos da sua conta" : "pede a API key e lista o cat\xE1logo"
|
|
73549
|
+
});
|
|
73550
|
+
const activeIsOpenRouter = state.activeModelKey.startsWith("openrouter:");
|
|
73551
|
+
const activeIdx = state.mode === "auto" ? 0 : activeIsOpenRouter ? openRouterIdx : Math.max(
|
|
73552
|
+
0,
|
|
73553
|
+
1 + state.chain.findIndex((c) => c.key === state.activeModelKey)
|
|
73554
|
+
);
|
|
73555
|
+
const choice2 = await inputUI.select("selecionar modelo", items, activeIdx);
|
|
73556
|
+
if (choice2 === null) {
|
|
73557
|
+
process.stdout.write(`${C5.dim}sele\xE7\xE3o de modelo cancelada${C5.reset}
|
|
73558
|
+
`);
|
|
73559
|
+
return;
|
|
73560
|
+
}
|
|
73561
|
+
if (choice2 === openRouterIdx) {
|
|
73562
|
+
await openOpenRouterPicker();
|
|
73563
|
+
return;
|
|
73564
|
+
}
|
|
73565
|
+
const arg = choice2 === 0 ? "auto" : state.chain[choice2 - 1].key;
|
|
73566
|
+
try {
|
|
73567
|
+
printModelResult(await agent.setModelSelection(arg));
|
|
73568
|
+
} catch (err) {
|
|
73569
|
+
printFatal(err);
|
|
73570
|
+
}
|
|
73571
|
+
};
|
|
73572
|
+
const handleApiSwap = async (inlineKey) => {
|
|
73573
|
+
if (inlineKey) {
|
|
73574
|
+
const stripped = inlineKey.replace(/\s+/g, "");
|
|
73575
|
+
const provider = KEY_PROVIDERS.find((p) => p.keyRegex.test(stripped)) ?? KEY_PROVIDERS[0];
|
|
73576
|
+
await promptAndSwapKey(provider, inlineKey);
|
|
73577
|
+
return;
|
|
73578
|
+
}
|
|
73579
|
+
const idx = await inputUI.select(
|
|
73580
|
+
"trocar chave de API",
|
|
73581
|
+
KEY_PROVIDERS.map((p) => ({
|
|
73582
|
+
label: p.name,
|
|
73583
|
+
hint: process.env[p.envVar]?.trim() ? `${p.envVar} (configurada)` : p.envVar
|
|
73584
|
+
})),
|
|
73585
|
+
0
|
|
73586
|
+
);
|
|
73587
|
+
if (idx === null) {
|
|
73588
|
+
process.stdout.write(`${C5.dim}troca de chave cancelada${C5.reset}
|
|
73589
|
+
`);
|
|
73590
|
+
return;
|
|
73591
|
+
}
|
|
73592
|
+
await promptAndSwapKey(KEY_PROVIDERS[idx]);
|
|
73203
73593
|
};
|
|
73204
73594
|
const handleLine = async (line) => {
|
|
73205
73595
|
if (closing) return;
|
|
@@ -73226,6 +73616,10 @@ ${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
|
|
|
73226
73616
|
}
|
|
73227
73617
|
if (input.startsWith("/model ") || input.startsWith("/models ")) {
|
|
73228
73618
|
const arg = input.replace(/^\/models?\s*/, "");
|
|
73619
|
+
if (arg.toLowerCase() === "openrouter" || arg.toLowerCase() === "or") {
|
|
73620
|
+
await openOpenRouterPicker();
|
|
73621
|
+
return;
|
|
73622
|
+
}
|
|
73229
73623
|
try {
|
|
73230
73624
|
printModelResult(await agent.setModelSelection(arg));
|
|
73231
73625
|
} catch (err) {
|
|
@@ -73379,11 +73773,12 @@ var init_index = __esm({
|
|
|
73379
73773
|
configuredModelProviders = [
|
|
73380
73774
|
deepseekEnabled ? "DeepSeek (proxy/web session)" : null,
|
|
73381
73775
|
process.env.NVIDIA_API_KEY?.trim() ? "NVIDIA build" : null,
|
|
73382
|
-
process.env.OPENAI_API_KEY?.trim() ? "OpenAI" : null
|
|
73776
|
+
process.env.OPENAI_API_KEY?.trim() ? "OpenAI" : null,
|
|
73777
|
+
process.env.OPENROUTER_API_KEY?.trim() ? "OpenRouter" : null
|
|
73383
73778
|
].filter((name25) => Boolean(name25));
|
|
73384
73779
|
if (configuredModelProviders.length === 0) {
|
|
73385
73780
|
console.error(
|
|
73386
|
-
`Nenhuma chave de modelo configurada. Defina NVIDIA_API_KEY (ou OPENAI_API_KEY) em ${clawfastEnvPath()} ou como vari\xE1vel de ambiente.`
|
|
73781
|
+
`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
73782
|
);
|
|
73388
73783
|
process.exit(1);
|
|
73389
73784
|
}
|