clawfast 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/clawfast.cjs +1838 -448
  3. package/package.json +1 -1
package/dist/clawfast.cjs CHANGED
@@ -85,8 +85,8 @@ var init_boot_ui = __esm({
85
85
  var require_main = __commonJS({
86
86
  "../node_modules/.pnpm/dotenv@17.4.2/node_modules/dotenv/lib/main.js"(exports2, module2) {
87
87
  "use strict";
88
- var fs6 = require("fs");
89
- var path10 = require("path");
88
+ var fs8 = require("fs");
89
+ var path12 = require("path");
90
90
  var os7 = require("os");
91
91
  var crypto2 = require("crypto");
92
92
  var TIPS = [
@@ -217,7 +217,7 @@ var require_main = __commonJS({
217
217
  if (options && options.path && options.path.length > 0) {
218
218
  if (Array.isArray(options.path)) {
219
219
  for (const filepath of options.path) {
220
- if (fs6.existsSync(filepath)) {
220
+ if (fs8.existsSync(filepath)) {
221
221
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
222
222
  }
223
223
  }
@@ -225,15 +225,15 @@ var require_main = __commonJS({
225
225
  possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
226
226
  }
227
227
  } else {
228
- possibleVaultPath = path10.resolve(process.cwd(), ".env.vault");
228
+ possibleVaultPath = path12.resolve(process.cwd(), ".env.vault");
229
229
  }
230
- if (fs6.existsSync(possibleVaultPath)) {
230
+ if (fs8.existsSync(possibleVaultPath)) {
231
231
  return possibleVaultPath;
232
232
  }
233
233
  return null;
234
234
  }
235
235
  function _resolveHome(envPath) {
236
- return envPath[0] === "~" ? path10.join(os7.homedir(), envPath.slice(1)) : envPath;
236
+ return envPath[0] === "~" ? path12.join(os7.homedir(), envPath.slice(1)) : envPath;
237
237
  }
238
238
  function _configVault(options) {
239
239
  const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
@@ -250,7 +250,7 @@ var require_main = __commonJS({
250
250
  return { parsed };
251
251
  }
252
252
  function configDotenv(options) {
253
- const dotenvPath = path10.resolve(process.cwd(), ".env");
253
+ const dotenvPath = path12.resolve(process.cwd(), ".env");
254
254
  let encoding = "utf8";
255
255
  let processEnv = process.env;
256
256
  if (options && options.processEnv != null) {
@@ -278,13 +278,13 @@ var require_main = __commonJS({
278
278
  }
279
279
  let lastError;
280
280
  const parsedAll = {};
281
- for (const path11 of optionPaths) {
281
+ for (const path13 of optionPaths) {
282
282
  try {
283
- const parsed = DotenvModule.parse(fs6.readFileSync(path11, { encoding }));
283
+ const parsed = DotenvModule.parse(fs8.readFileSync(path13, { encoding }));
284
284
  DotenvModule.populate(parsedAll, parsed, options);
285
285
  } catch (e) {
286
286
  if (debug) {
287
- _debug(`failed to load ${path11} ${e.message}`);
287
+ _debug(`failed to load ${path13} ${e.message}`);
288
288
  }
289
289
  lastError = e;
290
290
  }
@@ -297,7 +297,7 @@ var require_main = __commonJS({
297
297
  const shortPaths = [];
298
298
  for (const filePath of optionPaths) {
299
299
  try {
300
- const relative2 = path10.relative(process.cwd(), filePath);
300
+ const relative2 = path12.relative(process.cwd(), filePath);
301
301
  shortPaths.push(relative2);
302
302
  } catch (e) {
303
303
  if (debug) {
@@ -426,6 +426,52 @@ ${line}
426
426
  }
427
427
  import_node_fs.default.writeFileSync(filePath, content, { encoding: "utf8", mode: 384 });
428
428
  }
429
+ function fileDefines(filePath, key) {
430
+ try {
431
+ const content = import_node_fs.default.readFileSync(filePath, "utf8");
432
+ return new RegExp(`^${key}=`, "m").test(content);
433
+ } catch {
434
+ return false;
435
+ }
436
+ }
437
+ function swapNvidiaKey(key) {
438
+ const local = import_node_path.default.resolve(process.cwd(), ".env.local");
439
+ const target = fileDefines(local, "NVIDIA_API_KEY") ? local : clawfastEnvPath();
440
+ setEnvVar(target, "NVIDIA_API_KEY", key);
441
+ process.env.NVIDIA_API_KEY = key;
442
+ return target;
443
+ }
444
+ async function testNvidiaKey(key) {
445
+ const baseURL = process.env.NVIDIA_BASE_URL || "https://integrate.api.nvidia.com/v1";
446
+ try {
447
+ const res = await fetch(`${baseURL.replace(/\/$/, "")}/chat/completions`, {
448
+ method: "POST",
449
+ headers: {
450
+ Authorization: `Bearer ${key}`,
451
+ "Content-Type": "application/json"
452
+ },
453
+ body: JSON.stringify({
454
+ model: "openai/gpt-oss-120b",
455
+ messages: [{ role: "user", content: "hi" }],
456
+ max_tokens: 1
457
+ })
458
+ });
459
+ if (res.ok) return { ok: true, status: res.status, detail: "OK" };
460
+ let detail = res.statusText || `HTTP ${res.status}`;
461
+ try {
462
+ const body = await res.json();
463
+ detail = body.detail || body.error?.message || body.title || detail;
464
+ } catch {
465
+ }
466
+ return { ok: false, status: res.status, detail };
467
+ } catch (err) {
468
+ return {
469
+ ok: false,
470
+ status: 0,
471
+ detail: err instanceof Error ? err.message : String(err)
472
+ };
473
+ }
474
+ }
429
475
  function promptLine(question) {
430
476
  const rl = import_node_readline.default.createInterface({
431
477
  input: process.stdin,
@@ -502,7 +548,7 @@ var clawfastVersion, isDevVersion, isNewerVersion;
502
548
  var init_version = __esm({
503
549
  "src/version.ts"() {
504
550
  "use strict";
505
- clawfastVersion = () => true ? "2.0.0" : "0.0.0-dev";
551
+ clawfastVersion = () => true ? "2.0.2" : "0.0.0-dev";
506
552
  isDevVersion = () => clawfastVersion().includes("-dev");
507
553
  isNewerVersion = (a, b) => {
508
554
  const parse3 = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -869,14 +915,140 @@ var init_skills = __esm({
869
915
  }
870
916
  });
871
917
 
918
+ // src/ui.ts
919
+ var ui_exports = {};
920
+ __export(ui_exports, {
921
+ C: () => C2,
922
+ agentHeader: () => agentHeader,
923
+ buildBanner: () => buildBanner,
924
+ shellPrompt: () => shellPrompt,
925
+ shortCwd: () => shortCwd,
926
+ ui: () => ui,
927
+ vlen: () => vlen
928
+ });
929
+ function shortCwd() {
930
+ const cwd = process.cwd();
931
+ const home = import_node_os2.default.homedir();
932
+ const rel = cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd;
933
+ const norm = rel.replace(/\\/g, "/");
934
+ if (norm.length <= 30) return norm;
935
+ const parts = norm.split("/");
936
+ return parts.length > 3 ? `${parts[0]}/\u2026/${parts[parts.length - 1]}` : norm.slice(-30);
937
+ }
938
+ function asciiTitle(text2) {
939
+ const rows = 6;
940
+ const out3 = [];
941
+ for (let r = 0; r < rows; r++) {
942
+ out3.push(
943
+ [...text2].map((ch) => GLYPHS[ch] ? GLYPHS[ch][r] : " ").join(" ")
944
+ );
945
+ }
946
+ return out3;
947
+ }
948
+ function buildBanner(_systemPromptChars, version3 = "1") {
949
+ const user = (import_node_os2.default.userInfo().username || "hacker").toLowerCase();
950
+ const host = (import_node_os2.default.hostname() || "localhost").split(".")[0].toLowerCase();
951
+ const art = asciiTitle("CLAWFAST");
952
+ const artW = vlen(art[0]);
953
+ const slogan = "Com o clawfast voc\xEA faz tudo";
954
+ const header = [
955
+ "",
956
+ ...art.map((line) => `${C2.greenB}${C2.bold}${line}${C2.reset}`),
957
+ "",
958
+ `${C2.cyanB}${C2.bold}${center(slogan, artW)}${C2.reset}`,
959
+ ""
960
+ ];
961
+ const info = [
962
+ { text: "" },
963
+ { text: `Bem-vindo de volta, ${user}!`, accent: true },
964
+ { text: "" },
965
+ { text: "clawfast usa a pasta SPRIT/ para criar" },
966
+ { text: "arquivos, scripts e tudo mais." },
967
+ { text: "" },
968
+ { text: `${user}@${host} \xB7 sem limites` },
969
+ { text: shortCwd() },
970
+ { text: "" }
971
+ ];
972
+ const title = `clawfast v${version3}`;
973
+ const topFill = "\u2500".repeat(Math.max(0, BOX_W - vlen(title) - 5));
974
+ const top = `${C2.green}\u256D\u2500 ${C2.greenB}${C2.bold}${title}${C2.reset}${C2.green} ${topFill}\u256E${C2.reset}`;
975
+ const bottom = `${C2.green}\u2570${"\u2500".repeat(BOX_W - 2)}\u256F${C2.reset}`;
976
+ const cardLines = info.map(({ text: text2, accent }) => {
977
+ const color = accent ? `${C2.greenB}${C2.bold}` : C2.green;
978
+ const body = `${color}${padEndV(text2, INNER)}${C2.reset}`;
979
+ return `${C2.green}\u2502 ${C2.reset}${body}${C2.green} \u2502${C2.reset}`;
980
+ });
981
+ const card = [top, ...cardLines, bottom];
982
+ return "\n" + [...header, ...card].join("\n") + "\n";
983
+ }
984
+ function shellPrompt() {
985
+ const user = (import_node_os2.default.userInfo().username || "hacker").toLowerCase();
986
+ const host = (import_node_os2.default.hostname() || "localhost").split(".")[0].toLowerCase();
987
+ const W = 52;
988
+ const label = " \u2709 mensagem ";
989
+ const topFill = "\u2500".repeat(Math.max(0, W - vlen(label) - 3));
990
+ const top = `${C2.green}\u256D\u2500${C2.greenB}${C2.bold}${label}${C2.reset}${C2.green}${topFill}\u256E${C2.reset}`;
991
+ const id = `${user}\u327F${host} \xB7 ${shortCwd()}`;
992
+ const mid = `${C2.green}\u2502 ${C2.dim}${id}${C2.reset}`;
993
+ const bottom = `${C2.green}\u2570\u2500${C2.greenB}${C2.bold}\u276F${C2.reset} `;
994
+ return `
995
+ ${top}
996
+ ${mid}
997
+ ${bottom}`;
998
+ }
999
+ function agentHeader() {
1000
+ return `${C2.magenta}\u256D\u2500 clawfast ${"\u2500".repeat(20)}${C2.reset}
1001
+ `;
1002
+ }
1003
+ var import_node_os2, ESC, C2, INNER, BOX_W, vlen, padEndV, center, GLYPHS, ui;
1004
+ var init_ui = __esm({
1005
+ "src/ui.ts"() {
1006
+ "use strict";
1007
+ import_node_os2 = __toESM(require("node:os"));
1008
+ ESC = "\x1B[";
1009
+ C2 = {
1010
+ reset: `${ESC}0m`,
1011
+ bold: `${ESC}1m`,
1012
+ dim: `${ESC}90m`,
1013
+ green: `${ESC}32m`,
1014
+ greenB: `${ESC}92m`,
1015
+ cyan: `${ESC}36m`,
1016
+ cyanB: `${ESC}96m`,
1017
+ magenta: `${ESC}95m`,
1018
+ yellow: `${ESC}93m`,
1019
+ red: `${ESC}91m`
1020
+ };
1021
+ INNER = 42;
1022
+ BOX_W = INNER + 4;
1023
+ vlen = (s) => [...s.replace(/\x1b\[[0-9;]*m/g, "")].length;
1024
+ padEndV = (s, w) => s + " ".repeat(Math.max(0, w - vlen(s)));
1025
+ center = (s, w) => {
1026
+ const pad = Math.max(0, w - vlen(s));
1027
+ const left = Math.floor(pad / 2);
1028
+ return " ".repeat(left) + s + " ".repeat(pad - left);
1029
+ };
1030
+ GLYPHS = {
1031
+ C: [" \u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588"],
1032
+ L: ["\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588\u2588\u2588\u2588\u2588"],
1033
+ A: [" \u2588\u2588\u2588\u2588 ", "\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588\u2588"],
1034
+ W: ["\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588 \u2588\u2588", "\u2588\u2588 \u2588 \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588 \u2588\u2588 "],
1035
+ F: ["\u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588\u2588\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 "],
1036
+ S: [" \u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588 ", " \u2588\u2588", " \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588 "],
1037
+ T: ["\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 "]
1038
+ };
1039
+ ui = { C: C2, buildBanner, shellPrompt, agentHeader };
1040
+ }
1041
+ });
1042
+
872
1043
  // src/news.ts
873
- var import_node_fs4, import_node_path4, NEWS, latestNewsVersion, renderNews, stateFile, readState, writeState, consumePostUpdateLaunch;
1044
+ var import_node_fs4, import_node_path4, NEWS, latestNewsVersion, wrapText, renderNews, stateFile, readState, writeState, consumePostUpdateLaunch;
874
1045
  var init_news = __esm({
875
1046
  "src/news.ts"() {
876
1047
  "use strict";
877
1048
  import_node_fs4 = __toESM(require("node:fs"));
878
1049
  import_node_path4 = __toESM(require("node:path"));
879
1050
  init_config();
1051
+ init_ui();
880
1052
  NEWS = {
881
1053
  "2.0.0": [
882
1054
  'Modo ANALISE DE PROJETO (somente leitura): peca "analise/varredura/auditoria do meu projeto" e o clawfast troca sozinho para um auditor dedicado \u2014 sem /comando. Diga "modo normal" para sair.',
@@ -889,7 +1061,7 @@ var init_news = __esm({
889
1061
  "1.0.3": [
890
1062
  "Atualizacao: `clawfast update` atualiza o CLI e mostra as boas-vindas; aparece um aviso quando ha versao nova; /nov lista as novidades.",
891
1063
  "Skills: /skillcreator cria ou cola uma skill (de qualquer tamanho), /skills lista e /skill delete remove. As skills viram conhecimento disponivel para TODOS os modelos.",
892
- "Modelos na NVIDIA build: mistral-medium-3.5, kimi-k2.6, glm-5.1 e qwen3.5-397b \u2014 todos com function calling de verdade (as ferramentas disparam).",
1064
+ "Modelos na NVIDIA build: mistral-medium-3.5, gpt-oss-120b, glm-5.1 e qwen3.5-397b \u2014 todos com function calling de verdade (as ferramentas disparam).",
893
1065
  "Resiliencia a rate limit (429): cai para os outros modelos como contingencia e espera/retenta a cadeia automaticamente."
894
1066
  ]
895
1067
  };
@@ -905,14 +1077,50 @@ var init_news = __esm({
905
1077
  return 0;
906
1078
  })[0];
907
1079
  };
1080
+ wrapText = (text2, width) => {
1081
+ const words = text2.split(/\s+/).filter(Boolean);
1082
+ const lines = [];
1083
+ let line = "";
1084
+ for (const word of words) {
1085
+ if (line && vlen(line) + 1 + vlen(word) > width) {
1086
+ lines.push(line);
1087
+ line = word;
1088
+ } else {
1089
+ line = line ? `${line} ${word}` : word;
1090
+ }
1091
+ }
1092
+ if (line) lines.push(line);
1093
+ return lines.length ? lines : [""];
1094
+ };
908
1095
  renderNews = (version3) => {
909
1096
  const key = NEWS[version3] ? version3 : latestNewsVersion();
910
1097
  if (!key || !NEWS[key]) {
911
- return "Sem novidades registradas para esta versao.";
912
- }
913
- const lines = NEWS[key].map((item) => ` \u2022 ${item}`).join("\n");
914
- return `Novidades da versao ${key}:
915
- ${lines}`;
1098
+ return `${C2.dim}Sem novidades registradas para esta versao.${C2.reset}`;
1099
+ }
1100
+ const cols2 = process.stdout.columns || 80;
1101
+ const BOX = Math.min(Math.max(cols2 - 2, 48), 80);
1102
+ const INNER2 = BOX - 4;
1103
+ const BULLET = "\u2022 ";
1104
+ const INDENT = " ".repeat(BULLET.length);
1105
+ const title = `Novidades \xB7 v${key}`;
1106
+ const topFill = "\u2500".repeat(Math.max(0, BOX - vlen(title) - 5));
1107
+ const top = `${C2.green}\u256D\u2500 ${C2.greenB}${C2.bold}${title}${C2.reset}${C2.green} ${topFill}\u256E${C2.reset}`;
1108
+ const bottom = `${C2.green}\u2570${"\u2500".repeat(BOX - 2)}\u256F${C2.reset}`;
1109
+ const row = (content) => {
1110
+ const pad = " ".repeat(Math.max(0, INNER2 - vlen(content)));
1111
+ return `${C2.green}\u2502 ${C2.reset}${content}${pad}${C2.green} \u2502${C2.reset}`;
1112
+ };
1113
+ const body = [row("")];
1114
+ NEWS[key].forEach((item, idx) => {
1115
+ if (idx > 0) body.push(row(""));
1116
+ const wrapped = wrapText(item, INNER2 - BULLET.length);
1117
+ wrapped.forEach((line, i) => {
1118
+ const prefix = i === 0 ? `${C2.cyanB}${BULLET}${C2.reset}` : INDENT;
1119
+ body.push(row(`${prefix}${line}`));
1120
+ });
1121
+ });
1122
+ body.push(row(""));
1123
+ return [top, ...body, bottom].join("\n");
916
1124
  };
917
1125
  stateFile = () => import_node_path4.default.join(clawfastHome(), "state.json");
918
1126
  readState = () => {
@@ -1513,10 +1721,10 @@ function mergeDefs(...defs) {
1513
1721
  function cloneDef(schema) {
1514
1722
  return mergeDefs(schema._zod.def);
1515
1723
  }
1516
- function getElementAtPath(obj, path10) {
1517
- if (!path10)
1724
+ function getElementAtPath(obj, path12) {
1725
+ if (!path12)
1518
1726
  return obj;
1519
- return path10.reduce((acc, key) => acc?.[key], obj);
1727
+ return path12.reduce((acc, key) => acc?.[key], obj);
1520
1728
  }
1521
1729
  function promiseAllObject(promisesObj) {
1522
1730
  const keys = Object.keys(promisesObj);
@@ -1844,11 +2052,11 @@ function explicitlyAborted(x, startIndex = 0) {
1844
2052
  }
1845
2053
  return false;
1846
2054
  }
1847
- function prefixIssues(path10, issues) {
2055
+ function prefixIssues(path12, issues) {
1848
2056
  return issues.map((iss) => {
1849
2057
  var _a25;
1850
2058
  (_a25 = iss).path ?? (_a25.path = []);
1851
- iss.path.unshift(path10);
2059
+ iss.path.unshift(path12);
1852
2060
  return iss;
1853
2061
  });
1854
2062
  }
@@ -2066,16 +2274,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2066
2274
  }
2067
2275
  function formatError(error51, mapper = (issue2) => issue2.message) {
2068
2276
  const fieldErrors = { _errors: [] };
2069
- const processError = (error52, path10 = []) => {
2277
+ const processError = (error52, path12 = []) => {
2070
2278
  for (const issue2 of error52.issues) {
2071
2279
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2072
- issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
2280
+ issue2.errors.map((issues) => processError({ issues }, [...path12, ...issue2.path]));
2073
2281
  } else if (issue2.code === "invalid_key") {
2074
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
2282
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2075
2283
  } else if (issue2.code === "invalid_element") {
2076
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
2284
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2077
2285
  } else {
2078
- const fullpath = [...path10, ...issue2.path];
2286
+ const fullpath = [...path12, ...issue2.path];
2079
2287
  if (fullpath.length === 0) {
2080
2288
  fieldErrors._errors.push(mapper(issue2));
2081
2289
  } else {
@@ -2102,17 +2310,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2102
2310
  }
2103
2311
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2104
2312
  const result = { errors: [] };
2105
- const processError = (error52, path10 = []) => {
2313
+ const processError = (error52, path12 = []) => {
2106
2314
  var _a25, _b18;
2107
2315
  for (const issue2 of error52.issues) {
2108
2316
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2109
- issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
2317
+ issue2.errors.map((issues) => processError({ issues }, [...path12, ...issue2.path]));
2110
2318
  } else if (issue2.code === "invalid_key") {
2111
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
2319
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2112
2320
  } else if (issue2.code === "invalid_element") {
2113
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
2321
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2114
2322
  } else {
2115
- const fullpath = [...path10, ...issue2.path];
2323
+ const fullpath = [...path12, ...issue2.path];
2116
2324
  if (fullpath.length === 0) {
2117
2325
  result.errors.push(mapper(issue2));
2118
2326
  continue;
@@ -2144,8 +2352,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2144
2352
  }
2145
2353
  function toDotPath(_path) {
2146
2354
  const segs = [];
2147
- const path10 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2148
- for (const seg of path10) {
2355
+ const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2356
+ for (const seg of path12) {
2149
2357
  if (typeof seg === "number")
2150
2358
  segs.push(`[${seg}]`);
2151
2359
  else if (typeof seg === "symbol")
@@ -15648,13 +15856,13 @@ function resolveRef(ref, ctx) {
15648
15856
  if (!ref.startsWith("#")) {
15649
15857
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15650
15858
  }
15651
- const path10 = ref.slice(1).split("/").filter(Boolean);
15652
- if (path10.length === 0) {
15859
+ const path12 = ref.slice(1).split("/").filter(Boolean);
15860
+ if (path12.length === 0) {
15653
15861
  return ctx.rootSchema;
15654
15862
  }
15655
15863
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15656
- if (path10[0] === defsKey) {
15657
- const key = path10[1];
15864
+ if (path12[0] === defsKey) {
15865
+ const key = path12[1];
15658
15866
  if (!key || !ctx.defs[key]) {
15659
15867
  throw new Error(`Reference not found: ${ref}`);
15660
15868
  }
@@ -16843,8 +17051,8 @@ var init_parseUtil = __esm({
16843
17051
  init_errors3();
16844
17052
  init_en2();
16845
17053
  makeIssue = (params) => {
16846
- const { data, path: path10, errorMaps, issueData } = params;
16847
- const fullPath = [...path10, ...issueData.path || []];
17054
+ const { data, path: path12, errorMaps, issueData } = params;
17055
+ const fullPath = [...path12, ...issueData.path || []];
16848
17056
  const fullIssue = {
16849
17057
  ...issueData,
16850
17058
  path: fullPath
@@ -17127,11 +17335,11 @@ var init_types = __esm({
17127
17335
  init_parseUtil();
17128
17336
  init_util2();
17129
17337
  ParseInputLazyPath = class {
17130
- constructor(parent, value, path10, key) {
17338
+ constructor(parent, value, path12, key) {
17131
17339
  this._cachedPath = [];
17132
17340
  this.parent = parent;
17133
17341
  this.data = value;
17134
- this._path = path10;
17342
+ this._path = path12;
17135
17343
  this._key = key;
17136
17344
  }
17137
17345
  get path() {
@@ -23170,8 +23378,8 @@ var require_auth_config = __commonJS({
23170
23378
  writeAuthConfig: () => writeAuthConfig
23171
23379
  });
23172
23380
  module2.exports = __toCommonJS(auth_config_exports);
23173
- var fs6 = __toESM2(require("fs"));
23174
- var path10 = __toESM2(require("path"));
23381
+ var fs8 = __toESM2(require("fs"));
23382
+ var path12 = __toESM2(require("path"));
23175
23383
  var import_token_util = require_token_util();
23176
23384
  function getAuthConfigPath() {
23177
23385
  const dataDir = (0, import_token_util.getVercelDataDir)();
@@ -23180,15 +23388,15 @@ var require_auth_config = __commonJS({
23180
23388
  `Unable to find Vercel CLI data directory. Your platform: ${process.platform}. Supported: darwin, linux, win32.`
23181
23389
  );
23182
23390
  }
23183
- return path10.join(dataDir, "auth.json");
23391
+ return path12.join(dataDir, "auth.json");
23184
23392
  }
23185
23393
  function readAuthConfig() {
23186
23394
  try {
23187
23395
  const authPath = getAuthConfigPath();
23188
- if (!fs6.existsSync(authPath)) {
23396
+ if (!fs8.existsSync(authPath)) {
23189
23397
  return null;
23190
23398
  }
23191
- const content = fs6.readFileSync(authPath, "utf8");
23399
+ const content = fs8.readFileSync(authPath, "utf8");
23192
23400
  if (!content) {
23193
23401
  return null;
23194
23402
  }
@@ -23199,11 +23407,11 @@ var require_auth_config = __commonJS({
23199
23407
  }
23200
23408
  function writeAuthConfig(config3) {
23201
23409
  const authPath = getAuthConfigPath();
23202
- const authDir = path10.dirname(authPath);
23203
- if (!fs6.existsSync(authDir)) {
23204
- fs6.mkdirSync(authDir, { mode: 504, recursive: true });
23410
+ const authDir = path12.dirname(authPath);
23411
+ if (!fs8.existsSync(authDir)) {
23412
+ fs8.mkdirSync(authDir, { mode: 504, recursive: true });
23205
23413
  }
23206
- fs6.writeFileSync(authPath, JSON.stringify(config3, null, 2), { mode: 384 });
23414
+ fs8.writeFileSync(authPath, JSON.stringify(config3, null, 2), { mode: 384 });
23207
23415
  }
23208
23416
  function isValidAccessToken(authConfig, expirationBufferMs = 0) {
23209
23417
  if (!authConfig.token)
@@ -23394,8 +23602,8 @@ var require_token_util = __commonJS({
23394
23602
  saveToken: () => saveToken
23395
23603
  });
23396
23604
  module2.exports = __toCommonJS(token_util_exports);
23397
- var path10 = __toESM2(require("path"));
23398
- var fs6 = __toESM2(require("fs"));
23605
+ var path12 = __toESM2(require("path"));
23606
+ var fs8 = __toESM2(require("fs"));
23399
23607
  var import_token_error = require_token_error();
23400
23608
  var import_token_io = require_token_io();
23401
23609
  var import_auth_config = require_auth_config();
@@ -23407,7 +23615,7 @@ var require_token_util = __commonJS({
23407
23615
  if (!dataDir) {
23408
23616
  return null;
23409
23617
  }
23410
- return path10.join(dataDir, vercelFolder);
23618
+ return path12.join(dataDir, vercelFolder);
23411
23619
  }
23412
23620
  async function getVercelToken2(options) {
23413
23621
  const authConfig = (0, import_auth_config.readAuthConfig)();
@@ -23483,13 +23691,13 @@ var require_token_util = __commonJS({
23483
23691
  "Unable to find project root directory. Have you linked your project with `vc link?`"
23484
23692
  );
23485
23693
  }
23486
- const prjPath = path10.join(dir, ".vercel", "project.json");
23487
- if (!fs6.existsSync(prjPath)) {
23694
+ const prjPath = path12.join(dir, ".vercel", "project.json");
23695
+ if (!fs8.existsSync(prjPath)) {
23488
23696
  throw new import_token_error.VercelOidcTokenError(
23489
23697
  "project.json not found, have you linked your project with `vc link?`"
23490
23698
  );
23491
23699
  }
23492
- const prj = JSON.parse(fs6.readFileSync(prjPath, "utf8"));
23700
+ const prj = JSON.parse(fs8.readFileSync(prjPath, "utf8"));
23493
23701
  if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") {
23494
23702
  throw new TypeError(
23495
23703
  "Expected a string-valued projectId property. Try running `vc link` to re-link your project."
@@ -23504,11 +23712,11 @@ var require_token_util = __commonJS({
23504
23712
  "Unable to find user data directory. Please reach out to Vercel support."
23505
23713
  );
23506
23714
  }
23507
- const tokenPath = path10.join(dir, "com.vercel.token", `${projectId}.json`);
23715
+ const tokenPath = path12.join(dir, "com.vercel.token", `${projectId}.json`);
23508
23716
  const tokenJson = JSON.stringify(token);
23509
- fs6.mkdirSync(path10.dirname(tokenPath), { mode: 504, recursive: true });
23510
- fs6.writeFileSync(tokenPath, tokenJson);
23511
- fs6.chmodSync(tokenPath, 432);
23717
+ fs8.mkdirSync(path12.dirname(tokenPath), { mode: 504, recursive: true });
23718
+ fs8.writeFileSync(tokenPath, tokenJson);
23719
+ fs8.chmodSync(tokenPath, 432);
23512
23720
  return;
23513
23721
  }
23514
23722
  function loadToken(projectId) {
@@ -23518,11 +23726,11 @@ var require_token_util = __commonJS({
23518
23726
  "Unable to find user data directory. Please reach out to Vercel support."
23519
23727
  );
23520
23728
  }
23521
- const tokenPath = path10.join(dir, "com.vercel.token", `${projectId}.json`);
23522
- if (!fs6.existsSync(tokenPath)) {
23729
+ const tokenPath = path12.join(dir, "com.vercel.token", `${projectId}.json`);
23730
+ if (!fs8.existsSync(tokenPath)) {
23523
23731
  return null;
23524
23732
  }
23525
- const token = JSON.parse(fs6.readFileSync(tokenPath, "utf8"));
23733
+ const token = JSON.parse(fs8.readFileSync(tokenPath, "utf8"));
23526
23734
  assertVercelOidcTokenResponse(token);
23527
23735
  return token;
23528
23736
  }
@@ -35548,7 +35756,7 @@ function createOpenRouter(options = {}) {
35548
35756
  );
35549
35757
  const createChatModel = (modelId, settings = {}) => new OpenRouterChatLanguageModel(modelId, settings, {
35550
35758
  provider: "openrouter.chat",
35551
- url: ({ path: path10 }) => `${baseURL}${path10}`,
35759
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
35552
35760
  headers: getHeaders,
35553
35761
  compatibility,
35554
35762
  fetch: options.fetch,
@@ -35556,7 +35764,7 @@ function createOpenRouter(options = {}) {
35556
35764
  });
35557
35765
  const createCompletionModel = (modelId, settings = {}) => new OpenRouterCompletionLanguageModel(modelId, settings, {
35558
35766
  provider: "openrouter.completion",
35559
- url: ({ path: path10 }) => `${baseURL}${path10}`,
35767
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
35560
35768
  headers: getHeaders,
35561
35769
  compatibility,
35562
35770
  fetch: options.fetch,
@@ -35564,21 +35772,21 @@ function createOpenRouter(options = {}) {
35564
35772
  });
35565
35773
  const createEmbeddingModel = (modelId, settings = {}) => new OpenRouterEmbeddingModel(modelId, settings, {
35566
35774
  provider: "openrouter.embedding",
35567
- url: ({ path: path10 }) => `${baseURL}${path10}`,
35775
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
35568
35776
  headers: getHeaders,
35569
35777
  fetch: options.fetch,
35570
35778
  extraBody: options.extraBody
35571
35779
  });
35572
35780
  const createImageModel = (modelId, settings = {}) => new OpenRouterImageModel(modelId, settings, {
35573
35781
  provider: "openrouter.image",
35574
- url: ({ path: path10 }) => `${baseURL}${path10}`,
35782
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
35575
35783
  headers: getHeaders,
35576
35784
  fetch: options.fetch,
35577
35785
  extraBody: options.extraBody
35578
35786
  });
35579
35787
  const createVideoModel = (modelId, settings = {}) => new OpenRouterVideoModel(modelId, settings, {
35580
35788
  provider: "openrouter.video",
35581
- url: ({ path: path10 }) => `${baseURL}${path10}`,
35789
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
35582
35790
  headers: getHeaders,
35583
35791
  fetch: options.fetch,
35584
35792
  extraBody: options.extraBody
@@ -40114,37 +40322,37 @@ function createOpenAI(options = {}) {
40114
40322
  );
40115
40323
  const createChatModel = (modelId) => new OpenAIChatLanguageModel(modelId, {
40116
40324
  provider: `${providerName}.chat`,
40117
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40325
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40118
40326
  headers: getHeaders,
40119
40327
  fetch: options.fetch
40120
40328
  });
40121
40329
  const createCompletionModel = (modelId) => new OpenAICompletionLanguageModel(modelId, {
40122
40330
  provider: `${providerName}.completion`,
40123
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40331
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40124
40332
  headers: getHeaders,
40125
40333
  fetch: options.fetch
40126
40334
  });
40127
40335
  const createEmbeddingModel = (modelId) => new OpenAIEmbeddingModel(modelId, {
40128
40336
  provider: `${providerName}.embedding`,
40129
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40337
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40130
40338
  headers: getHeaders,
40131
40339
  fetch: options.fetch
40132
40340
  });
40133
40341
  const createImageModel = (modelId) => new OpenAIImageModel(modelId, {
40134
40342
  provider: `${providerName}.image`,
40135
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40343
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40136
40344
  headers: getHeaders,
40137
40345
  fetch: options.fetch
40138
40346
  });
40139
40347
  const createTranscriptionModel = (modelId) => new OpenAITranscriptionModel(modelId, {
40140
40348
  provider: `${providerName}.transcription`,
40141
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40349
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40142
40350
  headers: getHeaders,
40143
40351
  fetch: options.fetch
40144
40352
  });
40145
40353
  const createSpeechModel = (modelId) => new OpenAISpeechModel(modelId, {
40146
40354
  provider: `${providerName}.speech`,
40147
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40355
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40148
40356
  headers: getHeaders,
40149
40357
  fetch: options.fetch
40150
40358
  });
@@ -40159,7 +40367,7 @@ function createOpenAI(options = {}) {
40159
40367
  const createResponsesModel = (modelId) => {
40160
40368
  return new OpenAIResponsesLanguageModel(modelId, {
40161
40369
  provider: `${providerName}.responses`,
40162
- url: ({ path: path10 }) => `${baseURL}${path10}`,
40370
+ url: ({ path: path12 }) => `${baseURL}${path12}`,
40163
40371
  headers: getHeaders,
40164
40372
  fetch: options.fetch,
40165
40373
  fileIdPrefixes: ["file-"]
@@ -45557,25 +45765,29 @@ var init_providers = __esm({
45557
45765
  };
45558
45766
  };
45559
45767
  nvidiaPatchFetch = async (url2, init) => {
45560
- if (init?.body && typeof init.body === "string") {
45768
+ const key = process.env.NVIDIA_API_KEY?.trim();
45769
+ const headers = new Headers(init?.headers);
45770
+ if (key) headers.set("Authorization", `Bearer ${key}`);
45771
+ let nextInit = { ...init, headers };
45772
+ if (typeof nextInit.body === "string") {
45561
45773
  try {
45562
- const parsed = JSON.parse(init.body);
45774
+ const parsed = JSON.parse(nextInit.body);
45563
45775
  const patched = applyNvidiaMistralConfig(parsed);
45564
45776
  if (patched.changed) {
45565
- return globalThis.fetch(url2, {
45566
- ...init,
45567
- body: JSON.stringify(patched.body)
45568
- });
45777
+ nextInit = { ...nextInit, body: JSON.stringify(patched.body) };
45569
45778
  }
45570
45779
  } catch {
45571
45780
  }
45572
45781
  }
45573
- return globalThis.fetch(url2, init);
45782
+ return globalThis.fetch(url2, nextInit);
45574
45783
  };
45575
45784
  nvidia = createOpenAI({
45576
45785
  name: "nvidia",
45577
45786
  baseURL: process.env.NVIDIA_BASE_URL || "https://integrate.api.nvidia.com/v1",
45578
- apiKey: process.env.NVIDIA_API_KEY,
45787
+ // Placeholder so the SDK never throws "missing API key" at request build time
45788
+ // when the env is empty at load — nvidiaPatchFetch sets the real Authorization
45789
+ // header from process.env on every request (see above).
45790
+ apiKey: process.env.NVIDIA_API_KEY || "nvapi-clawfast-runtime",
45579
45791
  fetch: nvidiaPatchFetch
45580
45792
  });
45581
45793
  deepseek = createOpenAI({
@@ -45607,7 +45819,7 @@ var init_providers = __esm({
45607
45819
  "model-nvidia-mistral-medium-3.5": nvidia.chat(
45608
45820
  "mistralai/mistral-medium-3.5-128b"
45609
45821
  ),
45610
- "model-nvidia-kimi-k2.6": nvidia.chat("moonshotai/kimi-k2.6"),
45822
+ "model-nvidia-gpt-oss-120b": nvidia.chat("openai/gpt-oss-120b"),
45611
45823
  "model-nvidia-glm-5.1": nvidia.chat("z-ai/glm-5.1"),
45612
45824
  "model-nvidia-qwen3.5-397b": nvidia.chat("qwen/qwen3.5-397b-a17b"),
45613
45825
  // Extra explicit keys for the CLI.
@@ -45645,8 +45857,8 @@ var init_providers = __esm({
45645
45857
  ...hasEnvValue("NVIDIA_API_KEY") ? [
45646
45858
  "model-nvidia-mistral-medium-3.5",
45647
45859
  // NVIDIA mistralai/mistral-medium-3.5-128b
45648
- "model-nvidia-kimi-k2.6",
45649
- // NVIDIA moonshotai/kimi-k2.6
45860
+ "model-nvidia-gpt-oss-120b",
45861
+ // NVIDIA openai/gpt-oss-120b
45650
45862
  "model-nvidia-glm-5.1",
45651
45863
  // NVIDIA z-ai/glm-5.1
45652
45864
  "model-nvidia-qwen3.5-397b"
@@ -45672,7 +45884,7 @@ var init_providers = __esm({
45672
45884
  "model-opus-4.6": "May 2025",
45673
45885
  "model-kimi-k2.6": "April 2024",
45674
45886
  "model-nvidia-mistral-medium-3.5": "Unknown",
45675
- "model-nvidia-kimi-k2.6": "April 2024",
45887
+ "model-nvidia-gpt-oss-120b": "June 2024",
45676
45888
  "model-nvidia-glm-5.1": "Unknown",
45677
45889
  "model-nvidia-qwen3.5-397b": "Unknown",
45678
45890
  "model-deepseek-proxy": "July 2024",
@@ -45696,7 +45908,7 @@ var init_providers = __esm({
45696
45908
  "model-opus-4.6": "Anthropic Claude Opus 4.6",
45697
45909
  "model-kimi-k2.6": "Moonshot Kimi K2.6",
45698
45910
  "model-nvidia-mistral-medium-3.5": "NVIDIA - Mistral Medium 3.5",
45699
- "model-nvidia-kimi-k2.6": "NVIDIA - Moonshot Kimi K2.6",
45911
+ "model-nvidia-gpt-oss-120b": "NVIDIA - OpenAI GPT-OSS 120B",
45700
45912
  "model-nvidia-glm-5.1": "NVIDIA - Z.ai GLM 5.1",
45701
45913
  "model-nvidia-qwen3.5-397b": "NVIDIA - Qwen3.5 397B A17B",
45702
45914
  "model-deepseek-proxy": "DeepSeek (sessao web logada / deepsproxy)",
@@ -48730,8 +48942,8 @@ var init_background_process_tracker = __esm({
48730
48942
  /**
48731
48943
  * Normalize file path for comparison
48732
48944
  */
48733
- normalizePath(path10) {
48734
- let normalized = path10.trim().replace(/\/+/g, "/");
48945
+ normalizePath(path12) {
48946
+ let normalized = path12.trim().replace(/\/+/g, "/");
48735
48947
  if (normalized.startsWith("./")) {
48736
48948
  normalized = normalized.slice(2);
48737
48949
  }
@@ -48969,8 +49181,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? (0, import_pat
48969
49181
  return decodedFile;
48970
49182
  };
48971
49183
  }
48972
- function normalizeWindowsPath(path10) {
48973
- return path10.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
49184
+ function normalizeWindowsPath(path12) {
49185
+ return path12.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
48974
49186
  }
48975
49187
  var import_path;
48976
49188
  var init_module_node = __esm({
@@ -51859,9 +52071,9 @@ async function addSourceContext(frames) {
51859
52071
  LRU_FILE_CONTENTS_CACHE.reduce();
51860
52072
  return frames;
51861
52073
  }
51862
- function getContextLinesFromFile(path10, ranges, output) {
52074
+ function getContextLinesFromFile(path12, ranges, output) {
51863
52075
  return new Promise((resolve2) => {
51864
- const stream = (0, import_node_fs5.createReadStream)(path10);
52076
+ const stream = (0, import_node_fs5.createReadStream)(path12);
51865
52077
  const lineReaded = (0, import_node_readline2.createInterface)({
51866
52078
  input: stream
51867
52079
  });
@@ -51876,7 +52088,7 @@ function getContextLinesFromFile(path10, ranges, output) {
51876
52088
  let rangeStart = range[0];
51877
52089
  let rangeEnd = range[1];
51878
52090
  function onStreamError() {
51879
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path10, 1);
52091
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path12, 1);
51880
52092
  lineReaded.close();
51881
52093
  lineReaded.removeAllListeners();
51882
52094
  destroyStreamAndResolve();
@@ -51937,8 +52149,8 @@ function clearLineContext(frame2) {
51937
52149
  delete frame2.context_line;
51938
52150
  delete frame2.post_context;
51939
52151
  }
51940
- function shouldSkipContextLinesForFile(path10) {
51941
- return path10.startsWith("node:") || path10.endsWith(".min.js") || path10.endsWith(".min.cjs") || path10.endsWith(".min.mjs") || path10.startsWith("data:");
52152
+ function shouldSkipContextLinesForFile(path12) {
52153
+ return path12.startsWith("node:") || path12.endsWith(".min.js") || path12.endsWith(".min.cjs") || path12.endsWith(".min.mjs") || path12.startsWith("data:");
51942
52154
  }
51943
52155
  function shouldSkipContextLinesForFrame(frame2) {
51944
52156
  if (void 0 !== frame2.lineno && frame2.lineno > MAX_CONTEXTLINES_LINENO) return true;
@@ -67348,8 +67560,8 @@ var init_logger2 = __esm({
67348
67560
  });
67349
67561
 
67350
67562
  // ../lib/ai/tools/file.ts
67351
- function isSpritPath(path10) {
67352
- return path10.split(/[\\/]/).some((segment) => segment.toLowerCase() === "sprit");
67563
+ function isSpritPath(path12) {
67564
+ return path12.split(/[\\/]/).some((segment) => segment.toLowerCase() === "sprit");
67353
67565
  }
67354
67566
  function getViewSandboxType(sandbox) {
67355
67567
  return isCentrifugoSandbox(sandbox) ? "centrifugo" : "e2b";
@@ -67380,7 +67592,7 @@ function captureFileViewImageUsage(args) {
67380
67592
  const {
67381
67593
  context: context2,
67382
67594
  sandbox,
67383
- path: path10,
67595
+ path: path12,
67384
67596
  outcome,
67385
67597
  durationMs,
67386
67598
  mediaType,
@@ -67398,7 +67610,7 @@ function captureFileViewImageUsage(args) {
67398
67610
  model: getActiveModelName(context2),
67399
67611
  configured_model: context2.modelName,
67400
67612
  sandbox_type: getViewSandboxType(sandbox),
67401
- file_extension: getFileExtension(path10),
67613
+ file_extension: getFileExtension(path12),
67402
67614
  outcome,
67403
67615
  success: outcome === "success",
67404
67616
  duration_ms: durationMs,
@@ -67472,22 +67684,22 @@ async function runSandboxCommand(sandbox, command, envVars, timeoutMs = 6e4) {
67472
67684
  function isWindowsSandbox(sandbox) {
67473
67685
  return isCentrifugoSandbox(sandbox) && sandbox.isWindows();
67474
67686
  }
67475
- function getWindowsNativePath(path10) {
67476
- if (/^[A-Za-z]:[\\/]/.test(path10)) return path10;
67477
- if (path10.startsWith("/tmp/")) {
67478
- return `C:\\temp${path10.slice(4).replace(/\//g, "\\")}`;
67687
+ function getWindowsNativePath(path12) {
67688
+ if (/^[A-Za-z]:[\\/]/.test(path12)) return path12;
67689
+ if (path12.startsWith("/tmp/")) {
67690
+ return `C:\\temp${path12.slice(4).replace(/\//g, "\\")}`;
67479
67691
  }
67480
- return path10.replace(/\//g, "\\");
67692
+ return path12.replace(/\//g, "\\");
67481
67693
  }
67482
- function getPythonPathForSandbox(sandbox, path10) {
67483
- return isWindowsSandbox(sandbox) ? getWindowsNativePath(path10) : path10;
67694
+ function getPythonPathForSandbox(sandbox, path12) {
67695
+ return isWindowsSandbox(sandbox) ? getWindowsNativePath(path12) : path12;
67484
67696
  }
67485
- function toWindowsBashPath(path10) {
67486
- const drive = path10.match(/^([A-Za-z]):[\\/](.*)$/);
67697
+ function toWindowsBashPath(path12) {
67698
+ const drive = path12.match(/^([A-Za-z]):[\\/](.*)$/);
67487
67699
  if (drive) {
67488
67700
  return `/${drive[1].toLowerCase()}/${drive[2].replace(/\\/g, "/")}`;
67489
67701
  }
67490
- return path10.replace(/\\/g, "/");
67702
+ return path12.replace(/\\/g, "/");
67491
67703
  }
67492
67704
  async function detectSandboxShell(sandbox) {
67493
67705
  if (!isWindowsSandbox(sandbox)) return "bash";
@@ -67526,8 +67738,8 @@ PY`;
67526
67738
  }
67527
67739
  }
67528
67740
  }
67529
- async function getSandboxFileState(sandbox, path10) {
67530
- const pythonPath = getPythonPathForSandbox(sandbox, path10);
67741
+ async function getSandboxFileState(sandbox, path12) {
67742
+ const pythonPath = getPythonPathForSandbox(sandbox, path12);
67531
67743
  const result = await runPythonScript(
67532
67744
  sandbox,
67533
67745
  FILE_STATE_SCRIPT,
@@ -67544,23 +67756,23 @@ async function getSandboxFileState(sandbox, path10) {
67544
67756
  if (result.exitCode !== 0) {
67545
67757
  return {
67546
67758
  kind: "unknown",
67547
- path: path10,
67759
+ path: path12,
67548
67760
  error: result.stderr || result.stdout || "file state command failed"
67549
67761
  };
67550
67762
  }
67551
67763
  try {
67552
67764
  const payload = JSON.parse(result.stdout.trim());
67553
67765
  if (payload.kind === "file" && typeof payload.sizeBytes === "number" && Number.isFinite(payload.sizeBytes)) {
67554
- return { ...payload, path: path10 };
67766
+ return { ...payload, path: path12 };
67555
67767
  }
67556
67768
  if (payload.kind === "missing" || payload.kind === "not_file") {
67557
- return { ...payload, path: path10 };
67769
+ return { ...payload, path: path12 };
67558
67770
  }
67559
67771
  } catch {
67560
67772
  }
67561
67773
  return {
67562
67774
  kind: "unknown",
67563
- path: path10,
67775
+ path: path12,
67564
67776
  error: result.stderr || result.stdout || "invalid file state response"
67565
67777
  };
67566
67778
  }
@@ -67592,8 +67804,8 @@ ${numberedContent}${truncatedNotice}${footerNotice}`;
67592
67804
  })
67593
67805
  };
67594
67806
  }
67595
- async function readSandboxTextFile(sandbox, path10, range) {
67596
- const pythonPath = getPythonPathForSandbox(sandbox, path10);
67807
+ async function readSandboxTextFile(sandbox, path12, range) {
67808
+ const pythonPath = getPythonPathForSandbox(sandbox, path12);
67597
67809
  const envVars = {
67598
67810
  HACKERAI_FILE_READ_PATH: pythonPath,
67599
67811
  HACKERAI_FILE_READ_RANGE_START: String(range?.[0] ?? 0),
@@ -67621,40 +67833,40 @@ async function readSandboxTextFile(sandbox, path10, range) {
67621
67833
  }
67622
67834
  return payload;
67623
67835
  }
67624
- async function readSandboxTextFileWithFallback(sandbox, path10, range) {
67836
+ async function readSandboxTextFileWithFallback(sandbox, path12, range) {
67625
67837
  try {
67626
- return await readSandboxTextFile(sandbox, path10, range);
67838
+ return await readSandboxTextFile(sandbox, path12, range);
67627
67839
  } catch (error51) {
67628
67840
  const errorMessage = error51 instanceof Error ? error51.message : String(error51);
67629
67841
  if (errorMessage.startsWith("Invalid ") || errorMessage.includes("File not found")) {
67630
67842
  throw error51;
67631
67843
  }
67632
- const state = await getSandboxFileState(sandbox, path10);
67844
+ const state = await getSandboxFileState(sandbox, path12);
67633
67845
  if (state.kind === "unknown") {
67634
67846
  throw new Error(
67635
- `Unable to determine file size for ${path10}; refusing to load the file into memory. ${state.error}`
67847
+ `Unable to determine file size for ${path12}; refusing to load the file into memory. ${state.error}`
67636
67848
  );
67637
67849
  }
67638
67850
  if (state.kind === "missing") {
67639
- throw new Error(`File not found or is not a regular file: ${path10}`);
67851
+ throw new Error(`File not found or is not a regular file: ${path12}`);
67640
67852
  }
67641
67853
  if (state.kind === "not_file") {
67642
- throw new Error(`File is not a regular file: ${path10}`);
67854
+ throw new Error(`File is not a regular file: ${path12}`);
67643
67855
  }
67644
67856
  if (state.sizeBytes > MAX_TEXT_FILE_READ_BYTES) {
67645
67857
  if (range) {
67646
67858
  throw new Error(
67647
- `Unable to perform a bounded range read for ${path10}, and the file is too large to load safely (${formatBytes(state.sizeBytes)}). Use a targeted terminal command that writes a small result to a separate file.`
67859
+ `Unable to perform a bounded range read for ${path12}, and the file is too large to load safely (${formatBytes(state.sizeBytes)}). Use a targeted terminal command that writes a small result to a separate file.`
67648
67860
  );
67649
67861
  }
67650
67862
  return {
67651
- path: path10,
67863
+ path: path12,
67652
67864
  sizeBytes: state.sizeBytes,
67653
67865
  totalLines: 0,
67654
67866
  tooLarge: true
67655
67867
  };
67656
67868
  }
67657
- const fileContent = await sandbox.files.read(path10, {
67869
+ const fileContent = await sandbox.files.read(path12, {
67658
67870
  user: "user"
67659
67871
  });
67660
67872
  const lines = fileContent.split("\n");
@@ -67675,15 +67887,10 @@ async function readSandboxTextFileWithFallback(sandbox, path10, range) {
67675
67887
  `Invalid start_line: ${start}. File has ${lines.length} lines (1-indexed).`
67676
67888
  );
67677
67889
  }
67678
- if (end !== -1 && end > lines.length) {
67679
- throw new Error(
67680
- `Invalid end_line: ${end}. File has ${lines.length} lines (1-indexed).`
67681
- );
67682
- }
67683
67890
  const startIndex = start - 1;
67684
67891
  const endIndex = end === -1 ? lines.length : end;
67685
67892
  return {
67686
- path: path10,
67893
+ path: path12,
67687
67894
  sizeBytes: Buffer.byteLength(fileContent),
67688
67895
  totalLines: lines.length,
67689
67896
  content: lines.slice(startIndex, endIndex).join("\n"),
@@ -67691,7 +67898,7 @@ async function readSandboxTextFileWithFallback(sandbox, path10, range) {
67691
67898
  };
67692
67899
  }
67693
67900
  return {
67694
- path: path10,
67901
+ path: path12,
67695
67902
  sizeBytes: Buffer.byteLength(fileContent),
67696
67903
  totalLines: lines.length,
67697
67904
  content: fileContent,
@@ -67699,7 +67906,7 @@ async function readSandboxTextFileWithFallback(sandbox, path10, range) {
67699
67906
  };
67700
67907
  }
67701
67908
  }
67702
- async function appendSandboxTextFile(sandbox, path10, text2) {
67909
+ async function appendSandboxTextFile(sandbox, path12, text2) {
67703
67910
  const tempPath = `/tmp/hackerai_append_${Date.now()}_${Math.random().toString(36).slice(2)}.tmp`;
67704
67911
  await sandbox.files.write(tempPath, text2, {
67705
67912
  user: "user"
@@ -67708,7 +67915,7 @@ async function appendSandboxTextFile(sandbox, path10, text2) {
67708
67915
  sandbox,
67709
67916
  APPEND_TEXT_FILE_SCRIPT,
67710
67917
  {
67711
- HACKERAI_FILE_APPEND_TARGET_PATH: getPythonPathForSandbox(sandbox, path10),
67918
+ HACKERAI_FILE_APPEND_TARGET_PATH: getPythonPathForSandbox(sandbox, path12),
67712
67919
  HACKERAI_FILE_APPEND_SOURCE_PATH: getPythonPathForSandbox(
67713
67920
  sandbox,
67714
67921
  tempPath
@@ -67720,13 +67927,13 @@ async function appendSandboxTextFile(sandbox, path10, text2) {
67720
67927
  throw new Error(result.stderr || result.stdout || "Failed to append file");
67721
67928
  }
67722
67929
  }
67723
- async function readSandboxFileForView(sandbox, path10, includeData) {
67930
+ async function readSandboxFileForView(sandbox, path12, includeData) {
67724
67931
  if (isCentrifugoSandbox(sandbox) && sandbox.isWindows()) {
67725
67932
  throw new Error(
67726
67933
  "The view action is not available for Windows local sandboxes yet. Use a Linux/E2B sandbox or inspect the image manually."
67727
67934
  );
67728
67935
  }
67729
- const sandboxPath = getSandboxViewPath(sandbox, path10);
67936
+ const sandboxPath = getSandboxViewPath(sandbox, path12);
67730
67937
  const viewEnvVars = {
67731
67938
  HACKERAI_FILE_VIEW_PATH: sandboxPath,
67732
67939
  HACKERAI_FILE_VIEW_INCLUDE_DATA: includeData ? "1" : "0",
@@ -67986,8 +68193,9 @@ if range_start == 0 and size > max_full_bytes:
67986
68193
  if range_start > 0:
67987
68194
  if range_start > total_lines:
67988
68195
  emit({"error": f"Invalid start_line: {range_start}. File has {total_lines} lines (1-indexed)."}, 2)
67989
- if range_end != -1 and range_end > total_lines:
67990
- emit({"error": f"Invalid end_line: {range_end}. File has {total_lines} lines (1-indexed)."}, 2)
68196
+ # An end_line past EOF is harmless — the read loop simply stops at the last
68197
+ # line. (We used to error here, which broke the SPRIT chunked read: it always
68198
+ # requests a 100-line window, so every SPRIT file shorter than 100 lines failed.)
67991
68199
 
67992
68200
  selected = []
67993
68201
  selected_bytes = 0
@@ -68069,19 +68277,19 @@ try:
68069
68277
  except OSError:
68070
68278
  pass
68071
68279
  `;
68072
- getFilename = (path10) => path10.split("/").pop() || path10;
68073
- getFileExtension = (path10) => {
68074
- const filename = getFilename(path10);
68280
+ getFilename = (path12) => path12.split("/").pop() || path12;
68281
+ getFileExtension = (path12) => {
68282
+ const filename = getFilename(path12);
68075
68283
  const dotIndex = filename.lastIndexOf(".");
68076
68284
  if (dotIndex <= 0 || dotIndex === filename.length - 1) return void 0;
68077
68285
  return filename.slice(dotIndex + 1).toLowerCase();
68078
68286
  };
68079
- getSandboxViewPath = (sandbox, path10) => {
68287
+ getSandboxViewPath = (sandbox, path12) => {
68080
68288
  const maybeSandbox = sandbox;
68081
- if (isCentrifugoSandbox(maybeSandbox) && maybeSandbox.isWindows() && path10.startsWith("/tmp/")) {
68082
- return `C:\\temp${path10.slice(4).replace(/\//g, "\\")}`;
68289
+ if (isCentrifugoSandbox(maybeSandbox) && maybeSandbox.isWindows() && path12.startsWith("/tmp/")) {
68290
+ return `C:\\temp${path12.slice(4).replace(/\//g, "\\")}`;
68083
68291
  }
68084
- return path10;
68292
+ return path12;
68085
68293
  };
68086
68294
  stripTrailingWs = (line) => line.replace(/[ \t]+$/u, "");
68087
68295
  editSchema = external_exports.object({
@@ -68155,7 +68363,7 @@ ${instructionsDescription}`,
68155
68363
  "A list of edits to be sequentially applied to the file. Required for `edit` action."
68156
68364
  )
68157
68365
  }),
68158
- execute: async ({ action, path: path10, text: text2, range, edits }) => {
68366
+ execute: async ({ action, path: path12, text: text2, range, edits }) => {
68159
68367
  try {
68160
68368
  const { sandbox } = await sandboxManager.getSandbox();
68161
68369
  switch (action) {
@@ -68165,7 +68373,7 @@ ${instructionsDescription}`,
68165
68373
  captureFileViewImageUsage({
68166
68374
  context: context2,
68167
68375
  sandbox,
68168
- path: path10,
68376
+ path: path12,
68169
68377
  outcome: "unsupported_model",
68170
68378
  durationMs: Date.now() - viewStartedAt,
68171
68379
  failureReason: "unsupported_model"
@@ -68174,26 +68382,26 @@ ${instructionsDescription}`,
68174
68382
  }
68175
68383
  let viewPayload;
68176
68384
  try {
68177
- viewPayload = await readSandboxFileForView(sandbox, path10, false);
68385
+ viewPayload = await readSandboxFileForView(sandbox, path12, false);
68178
68386
  } catch (error51) {
68179
68387
  captureFileViewImageUsage({
68180
68388
  context: context2,
68181
68389
  sandbox,
68182
- path: path10,
68390
+ path: path12,
68183
68391
  outcome: "inspection_failed",
68184
68392
  durationMs: Date.now() - viewStartedAt,
68185
68393
  failureReason: classifyFileViewError(error51)
68186
68394
  });
68187
68395
  throw error51;
68188
68396
  }
68189
- const filename = getFilename(path10);
68397
+ const filename = getFilename(path12);
68190
68398
  let previewFiles = [];
68191
68399
  let previewUploadError;
68192
68400
  try {
68193
68401
  previewFiles = await uploadViewPreviewFiles({
68194
68402
  context: context2,
68195
68403
  sandbox,
68196
- sourcePath: path10,
68404
+ sourcePath: path12,
68197
68405
  payload: viewPayload
68198
68406
  });
68199
68407
  } catch (error51) {
@@ -68207,7 +68415,7 @@ ${instructionsDescription}`,
68207
68415
  user_id: context2.userID,
68208
68416
  sandbox_type: getViewSandboxType(sandbox),
68209
68417
  file_name: filename,
68210
- source_path: path10,
68418
+ source_path: path12,
68211
68419
  kind: viewPayload.kind,
68212
68420
  media_type: viewPayload.mediaType,
68213
68421
  size_bytes: viewPayload.sizeBytes,
@@ -68218,7 +68426,7 @@ ${instructionsDescription}`,
68218
68426
  captureFileViewImageUsage({
68219
68427
  context: context2,
68220
68428
  sandbox,
68221
- path: path10,
68429
+ path: path12,
68222
68430
  outcome: "success",
68223
68431
  durationMs: Date.now() - viewStartedAt,
68224
68432
  mediaType: viewPayload.mediaType,
@@ -68228,7 +68436,7 @@ ${instructionsDescription}`,
68228
68436
  return {
68229
68437
  action: "view",
68230
68438
  content: `Viewing image file: ${filename} (${viewPayload.mediaType}, ${viewPayload.sizeBytes} bytes).`,
68231
- path: path10,
68439
+ path: path12,
68232
68440
  filename,
68233
68441
  mediaType: viewPayload.mediaType,
68234
68442
  sizeBytes: viewPayload.sizeBytes,
@@ -68238,8 +68446,8 @@ ${instructionsDescription}`,
68238
68446
  };
68239
68447
  }
68240
68448
  case "read": {
68241
- const filename = path10.split("/").pop() || path10;
68242
- const spritChunked = isSpritPath(path10);
68449
+ const filename = path12.split("/").pop() || path12;
68450
+ const spritChunked = isSpritPath(path12);
68243
68451
  let effectiveRange = range;
68244
68452
  if (spritChunked) {
68245
68453
  const start = range && range[0] > 0 ? range[0] : 1;
@@ -68252,7 +68460,7 @@ ${instructionsDescription}`,
68252
68460
  }
68253
68461
  const readPayload = await readSandboxTextFileWithFallback(
68254
68462
  sandbox,
68255
- path10,
68463
+ path12,
68256
68464
  effectiveRange
68257
68465
  );
68258
68466
  if (readPayload.tooLarge) {
@@ -68289,46 +68497,46 @@ File is too large to read in full (${formatBytes(readPayload.sizeBytes)}, ${tota
68289
68497
  if (text2 === void 0) {
68290
68498
  return { error: "text is required for write action" };
68291
68499
  }
68292
- await sandbox.files.write(path10, text2, {
68500
+ await sandbox.files.write(path12, text2, {
68293
68501
  user: "user"
68294
68502
  });
68295
- return `File written: ${path10}`;
68503
+ return `File written: ${path12}`;
68296
68504
  }
68297
68505
  case "append": {
68298
68506
  if (text2 === void 0) {
68299
68507
  return { error: "text is required for append action" };
68300
68508
  }
68301
- const existingState = await getSandboxFileState(sandbox, path10);
68509
+ const existingState = await getSandboxFileState(sandbox, path12);
68302
68510
  if (existingState.kind === "unknown") {
68303
68511
  return {
68304
- error: `Cannot append safely because the existing file size could not be determined for ${path10}. ${existingState.error}`
68512
+ error: `Cannot append safely because the existing file size could not be determined for ${path12}. ${existingState.error}`
68305
68513
  };
68306
68514
  }
68307
68515
  if (existingState.kind === "not_file") {
68308
68516
  return {
68309
- error: `Cannot append to ${path10} because it is not a file.`
68517
+ error: `Cannot append to ${path12} because it is not a file.`
68310
68518
  };
68311
68519
  }
68312
68520
  if (existingState.kind === "file" && existingState.sizeBytes > MAX_TEXT_FILE_READ_BYTES) {
68313
- await appendSandboxTextFile(sandbox, path10, text2);
68521
+ await appendSandboxTextFile(sandbox, path12, text2);
68314
68522
  return {
68315
- content: `File appended: ${path10}
68523
+ content: `File appended: ${path12}
68316
68524
  Existing file is ${formatBytes(existingState.sizeBytes)}, so the full diff preview was skipped to avoid loading the entire file into memory.`
68317
68525
  };
68318
68526
  }
68319
68527
  let existingContent = "";
68320
68528
  try {
68321
- existingContent = await sandbox.files.read(path10, {
68529
+ existingContent = await sandbox.files.read(path12, {
68322
68530
  user: "user"
68323
68531
  });
68324
68532
  } catch {
68325
68533
  }
68326
68534
  const newContent = existingContent + text2;
68327
- await sandbox.files.write(path10, newContent, {
68535
+ await sandbox.files.write(path12, newContent, {
68328
68536
  user: "user"
68329
68537
  });
68330
68538
  return {
68331
- content: `File appended: ${path10}`,
68539
+ content: `File appended: ${path12}`,
68332
68540
  originalContent: truncateOutput({
68333
68541
  content: existingContent,
68334
68542
  mode: "read-file"
@@ -68343,31 +68551,31 @@ Existing file is ${formatBytes(existingState.sizeBytes)}, so the full diff previ
68343
68551
  if (!edits || edits.length === 0) {
68344
68552
  return { error: "edits array is required for edit action" };
68345
68553
  }
68346
- const existingState = await getSandboxFileState(sandbox, path10);
68554
+ const existingState = await getSandboxFileState(sandbox, path12);
68347
68555
  if (existingState.kind === "unknown") {
68348
68556
  return {
68349
- error: `Cannot edit ${path10} safely because the file size could not be determined. ${existingState.error}`
68557
+ error: `Cannot edit ${path12} safely because the file size could not be determined. ${existingState.error}`
68350
68558
  };
68351
68559
  }
68352
68560
  if (existingState.kind === "missing") {
68353
68561
  return {
68354
- error: `Cannot edit file ${path10} - file is empty or does not exist`
68562
+ error: `Cannot edit file ${path12} - file is empty or does not exist`
68355
68563
  };
68356
68564
  }
68357
68565
  if (existingState.kind === "not_file") {
68358
- return { error: `Cannot edit ${path10} because it is not a file.` };
68566
+ return { error: `Cannot edit ${path12} because it is not a file.` };
68359
68567
  }
68360
68568
  if (existingState.sizeBytes > MAX_TEXT_FILE_READ_BYTES) {
68361
68569
  return {
68362
- error: `File ${path10} is too large for the edit action (${formatBytes(existingState.sizeBytes)}). Use a targeted shell command, restore the file from a clean source, or replace it with the write action instead of loading the whole file into memory.`
68570
+ error: `File ${path12} is too large for the edit action (${formatBytes(existingState.sizeBytes)}). Use a targeted shell command, restore the file from a clean source, or replace it with the write action instead of loading the whole file into memory.`
68363
68571
  };
68364
68572
  }
68365
- const originalContent = await sandbox.files.read(path10, {
68573
+ const originalContent = await sandbox.files.read(path12, {
68366
68574
  user: "user"
68367
68575
  });
68368
68576
  if (!originalContent) {
68369
68577
  return {
68370
- error: `Cannot edit file ${path10} - file is empty or does not exist`
68578
+ error: `Cannot edit file ${path12} - file is empty or does not exist`
68371
68579
  };
68372
68580
  }
68373
68581
  const resolvedEdits = [];
@@ -68412,7 +68620,7 @@ ${hint}` : "")
68412
68620
  }
68413
68621
  }
68414
68622
  }
68415
- await sandbox.files.write(path10, content, {
68623
+ await sandbox.files.write(path12, content, {
68416
68624
  user: "user"
68417
68625
  });
68418
68626
  const lines = content.split("\n");
@@ -68504,6 +68712,1120 @@ ${numberedLines}`,
68504
68712
  }
68505
68713
  });
68506
68714
 
68715
+ // ../lib/utils/error-redaction.ts
68716
+ var REDACTED_VALUE, SENSITIVE_FIELD_PATTERN, ENV_SECRET_PATTERN, redactSensitiveErrorMessage, stringifyRedactedError;
68717
+ var init_error_redaction = __esm({
68718
+ "../lib/utils/error-redaction.ts"() {
68719
+ "use strict";
68720
+ REDACTED_VALUE = "[Redacted]";
68721
+ SENSITIVE_FIELD_PATTERN = /(["']?\b(?:serviceKey|service_key|apiKey|api_key|authorization|bearer|cookie|password|secret|token)\b["']?)(\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,}]+)/gi;
68722
+ ENV_SECRET_PATTERN = /(["']?\b(?:CONVEX_SERVICE_ROLE_KEY|POSTHOG_API_KEY|STRIPE_SECRET_KEY)\b["']?)(\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,}]+)/gi;
68723
+ redactSensitiveErrorMessage = (message) => message.replace(SENSITIVE_FIELD_PATTERN, (_match, key, separator) => {
68724
+ return `${key}${separator}"${REDACTED_VALUE}"`;
68725
+ }).replace(ENV_SECRET_PATTERN, (_match, key, separator) => {
68726
+ return `${key}${separator}"${REDACTED_VALUE}"`;
68727
+ });
68728
+ stringifyRedactedError = (error51) => {
68729
+ const message = error51 instanceof Error ? error51.message : typeof error51 === "string" ? error51 : (() => {
68730
+ try {
68731
+ return JSON.stringify(error51);
68732
+ } catch {
68733
+ return String(error51);
68734
+ }
68735
+ })();
68736
+ return redactSensitiveErrorMessage(message);
68737
+ };
68738
+ }
68739
+ });
68740
+
68741
+ // ../lib/ai/tools/utils/perplexity.ts
68742
+ var SEARCH_RESULT_CONTENT_MAX_TOKENS, RECENCY_MAP, PerplexityApiError, ERROR_BODY_SUMMARY_MAX_LENGTH, RETRYABLE_PERPLEXITY_STATUSES, HTML_ENTITY_MAP, normalizeWhitespace, decodeBasicHtmlEntities, stripHtml, extractHtmlTagText, redactNetworkDetails, truncateSummary, isRetryablePerplexityStatus, summarizePerplexityErrorBody, buildPerplexitySearchBody, formatSearchResults;
68743
+ var init_perplexity = __esm({
68744
+ "../lib/ai/tools/utils/perplexity.ts"() {
68745
+ "use strict";
68746
+ SEARCH_RESULT_CONTENT_MAX_TOKENS = 250;
68747
+ RECENCY_MAP = {
68748
+ past_day: "day",
68749
+ past_week: "week",
68750
+ past_month: "month",
68751
+ past_year: "year"
68752
+ };
68753
+ PerplexityApiError = class extends Error {
68754
+ constructor({
68755
+ status,
68756
+ statusText,
68757
+ bodySummary,
68758
+ retryable
68759
+ }) {
68760
+ const statusLabel = statusText ? `${status} ${statusText}` : `${status}`;
68761
+ const summary = bodySummary ? `: ${bodySummary}` : "";
68762
+ super(`Perplexity API error ${statusLabel}${summary}`);
68763
+ this.name = "PerplexityApiError";
68764
+ this.status = status;
68765
+ this.statusText = statusText;
68766
+ this.bodySummary = bodySummary;
68767
+ this.retryable = retryable;
68768
+ }
68769
+ };
68770
+ ERROR_BODY_SUMMARY_MAX_LENGTH = 400;
68771
+ RETRYABLE_PERPLEXITY_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
68772
+ HTML_ENTITY_MAP = {
68773
+ amp: "&",
68774
+ gt: ">",
68775
+ lt: "<",
68776
+ nbsp: " ",
68777
+ quot: '"',
68778
+ "#160": " ",
68779
+ "#39": "'"
68780
+ };
68781
+ normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
68782
+ decodeBasicHtmlEntities = (value) => value.replace(/&([a-zA-Z0-9#]+);/g, (match, entity) => {
68783
+ return HTML_ENTITY_MAP[entity] ?? match;
68784
+ });
68785
+ stripHtml = (value) => normalizeWhitespace(
68786
+ decodeBasicHtmlEntities(
68787
+ value.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ")
68788
+ )
68789
+ );
68790
+ extractHtmlTagText = (html, tagName) => {
68791
+ const matches = html.matchAll(
68792
+ new RegExp(`<${tagName}\\b[^>]*>([\\s\\S]*?)<\\/${tagName}>`, "gi")
68793
+ );
68794
+ return Array.from(matches).map((match) => stripHtml(match[1] ?? "")).filter(Boolean);
68795
+ };
68796
+ redactNetworkDetails = (value) => value.replace(
68797
+ /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g,
68798
+ "[Redacted IP]"
68799
+ ).replace(/\bRay ID:\s*[a-f0-9]+\b/gi, "Ray ID: [Redacted]");
68800
+ truncateSummary = (value) => value.length > ERROR_BODY_SUMMARY_MAX_LENGTH ? `${value.slice(0, ERROR_BODY_SUMMARY_MAX_LENGTH - 1)}\u2026` : value;
68801
+ isRetryablePerplexityStatus = (status) => RETRYABLE_PERPLEXITY_STATUSES.has(status);
68802
+ summarizePerplexityErrorBody = (body, contentType = "") => {
68803
+ const trimmed = body.trim();
68804
+ if (!trimmed) return "";
68805
+ let summary = "";
68806
+ if (contentType.includes("json") || trimmed.startsWith("{")) {
68807
+ try {
68808
+ const parsed = JSON.parse(trimmed);
68809
+ const error51 = typeof parsed.error === "string" ? parsed.error : parsed.error && typeof parsed.error === "object" && "message" in parsed.error && typeof parsed.error.message === "string" ? parsed.error.message : void 0;
68810
+ const message = error51 || (typeof parsed.message === "string" ? parsed.message : void 0) || (typeof parsed.detail === "string" ? parsed.detail : void 0);
68811
+ summary = message || trimmed;
68812
+ } catch {
68813
+ summary = trimmed;
68814
+ }
68815
+ } else if (/<[a-z][\s\S]*>/i.test(trimmed)) {
68816
+ const tagSummaries = [
68817
+ ...extractHtmlTagText(trimmed, "h1"),
68818
+ ...extractHtmlTagText(trimmed, "p"),
68819
+ ...extractHtmlTagText(trimmed, "h2")
68820
+ ];
68821
+ summary = tagSummaries.length > 0 ? tagSummaries.join(". ") : stripHtml(trimmed);
68822
+ } else {
68823
+ summary = trimmed;
68824
+ }
68825
+ return truncateSummary(redactNetworkDetails(normalizeWhitespace(summary)));
68826
+ };
68827
+ buildPerplexitySearchBody = (query, options) => {
68828
+ const searchBody = {
68829
+ query,
68830
+ max_results: options?.maxResults ?? 10,
68831
+ max_tokens_per_page: SEARCH_RESULT_CONTENT_MAX_TOKENS
68832
+ };
68833
+ if (options?.country) {
68834
+ searchBody.country = options.country;
68835
+ }
68836
+ if (options?.recency) {
68837
+ searchBody.search_recency_filter = options.recency;
68838
+ }
68839
+ return searchBody;
68840
+ };
68841
+ formatSearchResults = (results) => {
68842
+ return results.map((result) => ({
68843
+ title: result.title,
68844
+ url: result.url,
68845
+ content: result.snippet,
68846
+ date: result.date || null,
68847
+ lastUpdated: result.last_updated || null
68848
+ }));
68849
+ };
68850
+ }
68851
+ });
68852
+
68853
+ // ../lib/ai/tools/web-search.ts
68854
+ var WEB_SEARCH_COST_PER_REQUEST, PERPLEXITY_SEARCH_URL, WEB_SEARCH_MAX_ATTEMPTS, WEB_SEARCH_RETRY_BASE_DELAY_MS, WEB_SEARCH_RETRY_JITTER_MS, PERPLEXITY_QUERY_MAX_LENGTH, EMPTY_QUERY_TOOL_ERROR, QUERY_TOO_LONG_TOOL_ERROR, webSearchQuerySchema, sleep, getRetryDelayMs, createPerplexityApiError, formatPerplexityFailureForTool, fetchPerplexitySearch, normalizeSearchQueries, createWebSearch;
68855
+ var init_web_search = __esm({
68856
+ "../lib/ai/tools/web-search.ts"() {
68857
+ "use strict";
68858
+ init_dist5();
68859
+ init_zod();
68860
+ init_error_redaction();
68861
+ init_perplexity();
68862
+ WEB_SEARCH_COST_PER_REQUEST = 5e-3;
68863
+ PERPLEXITY_SEARCH_URL = "https://api.perplexity.ai/search";
68864
+ WEB_SEARCH_MAX_ATTEMPTS = 3;
68865
+ WEB_SEARCH_RETRY_BASE_DELAY_MS = 300;
68866
+ WEB_SEARCH_RETRY_JITTER_MS = 75;
68867
+ PERPLEXITY_QUERY_MAX_LENGTH = 8192;
68868
+ EMPTY_QUERY_TOOL_ERROR = "Error performing web search: Provide at least one non-empty query.";
68869
+ QUERY_TOO_LONG_TOOL_ERROR = `Error performing web search: Each query must be ${PERPLEXITY_QUERY_MAX_LENGTH} characters or fewer.`;
68870
+ webSearchQuerySchema = external_exports.string().trim().min(1).max(PERPLEXITY_QUERY_MAX_LENGTH);
68871
+ sleep = (delayMs, signal) => {
68872
+ if (delayMs <= 0) return Promise.resolve();
68873
+ if (signal?.aborted) {
68874
+ return Promise.reject(new DOMException("Operation aborted", "AbortError"));
68875
+ }
68876
+ return new Promise((resolve2, reject) => {
68877
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
68878
+ const onAbort = () => {
68879
+ clearTimeout(timeout);
68880
+ cleanup();
68881
+ reject(new DOMException("Operation aborted", "AbortError"));
68882
+ };
68883
+ const timeout = setTimeout(() => {
68884
+ cleanup();
68885
+ resolve2();
68886
+ }, delayMs);
68887
+ signal?.addEventListener("abort", onAbort, { once: true });
68888
+ });
68889
+ };
68890
+ getRetryDelayMs = (attemptIndex) => {
68891
+ const exponentialDelay = WEB_SEARCH_RETRY_BASE_DELAY_MS * Math.pow(2, attemptIndex);
68892
+ const jitter = Math.random() * WEB_SEARCH_RETRY_JITTER_MS;
68893
+ return Math.round(exponentialDelay + jitter);
68894
+ };
68895
+ createPerplexityApiError = async (response) => {
68896
+ const errorText = await response.text();
68897
+ const bodySummary = summarizePerplexityErrorBody(
68898
+ errorText,
68899
+ response.headers.get("content-type") || ""
68900
+ );
68901
+ return new PerplexityApiError({
68902
+ status: response.status,
68903
+ statusText: response.statusText,
68904
+ bodySummary,
68905
+ retryable: isRetryablePerplexityStatus(response.status)
68906
+ });
68907
+ };
68908
+ formatPerplexityFailureForTool = (error51, attempts) => {
68909
+ const statusText = error51.statusText ? ` ${error51.statusText}` : "";
68910
+ if (error51.retryable) {
68911
+ return `Error performing web search: Perplexity search is temporarily unavailable (HTTP ${error51.status}${statusText} after ${attempts} attempts). Please retry shortly or continue without live web results if the task can proceed.`;
68912
+ }
68913
+ if (error51.status === 401 || error51.status === 403) {
68914
+ return `Error performing web search: Perplexity search is not authorized (HTTP ${error51.status}${statusText}). Check the Perplexity API key or account access.`;
68915
+ }
68916
+ return `Error performing web search: Perplexity search failed (HTTP ${error51.status}${statusText}).`;
68917
+ };
68918
+ fetchPerplexitySearch = async (searchBody, abortSignal) => {
68919
+ for (let attemptIndex = 0; attemptIndex < WEB_SEARCH_MAX_ATTEMPTS; attemptIndex++) {
68920
+ const attempt = attemptIndex + 1;
68921
+ const isFinalAttempt = attempt === WEB_SEARCH_MAX_ATTEMPTS;
68922
+ try {
68923
+ const response = await fetch(PERPLEXITY_SEARCH_URL, {
68924
+ method: "POST",
68925
+ headers: {
68926
+ "Content-Type": "application/json",
68927
+ Authorization: `Bearer ${process.env.PERPLEXITY_API_KEY || ""}`
68928
+ },
68929
+ body: JSON.stringify(searchBody),
68930
+ signal: abortSignal
68931
+ });
68932
+ if (response.ok) {
68933
+ return response;
68934
+ }
68935
+ const error51 = await createPerplexityApiError(response);
68936
+ if (!error51.retryable || isFinalAttempt) {
68937
+ throw error51;
68938
+ }
68939
+ const delayMs = getRetryDelayMs(attemptIndex);
68940
+ console.warn("Web search provider error; retrying", {
68941
+ attempt,
68942
+ maxAttempts: WEB_SEARCH_MAX_ATTEMPTS,
68943
+ status: error51.status,
68944
+ statusText: error51.statusText,
68945
+ bodySummary: error51.bodySummary,
68946
+ delayMs
68947
+ });
68948
+ await sleep(delayMs, abortSignal);
68949
+ } catch (error51) {
68950
+ if (error51 instanceof Error && error51.name === "AbortError") {
68951
+ throw error51;
68952
+ }
68953
+ if (error51 instanceof PerplexityApiError) {
68954
+ throw error51;
68955
+ }
68956
+ if (isFinalAttempt) {
68957
+ throw error51;
68958
+ }
68959
+ const delayMs = getRetryDelayMs(attemptIndex);
68960
+ console.warn("Web search network error; retrying", {
68961
+ attempt,
68962
+ maxAttempts: WEB_SEARCH_MAX_ATTEMPTS,
68963
+ error: stringifyRedactedError(error51),
68964
+ delayMs
68965
+ });
68966
+ await sleep(delayMs, abortSignal);
68967
+ }
68968
+ }
68969
+ throw new Error("Web search failed before any Perplexity response was read");
68970
+ };
68971
+ normalizeSearchQueries = (rawQueries) => {
68972
+ const queries = rawQueries.map((query) => query.trim()).filter(Boolean);
68973
+ if (queries.length === 0) {
68974
+ return { queries, error: EMPTY_QUERY_TOOL_ERROR };
68975
+ }
68976
+ if (queries.some((query) => query.length > PERPLEXITY_QUERY_MAX_LENGTH)) {
68977
+ return { queries, error: QUERY_TOO_LONG_TOOL_ERROR };
68978
+ }
68979
+ return { queries: queries.slice(0, 3) };
68980
+ };
68981
+ createWebSearch = (context2) => {
68982
+ const { userLocation, onToolCost } = context2;
68983
+ return tool({
68984
+ description: `Search for information across various sources.
68985
+
68986
+ <instructions>
68987
+ - MUST use this tool to access up-to-date or external information when needed; DO NOT rely solely on internal knowledge
68988
+ - Each search MUST contain exactly 1 to 3 \`queries\` (NEVER more than 3). Queries MUST be variants of the same intent (i.e., query expansions), NOT different goals
68989
+ - For non-English queries, MUST include at least one English query as the final variant to expand coverage
68990
+ - For complex searches, MUST break down into step-by-step searches instead of using a single complex query
68991
+ - Access multiple URLs from search results for comprehensive information or cross-validation
68992
+ - CAN use Google dork syntax (site:, filetype:, inurl:, intitle:, etc.) for targeted reconnaissance and pentest enumeration
68993
+ - Only use \`time\` parameter when explicitly required by task, otherwise leave time range unrestricted
68994
+ - Prioritize cybersecurity-relevant information: CVEs, CVSS scores, exploits, PoCs, security tools, and pentest methodologies
68995
+ - Include specific versions, configurations, and technical details; cite reliable sources (NIST, OWASP, CVE databases)
68996
+ - For commands/installations, prioritize Kali Linux compatibility using apt or pre-installed tools
68997
+ </instructions>`,
68998
+ inputSchema: external_exports.object({
68999
+ queries: external_exports.array(webSearchQuerySchema).min(1).max(3).describe(
69000
+ "MAXIMUM 3 non-empty query variants (1-3 items only). Express the same search intent with different wording."
69001
+ ),
69002
+ time: external_exports.enum(["all", "past_day", "past_week", "past_month", "past_year"]).optional().describe(
69003
+ "Optional time filter to limit results to a recent time range"
69004
+ ),
69005
+ brief: external_exports.string().describe(
69006
+ "A one-sentence preamble describing the purpose of this operation"
69007
+ )
69008
+ }),
69009
+ execute: async ({
69010
+ queries: rawQueries,
69011
+ time: time3
69012
+ }, { abortSignal }) => {
69013
+ try {
69014
+ const { queries, error: error51 } = normalizeSearchQueries(rawQueries);
69015
+ if (error51) {
69016
+ return error51;
69017
+ }
69018
+ const searchBody = buildPerplexitySearchBody(
69019
+ queries.length === 1 ? queries[0] : queries,
69020
+ {
69021
+ country: userLocation?.country,
69022
+ recency: time3 && time3 !== "all" ? RECENCY_MAP[time3] : void 0
69023
+ }
69024
+ );
69025
+ const response = await fetchPerplexitySearch(searchBody, abortSignal);
69026
+ onToolCost?.(WEB_SEARCH_COST_PER_REQUEST);
69027
+ const searchResponse = await response.json();
69028
+ const isMultiQuery = queries.length > 1;
69029
+ let allResults;
69030
+ if (isMultiQuery && Array.isArray(searchResponse.results[0])) {
69031
+ allResults = searchResponse.results.flat();
69032
+ } else {
69033
+ allResults = searchResponse.results;
69034
+ }
69035
+ return formatSearchResults(allResults);
69036
+ } catch (error51) {
69037
+ if (error51 instanceof Error && error51.name === "AbortError") {
69038
+ return "Error: Operation aborted";
69039
+ }
69040
+ if (error51 instanceof PerplexityApiError) {
69041
+ console.error("Web search tool error:", {
69042
+ name: error51.name,
69043
+ status: error51.status,
69044
+ statusText: error51.statusText,
69045
+ retryable: error51.retryable,
69046
+ bodySummary: error51.bodySummary
69047
+ });
69048
+ return formatPerplexityFailureForTool(
69049
+ error51,
69050
+ error51.retryable ? WEB_SEARCH_MAX_ATTEMPTS : 1
69051
+ );
69052
+ }
69053
+ const errorMessage = stringifyRedactedError(error51);
69054
+ console.error("Web search tool error:", errorMessage);
69055
+ return `Error performing web search: ${errorMessage}`;
69056
+ }
69057
+ }
69058
+ });
69059
+ };
69060
+ }
69061
+ });
69062
+
69063
+ // ../lib/ai/tools/open-url.ts
69064
+ var createOpenUrlTool;
69065
+ var init_open_url = __esm({
69066
+ "../lib/ai/tools/open-url.ts"() {
69067
+ "use strict";
69068
+ init_dist5();
69069
+ init_zod();
69070
+ init_token_utils();
69071
+ createOpenUrlTool = () => {
69072
+ return tool({
69073
+ description: `Retrieve the full contents of a specific webpage by URL.
69074
+
69075
+ <instructions>
69076
+ - Use to fetch and read a specific webpage, usually obtained from a prior search
69077
+ - URLs must be valid and publicly accessible
69078
+ - Prioritize cybersecurity-relevant information: CVEs, CVSS scores, exploits, PoCs, security tools, and pentest methodologies
69079
+ - Include specific versions, configurations, and technical details; cite reliable sources (NIST, OWASP, CVE databases)
69080
+ </instructions>`,
69081
+ inputSchema: external_exports.object({
69082
+ url: external_exports.string().describe("The URL to open and retrieve content from"),
69083
+ brief: external_exports.string().describe(
69084
+ "A one-sentence preamble describing the purpose of this operation"
69085
+ )
69086
+ }),
69087
+ execute: async ({ url: url2 }, { abortSignal }) => {
69088
+ try {
69089
+ const jinaUrl = `https://r.jina.ai/${encodeURIComponent(url2)}`;
69090
+ const response = await fetch(jinaUrl, {
69091
+ method: "GET",
69092
+ headers: {
69093
+ Authorization: `Bearer ${process.env.JINA_API_KEY}`,
69094
+ "X-Timeout": "30",
69095
+ "X-Base": "final",
69096
+ "X-Token-Budget": "200000"
69097
+ },
69098
+ signal: abortSignal
69099
+ });
69100
+ if (!response.ok) {
69101
+ const errorBody = await response.text();
69102
+ return `Error: HTTP ${response.status} - ${errorBody}`;
69103
+ }
69104
+ const content = await response.text();
69105
+ const truncated = truncateContent(content, void 0, 2048);
69106
+ return truncated;
69107
+ } catch (error51) {
69108
+ if (error51 instanceof Error && error51.name === "AbortError") {
69109
+ return "Error: Operation aborted";
69110
+ }
69111
+ console.error("Open URL tool error:", error51);
69112
+ const errorMessage = error51 instanceof Error ? error51.message : "Unknown error occurred";
69113
+ return `Error opening URL: ${errorMessage}`;
69114
+ }
69115
+ }
69116
+ });
69117
+ };
69118
+ }
69119
+ });
69120
+
69121
+ // src/tools/scope.ts
69122
+ var import_node_fs6, import_node_path5, import_promises, ipv4ToInt, isIpLiteral, maskForPrefix, parseCidr, ScopeImpl, scopeFileFor, loadScope, hostFromUrl;
69123
+ var init_scope = __esm({
69124
+ "src/tools/scope.ts"() {
69125
+ "use strict";
69126
+ import_node_fs6 = __toESM(require("node:fs"));
69127
+ import_node_path5 = __toESM(require("node:path"));
69128
+ import_promises = __toESM(require("node:dns/promises"));
69129
+ ipv4ToInt = (ip) => {
69130
+ const parts = ip.split(".");
69131
+ if (parts.length !== 4) return null;
69132
+ let value = 0;
69133
+ for (const part of parts) {
69134
+ if (!/^\d{1,3}$/.test(part)) return null;
69135
+ const n = Number(part);
69136
+ if (n > 255) return null;
69137
+ value = value * 256 + n;
69138
+ }
69139
+ return value;
69140
+ };
69141
+ isIpLiteral = (host) => ipv4ToInt(host) !== null || host.includes(":");
69142
+ maskForPrefix = (prefix) => prefix === 0 ? 0 : 4294967295 << 32 - prefix >>> 0;
69143
+ parseCidr = (entry) => {
69144
+ const slash = entry.indexOf("/");
69145
+ if (slash === -1) {
69146
+ const v42 = ipv4ToInt(entry);
69147
+ if (v42 !== null) return { base: v42, mask: 4294967295 };
69148
+ return null;
69149
+ }
69150
+ const addr = entry.slice(0, slash);
69151
+ const prefix = Number(entry.slice(slash + 1));
69152
+ const v4 = ipv4ToInt(addr);
69153
+ if (v4 === null) return null;
69154
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return null;
69155
+ const mask = maskForPrefix(prefix);
69156
+ return { base: (v4 & mask) >>> 0, mask };
69157
+ };
69158
+ ScopeImpl = class _ScopeImpl {
69159
+ constructor() {
69160
+ this.hosts = /* @__PURE__ */ new Set();
69161
+ this.wildcards = [];
69162
+ // stored as ".example.com"
69163
+ this.cidrs = [];
69164
+ this.loadedFrom = null;
69165
+ this.entryCount = 0;
69166
+ }
69167
+ static parse(text2, loadedFrom) {
69168
+ const scope = new _ScopeImpl();
69169
+ scope.loadedFrom = loadedFrom;
69170
+ for (const raw of text2.split(/\r?\n/)) {
69171
+ const line = raw.trim();
69172
+ if (!line || line.startsWith("#")) continue;
69173
+ scope.entryCount++;
69174
+ if (line.startsWith("*.")) {
69175
+ scope.wildcards.push(line.slice(1).toLowerCase());
69176
+ continue;
69177
+ }
69178
+ const cidr = parseCidr(line);
69179
+ if (cidr) {
69180
+ scope.cidrs.push(cidr);
69181
+ continue;
69182
+ }
69183
+ scope.hosts.add(line.toLowerCase());
69184
+ }
69185
+ return scope;
69186
+ }
69187
+ isEmpty() {
69188
+ return this.entryCount === 0;
69189
+ }
69190
+ matchesNameRules(host) {
69191
+ if (this.hosts.has(host)) return true;
69192
+ for (const suffix of this.wildcards) {
69193
+ if (host.endsWith(suffix) || host === suffix.replace(/^\./, "")) {
69194
+ return true;
69195
+ }
69196
+ }
69197
+ return false;
69198
+ }
69199
+ async allows(host) {
69200
+ host = (host || "").toLowerCase().trim();
69201
+ if (!host) return false;
69202
+ if (this.matchesNameRules(host)) return true;
69203
+ if (this.cidrs.length === 0) return false;
69204
+ const candidates = [];
69205
+ if (isIpLiteral(host)) {
69206
+ candidates.push(host);
69207
+ } else {
69208
+ try {
69209
+ const records = await import_promises.default.lookup(host, { all: true });
69210
+ for (const r of records) candidates.push(r.address);
69211
+ } catch {
69212
+ return false;
69213
+ }
69214
+ }
69215
+ for (const ip of candidates) {
69216
+ const value = ipv4ToInt(ip);
69217
+ if (value === null) continue;
69218
+ for (const c of this.cidrs) {
69219
+ if ((value & c.mask) >>> 0 === c.base) return true;
69220
+ }
69221
+ }
69222
+ return false;
69223
+ }
69224
+ };
69225
+ scopeFileFor = (workdir) => import_node_path5.default.join(workdir, "recon", "scope.txt");
69226
+ loadScope = (workdir) => {
69227
+ const file2 = scopeFileFor(workdir);
69228
+ try {
69229
+ const text2 = import_node_fs6.default.readFileSync(file2, "utf8");
69230
+ return ScopeImpl.parse(text2, file2);
69231
+ } catch {
69232
+ return ScopeImpl.parse("", null);
69233
+ }
69234
+ };
69235
+ hostFromUrl = (url2) => {
69236
+ try {
69237
+ return new URL(url2).hostname.toLowerCase();
69238
+ } catch {
69239
+ return null;
69240
+ }
69241
+ };
69242
+ }
69243
+ });
69244
+
69245
+ // src/tools/http-request.ts
69246
+ var FUZZ, MAX_FUZZ, BODY_PREVIEW_CHARS, DEFAULT_TIMEOUT_S, LOOPBACK, numberEnv, sleep2, sampleUrl, NOTABLE_HEADERS, applyFuzz, buildInit, collectNotableHeaders, doRequest, summarizeFuzz, scopeRefusal, createHttpRequest;
69247
+ var init_http_request = __esm({
69248
+ "src/tools/http-request.ts"() {
69249
+ "use strict";
69250
+ init_dist5();
69251
+ init_zod();
69252
+ init_scope();
69253
+ FUZZ = "FUZZ";
69254
+ MAX_FUZZ = 500;
69255
+ BODY_PREVIEW_CHARS = 4e3;
69256
+ DEFAULT_TIMEOUT_S = 30;
69257
+ LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"]);
69258
+ numberEnv = (name25) => {
69259
+ const raw = Number(process.env[name25]);
69260
+ return Number.isFinite(raw) && raw >= 0 ? raw : void 0;
69261
+ };
69262
+ sleep2 = (ms, signal) => new Promise((resolve2) => {
69263
+ if (ms <= 0 || signal?.aborted) return resolve2();
69264
+ const t = setTimeout(resolve2, ms);
69265
+ signal?.addEventListener(
69266
+ "abort",
69267
+ () => {
69268
+ clearTimeout(t);
69269
+ resolve2();
69270
+ },
69271
+ { once: true }
69272
+ );
69273
+ });
69274
+ sampleUrl = (url2) => url2.split(FUZZ).join("x");
69275
+ NOTABLE_HEADERS = [
69276
+ "server",
69277
+ "x-powered-by",
69278
+ "location",
69279
+ "set-cookie",
69280
+ "content-type",
69281
+ "content-length",
69282
+ "www-authenticate",
69283
+ "access-control-allow-origin",
69284
+ "x-frame-options",
69285
+ "content-security-policy",
69286
+ "strict-transport-security"
69287
+ ];
69288
+ applyFuzz = (template, payload) => template == null ? template : template.split(FUZZ).join(payload);
69289
+ buildInit = (method, headers, body, followRedirects, timeoutSignal) => {
69290
+ const init = {
69291
+ method,
69292
+ redirect: followRedirects ? "follow" : "manual",
69293
+ signal: timeoutSignal
69294
+ };
69295
+ if (headers && Object.keys(headers).length) init.headers = headers;
69296
+ if (body != null && method !== "GET" && method !== "HEAD") init.body = body;
69297
+ return init;
69298
+ };
69299
+ collectNotableHeaders = (h) => {
69300
+ const out3 = {};
69301
+ for (const name25 of NOTABLE_HEADERS) {
69302
+ const value = h.get(name25);
69303
+ if (value != null) out3[name25] = value;
69304
+ }
69305
+ return out3;
69306
+ };
69307
+ doRequest = async (url2, method, headers, body, followRedirects, timeoutMs, parentSignal) => {
69308
+ const ctrl = new AbortController();
69309
+ const onAbort = () => ctrl.abort();
69310
+ parentSignal?.addEventListener("abort", onAbort, { once: true });
69311
+ const timer2 = setTimeout(() => ctrl.abort(), timeoutMs);
69312
+ const started2 = Date.now();
69313
+ try {
69314
+ const resp = await fetch(
69315
+ url2,
69316
+ buildInit(method, headers, body, followRedirects, ctrl.signal)
69317
+ );
69318
+ const text2 = await resp.text();
69319
+ return {
69320
+ status: resp.status,
69321
+ finalUrl: resp.url || url2,
69322
+ redirected: resp.redirected,
69323
+ timeMs: Date.now() - started2,
69324
+ text: text2,
69325
+ headers: resp.headers
69326
+ };
69327
+ } catch (err) {
69328
+ const timeMs = Date.now() - started2;
69329
+ const msg = parentSignal?.aborted || err instanceof Error && err.name === "AbortError" ? "request aborted/timed out" : err instanceof Error ? err.message : String(err);
69330
+ return { error: msg, timeMs };
69331
+ } finally {
69332
+ clearTimeout(timer2);
69333
+ parentSignal?.removeEventListener("abort", onAbort);
69334
+ }
69335
+ };
69336
+ summarizeFuzz = (rows) => {
69337
+ const ok = rows.filter((r) => r.status !== "ERR");
69338
+ const statusCounts = /* @__PURE__ */ new Map();
69339
+ for (const r of ok) {
69340
+ if (typeof r.status === "number") {
69341
+ statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1);
69342
+ }
69343
+ }
69344
+ let baselineStatus = null;
69345
+ let max = -1;
69346
+ for (const [status, count] of statusCounts) {
69347
+ if (count > max) {
69348
+ max = count;
69349
+ baselineStatus = status;
69350
+ }
69351
+ }
69352
+ const lengths = ok.map((r) => r.bodyLength).sort((a, b) => a - b);
69353
+ const medianLen = lengths.length ? lengths[Math.floor(lengths.length / 2)] : 0;
69354
+ const anomalies = rows.filter((r) => {
69355
+ if (r.status === "ERR") return true;
69356
+ if (baselineStatus != null && r.status !== baselineStatus) return true;
69357
+ if (medianLen > 0 && Math.abs(r.bodyLength - medianLen) > medianLen * 0.25) {
69358
+ return true;
69359
+ }
69360
+ return false;
69361
+ });
69362
+ const lines = [];
69363
+ lines.push(
69364
+ `Fuzzed ${rows.length} payload(s). Baseline status: ${baselineStatus ?? "n/a"} (median body ${medianLen} bytes).`
69365
+ );
69366
+ if (anomalies.length === 0) {
69367
+ lines.push("No anomalies \u2014 every response matched the baseline.");
69368
+ } else {
69369
+ lines.push(`${anomalies.length} anomaly/anomalies worth checking:`);
69370
+ for (const r of anomalies.slice(0, 40)) {
69371
+ lines.push(
69372
+ ` \u2022 ${JSON.stringify(r.payload)} \u2192 ${r.status}` + (r.status === "ERR" ? ` (${r.error ?? "error"})` : ` | ${r.bodyLength} bytes | ${r.timeMs}ms`)
69373
+ );
69374
+ }
69375
+ if (anomalies.length > 40) {
69376
+ lines.push(` \u2026 +${anomalies.length - 40} more`);
69377
+ }
69378
+ }
69379
+ return lines.join("\n");
69380
+ };
69381
+ scopeRefusal = (host, workdir) => `Recusado: o host "${host}" n\xE3o est\xE1 no escopo autorizado. Adicione-o a ${scopeFileFor(workdir)} (formatos: example.com, *.example.com, CIDR 10.0.0.0/24, ou um IP) \u2014 somente alvos que voc\xEA possui ou tem autoriza\xE7\xE3o expl\xEDcita para testar. Edite o scope.txt com a ferramenta file e tente de novo.`;
69382
+ createHttpRequest = (deps) => {
69383
+ const { workdir, loadScopeFn = loadScope } = deps;
69384
+ const envThrottle = numberEnv("HACKERAI_HTTP_THROTTLE_MS");
69385
+ const envJitter = numberEnv("HACKERAI_HTTP_JITTER_MS");
69386
+ return tool({
69387
+ description: `Send a fully-controlled HTTP request (a native Repeater) and, with \`fuzz\`, an Intruder. Use this to iterate on a web vulnerability inside the agent loop instead of writing a one-off script for every probe.
69388
+
69389
+ WHAT IT DOES
69390
+ - Single request: full control of method, headers, body and redirect handling; returns status, timing, notable security headers, body length and a body preview.
69391
+ - Intruder/fuzz: put the marker \`FUZZ\` in the url, body, or any header value and pass \`fuzz\` (a list of payloads). Each payload is substituted, sent sequentially with OPSEC throttle+jitter, and the results are diffed against a baseline so anomalies (auth bypass, IDOR, injection, hidden paths/params) are highlighted.
69392
+
69393
+ SCOPE: every request is gated by recon/scope.txt (same allowlist as the recon toolkit). Out-of-scope hosts are refused; loopback is always allowed. Add authorized targets to scope.txt first.
69394
+
69395
+ USE FOR: testing a single endpoint, replaying/tampering a captured request, parameter/path/value fuzzing, verifying an exploit by observing the real response. NOT a replacement for bulk crawling \u2014 use the Python recon toolkit for that.`,
69396
+ inputSchema: external_exports.object({
69397
+ brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this request."),
69398
+ url: external_exports.string().url().describe(
69399
+ "Target URL. May contain the marker FUZZ (replaced by each fuzz payload)."
69400
+ ),
69401
+ method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).optional().describe("HTTP method (default GET)."),
69402
+ headers: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("Request headers. Values may contain FUZZ."),
69403
+ body: external_exports.string().optional().describe("Request body for POST/PUT/PATCH/DELETE. May contain FUZZ."),
69404
+ follow_redirects: external_exports.boolean().optional().describe("Follow 3xx redirects (default true). Set false to inspect Location."),
69405
+ timeout: external_exports.number().optional().describe(`Per-request timeout in seconds (default ${DEFAULT_TIMEOUT_S}).`),
69406
+ fuzz: external_exports.array(external_exports.string()).optional().describe(
69407
+ `Intruder payloads \u2014 substituted into the FUZZ marker, one request each (max ${MAX_FUZZ}).`
69408
+ ),
69409
+ throttle_ms: external_exports.number().optional().describe("OPSEC: delay between fuzz requests in ms (overrides HACKERAI_HTTP_THROTTLE_MS)."),
69410
+ jitter_ms: external_exports.number().optional().describe("OPSEC: random extra delay 0..jitter added to each throttle.")
69411
+ }),
69412
+ execute: async (input, { abortSignal } = {}) => {
69413
+ const method = input.method ?? "GET";
69414
+ const followRedirects = input.follow_redirects ?? true;
69415
+ const timeoutMs = Math.max(1, input.timeout ?? DEFAULT_TIMEOUT_S) * 1e3;
69416
+ const host = hostFromUrl(sampleUrl(input.url));
69417
+ if (!host) {
69418
+ return { error: `URL inv\xE1lida: ${input.url}` };
69419
+ }
69420
+ if (!LOOPBACK.has(host)) {
69421
+ const scope = loadScopeFn(workdir);
69422
+ const allowed = await scope.allows(host);
69423
+ if (!allowed) {
69424
+ return { error: scopeRefusal(host, workdir) };
69425
+ }
69426
+ }
69427
+ const payloads = input.fuzz ?? [];
69428
+ if (payloads.length > 0) {
69429
+ const templateHasMarker = input.url.includes(FUZZ) || (input.body?.includes(FUZZ) ?? false) || Object.values(input.headers ?? {}).some((v) => v.includes(FUZZ));
69430
+ if (!templateHasMarker) {
69431
+ return {
69432
+ error: "fuzz foi fornecido mas nenhum marcador FUZZ existe na url, body ou headers. Coloque FUZZ onde os payloads devem entrar."
69433
+ };
69434
+ }
69435
+ const list = payloads.slice(0, MAX_FUZZ);
69436
+ const throttle = input.throttle_ms ?? envThrottle ?? 0;
69437
+ const jitter = input.jitter_ms ?? envJitter ?? 0;
69438
+ const rows = [];
69439
+ for (let i = 0; i < list.length; i++) {
69440
+ if (abortSignal?.aborted) break;
69441
+ const payload = list[i];
69442
+ const url2 = applyFuzz(input.url, payload);
69443
+ const body = applyFuzz(input.body, payload);
69444
+ const headers = input.headers ? Object.fromEntries(
69445
+ Object.entries(input.headers).map(([k, v]) => [
69446
+ k,
69447
+ applyFuzz(v, payload)
69448
+ ])
69449
+ ) : void 0;
69450
+ const res2 = await doRequest(
69451
+ url2,
69452
+ method,
69453
+ headers,
69454
+ body,
69455
+ followRedirects,
69456
+ timeoutMs,
69457
+ abortSignal
69458
+ );
69459
+ if ("error" in res2) {
69460
+ rows.push({ payload, status: "ERR", bodyLength: 0, timeMs: res2.timeMs, error: res2.error });
69461
+ } else {
69462
+ rows.push({
69463
+ payload,
69464
+ status: res2.status,
69465
+ bodyLength: res2.text.length,
69466
+ timeMs: res2.timeMs,
69467
+ finalUrl: res2.finalUrl
69468
+ });
69469
+ }
69470
+ if (i < list.length - 1 && (throttle > 0 || jitter > 0)) {
69471
+ await sleep2(throttle + Math.floor(Math.random() * (jitter + 1)), abortSignal);
69472
+ }
69473
+ }
69474
+ return { fuzz: { host, count: rows.length, rows } };
69475
+ }
69476
+ const res = await doRequest(
69477
+ input.url,
69478
+ method,
69479
+ input.headers,
69480
+ input.body,
69481
+ followRedirects,
69482
+ timeoutMs,
69483
+ abortSignal
69484
+ );
69485
+ if ("error" in res) {
69486
+ const single2 = {
69487
+ status: 0,
69488
+ ok: false,
69489
+ finalUrl: input.url,
69490
+ redirected: false,
69491
+ timeMs: res.timeMs,
69492
+ bodyLength: 0,
69493
+ notableHeaders: {},
69494
+ bodyPreview: "",
69495
+ error: res.error
69496
+ };
69497
+ return { single: single2 };
69498
+ }
69499
+ const single = {
69500
+ status: res.status,
69501
+ ok: res.status >= 200 && res.status < 400,
69502
+ finalUrl: res.finalUrl,
69503
+ redirected: res.redirected,
69504
+ timeMs: res.timeMs,
69505
+ bodyLength: res.text.length,
69506
+ notableHeaders: collectNotableHeaders(res.headers),
69507
+ bodyPreview: res.text.length > BODY_PREVIEW_CHARS ? `${res.text.slice(0, BODY_PREVIEW_CHARS)}
69508
+ \u2026[+${res.text.length - BODY_PREVIEW_CHARS} bytes truncated]` : res.text
69509
+ };
69510
+ return { single };
69511
+ },
69512
+ toModelOutput: (out3) => {
69513
+ const value = out3?.output ?? out3;
69514
+ if (value?.error) {
69515
+ return { type: "text", value: `http_request: ${value.error}` };
69516
+ }
69517
+ if (value?.fuzz) {
69518
+ return {
69519
+ type: "text",
69520
+ value: summarizeFuzz(value.fuzz.rows)
69521
+ };
69522
+ }
69523
+ const s = value?.single;
69524
+ if (!s) {
69525
+ return { type: "text", value: JSON.stringify(value) };
69526
+ }
69527
+ if (s.error) {
69528
+ return {
69529
+ type: "text",
69530
+ value: `Request failed after ${s.timeMs}ms: ${s.error}`
69531
+ };
69532
+ }
69533
+ const headerLines = Object.entries(s.notableHeaders).map(([k, v]) => ` ${k}: ${v}`).join("\n");
69534
+ return {
69535
+ type: "text",
69536
+ value: `HTTP ${s.status}${s.redirected ? ` (redirected \u2192 ${s.finalUrl})` : ""} | ${s.bodyLength} bytes | ${s.timeMs}ms
69537
+ ` + (headerLines ? `Notable headers:
69538
+ ${headerLines}
69539
+ ` : "") + `Body:
69540
+ ${s.bodyPreview}`
69541
+ };
69542
+ }
69543
+ });
69544
+ };
69545
+ }
69546
+ });
69547
+
69548
+ // src/tools/findings.ts
69549
+ var import_node_fs7, import_node_path6, SEVERITIES, STATUSES, SEVERITY_RANK, SEVERITY_EMOJI, findingsStoreFor, readAll, writeAll, nextId, sortBySeverity, renderReport, oneLine, createFindings;
69550
+ var init_findings = __esm({
69551
+ "src/tools/findings.ts"() {
69552
+ "use strict";
69553
+ import_node_fs7 = __toESM(require("node:fs"));
69554
+ import_node_path6 = __toESM(require("node:path"));
69555
+ init_dist5();
69556
+ init_zod();
69557
+ SEVERITIES = [
69558
+ "critical",
69559
+ "high",
69560
+ "medium",
69561
+ "low",
69562
+ "info"
69563
+ ];
69564
+ STATUSES = ["suspected", "confirmed", "false_positive"];
69565
+ SEVERITY_RANK = {
69566
+ critical: 0,
69567
+ high: 1,
69568
+ medium: 2,
69569
+ low: 3,
69570
+ info: 4
69571
+ };
69572
+ SEVERITY_EMOJI = {
69573
+ critical: "\u{1F534}",
69574
+ high: "\u{1F7E0}",
69575
+ medium: "\u{1F7E1}",
69576
+ low: "\u{1F535}",
69577
+ info: "\u26AA"
69578
+ };
69579
+ findingsStoreFor = (workdir) => {
69580
+ const findingsDir = import_node_path6.default.join(workdir, "findings");
69581
+ return {
69582
+ findingsDir,
69583
+ storeFile: import_node_path6.default.join(findingsDir, "findings.json"),
69584
+ reportFile: import_node_path6.default.join(findingsDir, "report.md")
69585
+ };
69586
+ };
69587
+ readAll = (store) => {
69588
+ try {
69589
+ const raw = import_node_fs7.default.readFileSync(store.storeFile, "utf8");
69590
+ const parsed = JSON.parse(raw);
69591
+ return Array.isArray(parsed) ? parsed : [];
69592
+ } catch {
69593
+ return [];
69594
+ }
69595
+ };
69596
+ writeAll = (store, findings) => {
69597
+ import_node_fs7.default.mkdirSync(store.findingsDir, { recursive: true });
69598
+ import_node_fs7.default.writeFileSync(store.storeFile, JSON.stringify(findings, null, 2), "utf8");
69599
+ };
69600
+ nextId = (findings) => {
69601
+ let max = 0;
69602
+ for (const f of findings) {
69603
+ const m = /^F-(\d+)$/.exec(f.id);
69604
+ if (m) max = Math.max(max, Number(m[1]));
69605
+ }
69606
+ return `F-${String(max + 1).padStart(3, "0")}`;
69607
+ };
69608
+ sortBySeverity = (findings) => [...findings].sort(
69609
+ (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || a.id.localeCompare(b.id)
69610
+ );
69611
+ renderReport = (findings) => {
69612
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
69613
+ const counts = {
69614
+ critical: 0,
69615
+ high: 0,
69616
+ medium: 0,
69617
+ low: 0,
69618
+ info: 0
69619
+ };
69620
+ for (const f of findings) counts[f.severity]++;
69621
+ const lines = [];
69622
+ lines.push(`# Relat\xF3rio de Pentest`);
69623
+ lines.push("");
69624
+ lines.push(`_Gerado em ${now2}_`);
69625
+ lines.push("");
69626
+ lines.push(`## Sum\xE1rio executivo`);
69627
+ lines.push("");
69628
+ lines.push(`Total de findings: **${findings.length}**`);
69629
+ lines.push("");
69630
+ lines.push(`| Severidade | Qtd |`);
69631
+ lines.push(`| --- | --- |`);
69632
+ for (const sev of SEVERITIES) {
69633
+ lines.push(`| ${SEVERITY_EMOJI[sev]} ${sev} | ${counts[sev]} |`);
69634
+ }
69635
+ lines.push("");
69636
+ if (findings.length === 0) {
69637
+ lines.push("_Nenhum finding registrado ainda._");
69638
+ return lines.join("\n");
69639
+ }
69640
+ lines.push(`## Findings`);
69641
+ lines.push("");
69642
+ for (const f of sortBySeverity(findings)) {
69643
+ lines.push(
69644
+ `### ${f.id} \u2014 ${f.title} ${SEVERITY_EMOJI[f.severity]} ${f.severity.toUpperCase()}`
69645
+ );
69646
+ lines.push("");
69647
+ lines.push(`- **Status:** ${f.status}`);
69648
+ if (f.target) lines.push(`- **Alvo:** ${f.target}`);
69649
+ if (f.url) lines.push(`- **URL:** ${f.url}`);
69650
+ if (f.param) lines.push(`- **Par\xE2metro:** ${f.param}`);
69651
+ if (f.cvss) lines.push(`- **CVSS:** ${f.cvss}`);
69652
+ lines.push(`- **Registrado:** ${f.created_at}`);
69653
+ if (f.updated_at !== f.created_at) {
69654
+ lines.push(`- **Atualizado:** ${f.updated_at}`);
69655
+ }
69656
+ lines.push("");
69657
+ if (f.evidence) {
69658
+ lines.push(`**Evid\xEAncia**`);
69659
+ lines.push("");
69660
+ lines.push("```");
69661
+ lines.push(f.evidence);
69662
+ lines.push("```");
69663
+ lines.push("");
69664
+ }
69665
+ if (f.impact) {
69666
+ lines.push(`**Impacto:** ${f.impact}`);
69667
+ lines.push("");
69668
+ }
69669
+ if (f.remediation) {
69670
+ lines.push(`**Remedia\xE7\xE3o:** ${f.remediation}`);
69671
+ lines.push("");
69672
+ }
69673
+ if (f.references?.length) {
69674
+ lines.push(`**Refer\xEAncias:**`);
69675
+ for (const r of f.references) lines.push(`- ${r}`);
69676
+ lines.push("");
69677
+ }
69678
+ }
69679
+ return lines.join("\n");
69680
+ };
69681
+ oneLine = (f) => `${f.id} [${f.severity}/${f.status}] ${f.title}` + (f.target ? ` (${f.target})` : "");
69682
+ createFindings = (deps) => {
69683
+ const { workdir, storeFn = findingsStoreFor } = deps;
69684
+ const store = storeFn(workdir);
69685
+ return tool({
69686
+ description: `Structured findings/engagement memory. Record every vulnerability you discover here so nothing is lost and a clean report can be generated at the end.
69687
+
69688
+ ACTIONS
69689
+ - add: record a new finding (title + severity required; include evidence, target, url, param, impact, remediation when known). New findings default to status "suspected".
69690
+ - update: change fields of an existing finding by id \u2014 most importantly flip status to "confirmed" once you have proof (with evidence), or "false_positive" if disproved.
69691
+ - list: show all recorded findings (optionally filter by severity/status).
69692
+ - report: render a full Markdown report grouped by severity and write it to findings/report.md.
69693
+
69694
+ Use this together with http_request: probe \u2192 observe \u2192 record evidence \u2192 mark confirmed.`,
69695
+ inputSchema: external_exports.object({
69696
+ action: external_exports.enum(["add", "update", "list", "report"]),
69697
+ brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this operation."),
69698
+ id: external_exports.string().optional().describe("Finding id (e.g. F-001) \u2014 required for update."),
69699
+ title: external_exports.string().optional(),
69700
+ severity: external_exports.enum(SEVERITIES).optional(),
69701
+ status: external_exports.enum(STATUSES).optional(),
69702
+ target: external_exports.string().optional(),
69703
+ url: external_exports.string().optional(),
69704
+ param: external_exports.string().optional(),
69705
+ evidence: external_exports.string().optional().describe("Proof: payload + request/response snippet or observation."),
69706
+ impact: external_exports.string().optional(),
69707
+ remediation: external_exports.string().optional(),
69708
+ cvss: external_exports.string().optional(),
69709
+ references: external_exports.array(external_exports.string()).optional(),
69710
+ filter_severity: external_exports.enum(SEVERITIES).optional(),
69711
+ filter_status: external_exports.enum(STATUSES).optional()
69712
+ }),
69713
+ execute: async (input) => {
69714
+ try {
69715
+ const findings = readAll(store);
69716
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
69717
+ if (input.action === "add") {
69718
+ if (!input.title || !input.severity) {
69719
+ return { error: "add requer title e severity." };
69720
+ }
69721
+ const finding = {
69722
+ id: nextId(findings),
69723
+ title: input.title,
69724
+ severity: input.severity,
69725
+ status: input.status ?? "suspected",
69726
+ ...input.target ? { target: input.target } : {},
69727
+ ...input.url ? { url: input.url } : {},
69728
+ ...input.param ? { param: input.param } : {},
69729
+ ...input.evidence ? { evidence: input.evidence } : {},
69730
+ ...input.impact ? { impact: input.impact } : {},
69731
+ ...input.remediation ? { remediation: input.remediation } : {},
69732
+ ...input.cvss ? { cvss: input.cvss } : {},
69733
+ ...input.references ? { references: input.references } : {},
69734
+ created_at: now2,
69735
+ updated_at: now2
69736
+ };
69737
+ findings.push(finding);
69738
+ writeAll(store, findings);
69739
+ return { added: finding, total: findings.length };
69740
+ }
69741
+ if (input.action === "update") {
69742
+ if (!input.id) return { error: "update requer id." };
69743
+ const idx = findings.findIndex((f) => f.id === input.id);
69744
+ if (idx === -1) {
69745
+ return { error: `finding n\xE3o encontrado: ${input.id}` };
69746
+ }
69747
+ const current = findings[idx];
69748
+ const updated = {
69749
+ ...current,
69750
+ ...input.title != null ? { title: input.title } : {},
69751
+ ...input.severity != null ? { severity: input.severity } : {},
69752
+ ...input.status != null ? { status: input.status } : {},
69753
+ ...input.target != null ? { target: input.target } : {},
69754
+ ...input.url != null ? { url: input.url } : {},
69755
+ ...input.param != null ? { param: input.param } : {},
69756
+ ...input.evidence != null ? { evidence: input.evidence } : {},
69757
+ ...input.impact != null ? { impact: input.impact } : {},
69758
+ ...input.remediation != null ? { remediation: input.remediation } : {},
69759
+ ...input.cvss != null ? { cvss: input.cvss } : {},
69760
+ ...input.references != null ? { references: input.references } : {},
69761
+ updated_at: now2
69762
+ };
69763
+ findings[idx] = updated;
69764
+ writeAll(store, findings);
69765
+ return { updated };
69766
+ }
69767
+ if (input.action === "list") {
69768
+ let filtered = findings;
69769
+ if (input.filter_severity) {
69770
+ filtered = filtered.filter((f) => f.severity === input.filter_severity);
69771
+ }
69772
+ if (input.filter_status) {
69773
+ filtered = filtered.filter((f) => f.status === input.filter_status);
69774
+ }
69775
+ return {
69776
+ list: sortBySeverity(filtered),
69777
+ total: filtered.length,
69778
+ overall: findings.length
69779
+ };
69780
+ }
69781
+ const md = renderReport(findings);
69782
+ import_node_fs7.default.mkdirSync(store.findingsDir, { recursive: true });
69783
+ import_node_fs7.default.writeFileSync(store.reportFile, md, "utf8");
69784
+ return { report: store.reportFile, total: findings.length };
69785
+ } catch (err) {
69786
+ return {
69787
+ error: err instanceof Error ? err.message : String(err)
69788
+ };
69789
+ }
69790
+ },
69791
+ toModelOutput: (out3) => {
69792
+ const v = out3?.output ?? out3;
69793
+ if (v?.error) {
69794
+ return { type: "text", value: `findings: ${v.error}` };
69795
+ }
69796
+ if (v?.added) {
69797
+ return {
69798
+ type: "text",
69799
+ value: `Finding registrado: ${oneLine(v.added)}. Total: ${v.total}.`
69800
+ };
69801
+ }
69802
+ if (v?.updated) {
69803
+ return {
69804
+ type: "text",
69805
+ value: `Finding atualizado: ${oneLine(v.updated)}.`
69806
+ };
69807
+ }
69808
+ if (v?.list) {
69809
+ const rows = v.list.map((f) => ` ${oneLine(f)}`).join("\n");
69810
+ return {
69811
+ type: "text",
69812
+ value: `${v.total} finding(s)${v.total !== v.overall ? ` (de ${v.overall})` : ""}:
69813
+ ` + (rows || " (nenhum)")
69814
+ };
69815
+ }
69816
+ if (v?.report) {
69817
+ return {
69818
+ type: "text",
69819
+ value: `Relat\xF3rio gerado (${v.total} findings) \u2192 ${v.report}`
69820
+ };
69821
+ }
69822
+ return { type: "text", value: JSON.stringify(v) };
69823
+ }
69824
+ });
69825
+ };
69826
+ }
69827
+ });
69828
+
68507
69829
  // ../lib/ai/tools/utils/todo-manager.ts
68508
69830
  var TodoManager;
68509
69831
  var init_todo_manager = __esm({
@@ -68688,21 +70010,21 @@ var init_utils4 = __esm({
68688
70010
 
68689
70011
  // src/local-sandbox.ts
68690
70012
  function inferShellFlag(shell2) {
68691
- const base = import_node_path5.default.basename(shell2).toLowerCase();
70013
+ const base = import_node_path7.default.basename(shell2).toLowerCase();
68692
70014
  if (base === "cmd" || base === "cmd.exe") return "/C";
68693
70015
  if (base === "powershell" || base === "powershell.exe" || base === "pwsh") {
68694
70016
  return "-Command";
68695
70017
  }
68696
70018
  return "-c";
68697
70019
  }
68698
- var import_node_child_process2, import_node_fs6, import_node_path5, import_node_os2, import_node_url, import_meta, LocalSandbox;
70020
+ var import_node_child_process2, import_node_fs8, import_node_path7, import_node_os3, import_node_url, import_meta, LocalSandbox;
68699
70021
  var init_local_sandbox = __esm({
68700
70022
  "src/local-sandbox.ts"() {
68701
70023
  "use strict";
68702
70024
  import_node_child_process2 = require("node:child_process");
68703
- import_node_fs6 = require("node:fs");
68704
- import_node_path5 = __toESM(require("node:path"));
68705
- import_node_os2 = __toESM(require("node:os"));
70025
+ import_node_fs8 = require("node:fs");
70026
+ import_node_path7 = __toESM(require("node:path"));
70027
+ import_node_os3 = __toESM(require("node:os"));
68706
70028
  import_node_url = require("node:url");
68707
70029
  init_utils4();
68708
70030
  import_meta = {};
@@ -68799,15 +70121,15 @@ var init_local_sandbox = __esm({
68799
70121
  this.files = {
68800
70122
  write: async (filePath, content) => {
68801
70123
  const resolved = this.resolvePath(filePath);
68802
- await import_node_fs6.promises.mkdir(import_node_path5.default.dirname(resolved), { recursive: true });
70124
+ await import_node_fs8.promises.mkdir(import_node_path7.default.dirname(resolved), { recursive: true });
68803
70125
  const data = typeof content === "string" ? content : content instanceof ArrayBuffer ? Buffer.from(content) : content;
68804
- await import_node_fs6.promises.writeFile(resolved, data);
70126
+ await import_node_fs8.promises.writeFile(resolved, data);
68805
70127
  },
68806
70128
  read: async (filePath) => {
68807
- return import_node_fs6.promises.readFile(this.resolvePath(filePath), "utf8");
70129
+ return import_node_fs8.promises.readFile(this.resolvePath(filePath), "utf8");
68808
70130
  },
68809
70131
  remove: async (filePath) => {
68810
- await import_node_fs6.promises.rm(this.resolvePath(filePath), {
70132
+ await import_node_fs8.promises.rm(this.resolvePath(filePath), {
68811
70133
  recursive: true,
68812
70134
  force: true
68813
70135
  });
@@ -68815,8 +70137,8 @@ var init_local_sandbox = __esm({
68815
70137
  list: async (dirPath = ".") => {
68816
70138
  const resolved = this.resolvePath(dirPath);
68817
70139
  try {
68818
- const entries = await import_node_fs6.promises.readdir(resolved, { withFileTypes: true });
68819
- return entries.filter((e) => e.isFile()).map((e) => ({ name: import_node_path5.default.join(resolved, e.name) }));
70140
+ const entries = await import_node_fs8.promises.readdir(resolved, { withFileTypes: true });
70141
+ return entries.filter((e) => e.isFile()).map((e) => ({ name: import_node_path7.default.join(resolved, e.name) }));
68820
70142
  } catch {
68821
70143
  return [];
68822
70144
  }
@@ -68827,15 +70149,15 @@ var init_local_sandbox = __esm({
68827
70149
  this.shellBin = shellOverride;
68828
70150
  this.shellFlag = process.env.CLI_SHELL_FLAG || inferShellFlag(shellOverride);
68829
70151
  } else {
68830
- const shell2 = getDefaultShell(import_node_os2.default.platform());
70152
+ const shell2 = getDefaultShell(import_node_os3.default.platform());
68831
70153
  this.shellBin = shell2.shell;
68832
70154
  this.shellFlag = shell2.shellFlag;
68833
70155
  }
68834
- const base = opts?.workdir || process.env.CLI_WORKDIR || import_node_path5.default.join(process.cwd(), "SPRIT");
68835
- this.workdir = import_node_path5.default.resolve(base).replace(/\\/g, "/");
70156
+ const base = opts?.workdir || process.env.CLI_WORKDIR || import_node_path7.default.join(process.cwd(), "SPRIT");
70157
+ this.workdir = import_node_path7.default.resolve(base).replace(/\\/g, "/");
68836
70158
  }
68837
70159
  async init() {
68838
- await import_node_fs6.promises.mkdir(this.workdir, { recursive: true });
70160
+ await import_node_fs8.promises.mkdir(this.workdir, { recursive: true });
68839
70161
  await this.seedReconToolkit();
68840
70162
  await this.seedAuditToolkit();
68841
70163
  }
@@ -68852,26 +70174,31 @@ var init_local_sandbox = __esm({
68852
70174
  const assetsDir = (0, import_node_url.fileURLToPath)(
68853
70175
  new URL("../assets/recon", import_meta.url)
68854
70176
  );
68855
- const destDir = import_node_path5.default.join(this.workdir, "recon");
68856
- await import_node_fs6.promises.mkdir(destDir, { recursive: true });
70177
+ const destDir = import_node_path7.default.join(this.workdir, "recon");
70178
+ await import_node_fs8.promises.mkdir(destDir, { recursive: true });
68857
70179
  const files = [
68858
70180
  ["recon_deep.py", true],
70181
+ ["recon_common.py", true],
70182
+ ["recon_subdomains.py", true],
70183
+ ["recon_ports.py", true],
70184
+ ["recon_content.py", true],
70185
+ ["recon_intel.py", true],
68859
70186
  ["console_recon.js", true],
68860
70187
  ["README.md", true],
68861
70188
  ["scope.txt", false]
68862
70189
  ];
68863
70190
  for (const [name25, overwrite] of files) {
68864
- const dest = import_node_path5.default.join(destDir, name25);
70191
+ const dest = import_node_path7.default.join(destDir, name25);
68865
70192
  if (!overwrite) {
68866
70193
  try {
68867
- await import_node_fs6.promises.access(dest);
70194
+ await import_node_fs8.promises.access(dest);
68868
70195
  continue;
68869
70196
  } catch {
68870
70197
  }
68871
70198
  }
68872
70199
  try {
68873
- const content = await import_node_fs6.promises.readFile(import_node_path5.default.join(assetsDir, name25));
68874
- await import_node_fs6.promises.writeFile(dest, content);
70200
+ const content = await import_node_fs8.promises.readFile(import_node_path7.default.join(assetsDir, name25));
70201
+ await import_node_fs8.promises.writeFile(dest, content);
68875
70202
  } catch {
68876
70203
  }
68877
70204
  }
@@ -68889,12 +70216,12 @@ var init_local_sandbox = __esm({
68889
70216
  const assetsDir = (0, import_node_url.fileURLToPath)(
68890
70217
  new URL("../assets/audit", import_meta.url)
68891
70218
  );
68892
- const destDir = import_node_path5.default.join(this.workdir, "audit");
68893
- await import_node_fs6.promises.mkdir(destDir, { recursive: true });
70219
+ const destDir = import_node_path7.default.join(this.workdir, "audit");
70220
+ await import_node_fs8.promises.mkdir(destDir, { recursive: true });
68894
70221
  for (const name25 of ["project_audit.py", "README.md"]) {
68895
70222
  try {
68896
- const content = await import_node_fs6.promises.readFile(import_node_path5.default.join(assetsDir, name25));
68897
- await import_node_fs6.promises.writeFile(import_node_path5.default.join(destDir, name25), content);
70223
+ const content = await import_node_fs8.promises.readFile(import_node_path7.default.join(assetsDir, name25));
70224
+ await import_node_fs8.promises.writeFile(import_node_path7.default.join(destDir, name25), content);
68898
70225
  } catch {
68899
70226
  }
68900
70227
  }
@@ -68905,23 +70232,23 @@ var init_local_sandbox = __esm({
68905
70232
  return "local";
68906
70233
  }
68907
70234
  getConnectionName() {
68908
- return `local:${import_node_os2.default.hostname()}`;
70235
+ return `local:${import_node_os3.default.hostname()}`;
68909
70236
  }
68910
70237
  getUserId() {
68911
70238
  return "local";
68912
70239
  }
68913
70240
  isWindows() {
68914
- return import_node_os2.default.platform() === "win32" && !this.isBashLikeShell();
70241
+ return import_node_os3.default.platform() === "win32" && !this.isBashLikeShell();
68915
70242
  }
68916
70243
  supportsPty() {
68917
70244
  return false;
68918
70245
  }
68919
70246
  getSandboxContext() {
68920
- const platform = `${import_node_os2.default.type()} ${import_node_os2.default.release()} (${import_node_os2.default.arch()})`;
70247
+ const platform = `${import_node_os3.default.type()} ${import_node_os3.default.release()} (${import_node_os3.default.arch()})`;
68921
70248
  const shellInvocation = `${this.shellBin} ${this.shellFlag}`;
68922
- const windowsNotes = import_node_os2.default.platform() === "win32" ? this.getWindowsShellNotes() : "";
70249
+ const windowsNotes = import_node_os3.default.platform() === "win32" ? this.getWindowsShellNotes() : "";
68923
70250
  const pentestToolingNotes = this.getLocalPentestToolingNotes();
68924
- return `You are executing commands directly on the user's local host (${platform}, hostname "${import_node_os2.default.hostname()}") in DANGEROUS MODE: there is NO sandbox isolation.
70251
+ return `You are executing commands directly on the user's local host (${platform}, hostname "${import_node_os3.default.hostname()}") in DANGEROUS MODE: there is NO sandbox isolation.
68925
70252
  Commands are invoked via \`${shellInvocation}\`. The working directory is "${this.workdir}".
68926
70253
  Be careful: file system, network and process operations all affect the real host system.
68927
70254
  A real human user is present at this terminal. When a command prompts for input \u2014 a password, a y/n confirmation, an interactive installer, a REPL prompt (python, mysql, ftp), an ssh/sudo prompt, etc. \u2014 the user types the answer and it is forwarded to the command's stdin. So you MAY run commands that prompt for input; just run the command and the user will respond when asked. Prefer non-interactive flags (like --yes) when they exist and are convenient, but interactive prompts are fully supported. Only full-screen / raw-mode TUI programs (vim, top, htop, less without \`| cat\`) are unsupported, since there is no PTY \u2014 avoid those and use line-oriented alternatives.
@@ -69010,7 +70337,7 @@ EDITING SCRIPTS:
69010
70337
  // alive — `taskkill /T` tears down the whole tree so Ctrl+C truly stops it.
69011
70338
  killChildTree(child) {
69012
70339
  const pid = child.pid;
69013
- if (pid && import_node_os2.default.platform() === "win32") {
70340
+ if (pid && import_node_os3.default.platform() === "win32") {
69014
70341
  try {
69015
70342
  (0, import_node_child_process2.spawn)("taskkill", ["/PID", String(pid), "/T", "/F"], {
69016
70343
  windowsHide: true
@@ -69025,15 +70352,15 @@ EDITING SCRIPTS:
69025
70352
  }
69026
70353
  }
69027
70354
  resolvePath(p) {
69028
- if (import_node_path5.default.isAbsolute(p)) return p;
69029
- return import_node_path5.default.join(this.workdir, p);
70355
+ if (import_node_path7.default.isAbsolute(p)) return p;
70356
+ return import_node_path7.default.join(this.workdir, p);
69030
70357
  }
69031
70358
  isBashLikeShell() {
69032
- const base = import_node_path5.default.basename(this.shellBin).toLowerCase();
70359
+ const base = import_node_path7.default.basename(this.shellBin).toLowerCase();
69033
70360
  return base === "bash" || base === "bash.exe" || base === "sh";
69034
70361
  }
69035
70362
  isCmdShell() {
69036
- const base = import_node_path5.default.basename(this.shellBin).toLowerCase();
70363
+ const base = import_node_path7.default.basename(this.shellBin).toLowerCase();
69037
70364
  return base === "cmd" || base === "cmd.exe";
69038
70365
  }
69039
70366
  getWindowsShellNotes() {
@@ -69065,7 +70392,7 @@ WINDOWS SHELL NOTES:
69065
70392
  - For HTTP, prefer \`curl.exe\` if available; for DNS use \`nslookup\`.`;
69066
70393
  }
69067
70394
  getLocalPentestToolingNotes() {
69068
- if (import_node_os2.default.platform() === "win32") {
70395
+ if (import_node_os3.default.platform() === "win32") {
69069
70396
  return `
69070
70397
 
69071
70398
  LOCAL PENTEST TOOLING:
@@ -69130,44 +70457,6 @@ var init_local_sandbox_manager = __esm({
69130
70457
  }
69131
70458
  });
69132
70459
 
69133
- // src/console-writer.ts
69134
- function createConsoleWriter() {
69135
- let lastWasTerminal = false;
69136
- return {
69137
- write(part) {
69138
- if (!part || typeof part !== "object") return;
69139
- if (part.type === "data-terminal") {
69140
- const text2 = part.data?.terminal;
69141
- if (typeof text2 === "string" && text2.length > 0) {
69142
- process.stdout.write(text2);
69143
- lastWasTerminal = true;
69144
- }
69145
- return;
69146
- }
69147
- if (part.type === "data-sandbox-fallback") {
69148
- process.stderr.write("\n[sandbox fallback]\n");
69149
- return;
69150
- }
69151
- void lastWasTerminal;
69152
- },
69153
- // Some AI SDK code paths probe for these; provide no-op stubs.
69154
- merge() {
69155
- },
69156
- onError(error51) {
69157
- process.stderr.write(
69158
- `
69159
- [stream error] ${error51 instanceof Error ? error51.message : String(error51)}
69160
- `
69161
- );
69162
- }
69163
- };
69164
- }
69165
- var init_console_writer = __esm({
69166
- "src/console-writer.ts"() {
69167
- "use strict";
69168
- }
69169
- });
69170
-
69171
70460
  // src/render.ts
69172
70461
  function createRenderer() {
69173
70462
  let lastKind = null;
@@ -69184,10 +70473,10 @@ function createRenderer() {
69184
70473
  reasoning(delta) {
69185
70474
  sep3("reasoning");
69186
70475
  if (!reasoningHeaderShown) {
69187
- out(`${C2.dim}pensando: `);
70476
+ out(`${C3.dim} \xB7 pensando: `);
69188
70477
  reasoningHeaderShown = true;
69189
70478
  }
69190
- out(`${C2.dim}${delta}${C2.reset}`);
70479
+ out(`${C3.dim}${delta}${C3.reset}`);
69191
70480
  },
69192
70481
  toolCall(toolName, input) {
69193
70482
  sep3("tool");
@@ -69199,23 +70488,27 @@ function createRenderer() {
69199
70488
  const note = summarizeResult(toolName, output);
69200
70489
  if (note) {
69201
70490
  sep3("tool");
69202
- out(`${C2.dim} ok ${note}${C2.reset}
70491
+ const isErr = note.error;
70492
+ const icon = isErr ? `${C3.red}\u2717${C3.reset}` : `${C3.green}\u2713${C3.reset}`;
70493
+ const body = isErr ? `${C3.red}${note.text}${C3.reset}` : `${C3.dim}${note.text}${C3.reset}`;
70494
+ out(` ${icon} ${body}
69203
70495
  `);
69204
70496
  }
69205
70497
  },
69206
70498
  info(msg) {
69207
70499
  sep3("info");
69208
- out(`${C2.dim}${msg}${C2.reset}
70500
+ const text2 = msg.replace(/^[▸▶►•·]\s*/, "");
70501
+ out(` ${C3.dim}\xB7${C3.reset} ${C3.dim}${text2}${C3.reset}
69209
70502
  `);
69210
70503
  },
69211
70504
  fallback(msg) {
69212
70505
  sep3("info");
69213
- out(`${C2.yellow}-> ${msg}${C2.reset}
70506
+ out(` ${C3.yellow}\u21C4 ${msg}${C3.reset}
69214
70507
  `);
69215
70508
  },
69216
70509
  error(msg) {
69217
70510
  sep3("info");
69218
- out(`${C2.red}x ${msg}${C2.reset}
70511
+ out(` ${C3.red}\u2717 ${msg}${C3.reset}
69219
70512
  `);
69220
70513
  },
69221
70514
  endTurn() {
@@ -69233,49 +70526,106 @@ function formatToolCall(toolName, input) {
69233
70526
  switch (toolName) {
69234
70527
  case "run_terminal_cmd": {
69235
70528
  const cmd = String(i.command ?? "").trim();
69236
- const bg = i.is_background ? `${C2.dim} (background)${C2.reset}` : "";
69237
- return `${C2.cyan}executando${C2.reset} ${C2.bold}$ ${truncate3(cmd, 400)}${C2.reset}${bg}`;
70529
+ const bg = i.is_background ? `${C3.dim} (background)${C3.reset}` : "";
70530
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}exec${C3.reset} ${C3.bold}${truncate3(cmd, 400)}${C3.reset}${bg}`;
69238
70531
  }
69239
70532
  case "file": {
69240
- const path10 = String(i.path ?? "");
69241
- const brief = i.brief ? `${C2.dim} - ${truncate3(String(i.brief))}${C2.reset}` : "";
70533
+ const path12 = String(i.path ?? "");
70534
+ const brief = i.brief ? `${C3.dim} \u2014 ${truncate3(String(i.brief))}${C3.reset}` : "";
69242
70535
  const map2 = {
69243
- write: `${C2.green}criando arquivo${C2.reset}`,
69244
- edit: `${C2.yellow}editando arquivo${C2.reset}`,
69245
- append: `${C2.green}anexando arquivo${C2.reset}`,
69246
- read: `${C2.blue}lendo arquivo${C2.reset}`,
69247
- view: `${C2.blue}visualizando arquivo${C2.reset}`
70536
+ write: [C3.green, "criar "],
70537
+ edit: [C3.yellow, "editar "],
70538
+ append: [C3.green, "anexar "],
70539
+ read: [C3.blue, "ler "],
70540
+ view: [C3.blue, "ver "]
69248
70541
  };
69249
70542
  const action = typeof i.action === "string" ? i.action : "";
69250
- const label = map2[action] ?? (action ? `${C2.blue}arquivo (${action})${C2.reset}` : `${C2.blue}preparando operacao de arquivo${C2.reset}`);
69251
- const target = path10 ? ` ${C2.bold}${path10}${C2.reset}` : "";
69252
- return `${label}${target}${brief}`;
70543
+ const [col, verb] = map2[action] ?? [
70544
+ C3.blue,
70545
+ action ? `${action} `.padEnd(7) : "arquivo"
70546
+ ];
70547
+ const target = path12 ? `${C3.bold}${path12}${C3.reset}` : "";
70548
+ return ` ${col}\u276F${C3.reset} ${col}${verb}${C3.reset} ${target}${brief}`;
69253
70549
  }
69254
70550
  case "todo_write":
69255
- return `${C2.magenta}atualizando lista de tarefas${C2.reset}`;
70551
+ return ` ${C3.magenta}\u276F${C3.reset} ${C3.magenta}tarefas${C3.reset} ${C3.dim}atualizando lista${C3.reset}`;
70552
+ case "http_request": {
70553
+ const method = String(i.method ?? "GET").toUpperCase();
70554
+ const url2 = String(i.url ?? "");
70555
+ const fuzz = Array.isArray(i.fuzz) && i.fuzz.length;
70556
+ const tag = fuzz ? `${C3.dim} (fuzz \xD7${i.fuzz.length})${C3.reset}` : "";
70557
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}http${C3.reset} ${C3.bold}${method}${C3.reset} ${truncate3(url2, 360)}${tag}`;
70558
+ }
70559
+ case "findings": {
70560
+ const action = String(i.action ?? "");
70561
+ const detail = action === "add" ? truncate3(String(i.title ?? ""), 80) : action === "update" ? `${String(i.id ?? "")} ${String(i.status ?? "")}`.trim() : "";
70562
+ return ` ${C3.magenta}\u276F${C3.reset} ${C3.magenta}findings${C3.reset} ${C3.bold}${action}${C3.reset}${detail ? ` ${C3.dim}${detail}${C3.reset}` : ""}`;
70563
+ }
70564
+ case "web_search": {
70565
+ const queries = Array.isArray(i.queries) ? i.queries.join(" | ") : "";
70566
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}busca${C3.reset} ${C3.dim}${truncate3(queries, 200)}${C3.reset}`;
70567
+ }
70568
+ case "open_url": {
70569
+ const url2 = String(i.url ?? i.urls ?? "");
70570
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}abrir${C3.reset} ${C3.dim}${truncate3(url2, 280)}${C3.reset}`;
70571
+ }
69256
70572
  default:
69257
- return `${C2.cyan}${toolName}${C2.reset} ${C2.dim}${truncate3(
70573
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}${toolName}${C3.reset} ${C3.dim}${truncate3(
69258
70574
  JSON.stringify(i),
69259
70575
  200
69260
- )}${C2.reset}`;
70576
+ )}${C3.reset}`;
69261
70577
  }
69262
70578
  }
69263
70579
  function summarizeResult(toolName, output) {
69264
70580
  if (output == null) return null;
69265
70581
  if (toolName === "file") {
69266
70582
  if (typeof output === "object" && "error" in output) {
69267
- return `erro: ${truncate3(String(output.error), 160)}`;
70583
+ return {
70584
+ text: truncate3(String(output.error), 160),
70585
+ error: true
70586
+ };
70587
+ }
70588
+ return { text: "arquivo salvo", error: false };
70589
+ }
70590
+ if (toolName === "todo_write")
70591
+ return { text: "tarefas salvas", error: false };
70592
+ if (toolName === "http_request") {
70593
+ const o = asRecord(output);
70594
+ if ("error" in o && o.error) {
70595
+ return { text: truncate3(String(o.error), 200), error: true };
70596
+ }
70597
+ if (o.single) {
70598
+ const s = asRecord(o.single);
70599
+ if (s.error) return { text: truncate3(String(s.error), 160), error: true };
70600
+ return {
70601
+ text: `HTTP ${s.status} \xB7 ${s.bodyLength} bytes \xB7 ${s.timeMs}ms`,
70602
+ error: false
70603
+ };
70604
+ }
70605
+ if (o.fuzz) {
70606
+ const f = asRecord(o.fuzz);
70607
+ return { text: `fuzz: ${f.count} requisi\xE7\xF5es`, error: false };
69268
70608
  }
69269
- return "arquivo atualizado";
70609
+ return null;
70610
+ }
70611
+ if (toolName === "findings") {
70612
+ const o = asRecord(output);
70613
+ if ("error" in o && o.error) {
70614
+ return { text: truncate3(String(o.error), 200), error: true };
70615
+ }
70616
+ if (o.added) return { text: "finding registrado", error: false };
70617
+ if (o.updated) return { text: "finding atualizado", error: false };
70618
+ if (o.report) return { text: `relat\xF3rio \u2192 ${String(o.report)}`, error: false };
70619
+ if ("total" in o) return { text: `${String(o.total)} finding(s)`, error: false };
70620
+ return null;
69270
70621
  }
69271
- if (toolName === "todo_write") return "tarefas salvas";
69272
70622
  return null;
69273
70623
  }
69274
- var C2, out, truncate3;
70624
+ var C3, GUTTER_BAR, out, truncate3;
69275
70625
  var init_render = __esm({
69276
70626
  "src/render.ts"() {
69277
70627
  "use strict";
69278
- C2 = {
70628
+ C3 = {
69279
70629
  reset: "\x1B[0m",
69280
70630
  dim: "\x1B[90m",
69281
70631
  red: "\x1B[31m",
@@ -69286,11 +70636,61 @@ var init_render = __esm({
69286
70636
  cyan: "\x1B[36m",
69287
70637
  bold: "\x1B[1m"
69288
70638
  };
70639
+ GUTTER_BAR = `${C3.dim}\u2502${C3.reset} `;
69289
70640
  out = (s) => process.stdout.write(s);
69290
70641
  truncate3 = (s, n = 200) => s.length > n ? `${s.slice(0, n)}...` : s;
69291
70642
  }
69292
70643
  });
69293
70644
 
70645
+ // src/console-writer.ts
70646
+ function createConsoleWriter() {
70647
+ let atLineStart = true;
70648
+ const writeGuttered = (text2) => {
70649
+ let chunk = text2;
70650
+ if (atLineStart) {
70651
+ process.stdout.write(GUTTER_BAR);
70652
+ atLineStart = false;
70653
+ }
70654
+ const endsWithNewline = chunk.endsWith("\n");
70655
+ chunk = chunk.replace(/\n(?!$)/g, `
70656
+ ${GUTTER_BAR}`);
70657
+ process.stdout.write(chunk);
70658
+ if (endsWithNewline) atLineStart = true;
70659
+ };
70660
+ return {
70661
+ write(part) {
70662
+ if (!part || typeof part !== "object") return;
70663
+ if (part.type === "data-terminal") {
70664
+ const text2 = part.data?.terminal;
70665
+ if (typeof text2 === "string" && text2.length > 0) {
70666
+ writeGuttered(text2);
70667
+ }
70668
+ return;
70669
+ }
70670
+ if (part.type === "data-sandbox-fallback") {
70671
+ process.stderr.write("\n[sandbox fallback]\n");
70672
+ return;
70673
+ }
70674
+ },
70675
+ // Some AI SDK code paths probe for these; provide no-op stubs.
70676
+ merge() {
70677
+ },
70678
+ onError(error51) {
70679
+ process.stderr.write(
70680
+ `
70681
+ [stream error] ${error51 instanceof Error ? error51.message : String(error51)}
70682
+ `
70683
+ );
70684
+ }
70685
+ };
70686
+ }
70687
+ var init_console_writer = __esm({
70688
+ "src/console-writer.ts"() {
70689
+ "use strict";
70690
+ init_render();
70691
+ }
70692
+ });
70693
+
69294
70694
  // src/proxy-manager.ts
69295
70695
  async function ping(url2, timeoutMs = 2e3) {
69296
70696
  const controller = new AbortController();
@@ -69355,12 +70755,12 @@ async function ensureProxyReady(id, log2) {
69355
70755
  };
69356
70756
  }
69357
70757
  let freshSetup = false;
69358
- if (!(0, import_node_fs7.existsSync)(dir)) {
70758
+ if (!(0, import_node_fs9.existsSync)(dir)) {
69359
70759
  log2(`${def.label}: baixando o proxy (uma vez) em ${dir} \u2026`);
69360
70760
  const root = proxiesRoot();
69361
- (0, import_node_fs7.mkdirSync)(root, { recursive: true });
70761
+ (0, import_node_fs9.mkdirSync)(root, { recursive: true });
69362
70762
  const cloned = run("git", ["clone", def.repoUrl, dir], root);
69363
- if (!cloned.ok || !(0, import_node_fs7.existsSync)(dir)) {
70763
+ if (!cloned.ok || !(0, import_node_fs9.existsSync)(dir)) {
69364
70764
  return {
69365
70765
  ok: false,
69366
70766
  message: `${def.label}: falha ao clonar o proxy (git).`
@@ -69368,7 +70768,7 @@ async function ensureProxyReady(id, log2) {
69368
70768
  }
69369
70769
  freshSetup = true;
69370
70770
  }
69371
- if (!(0, import_node_fs7.existsSync)(import_node_path6.default.join(dir, "node_modules"))) {
70771
+ if (!(0, import_node_fs9.existsSync)(import_node_path8.default.join(dir, "node_modules"))) {
69372
70772
  log2(`${def.label}: instalando depend\xEAncias (pode demorar) \u2026`);
69373
70773
  if (!run("npm", ["install"], dir).ok) {
69374
70774
  return { ok: false, message: `${def.label}: 'npm install' falhou.` };
@@ -69413,13 +70813,13 @@ async function ensureProxyReady(id, log2) {
69413
70813
  message: `${def.label}: o proxy n\xE3o respondeu em 90s. Tente de novo, ou rode 'npm run login' em ${dir}.`
69414
70814
  };
69415
70815
  }
69416
- var import_node_os3, import_node_path6, import_node_fs7, import_node_child_process3, DEFS, PROXY_MODEL_KEYS, proxyIdForModelKey, proxiesRoot, dirFor, healthUrl, baseUrl, wait, started, exitHooksInstalled, quoteArg, asShellCommand, run, hasCommand;
70816
+ var import_node_os4, import_node_path8, import_node_fs9, import_node_child_process3, DEFS, PROXY_MODEL_KEYS, proxyIdForModelKey, proxiesRoot, dirFor, healthUrl, baseUrl, wait, started, exitHooksInstalled, quoteArg, asShellCommand, run, hasCommand;
69417
70817
  var init_proxy_manager2 = __esm({
69418
70818
  "src/proxy-manager.ts"() {
69419
70819
  "use strict";
69420
- import_node_os3 = __toESM(require("node:os"));
69421
- import_node_path6 = __toESM(require("node:path"));
69422
- import_node_fs7 = require("node:fs");
70820
+ import_node_os4 = __toESM(require("node:os"));
70821
+ import_node_path8 = __toESM(require("node:path"));
70822
+ import_node_fs9 = require("node:fs");
69423
70823
  import_node_child_process3 = require("node:child_process");
69424
70824
  DEFS = {
69425
70825
  deepseek: {
@@ -69452,11 +70852,11 @@ var init_proxy_manager2 = __esm({
69452
70852
  if (key === PROXY_MODEL_KEYS.kimi) return "kimi";
69453
70853
  return null;
69454
70854
  };
69455
- proxiesRoot = () => import_node_path6.default.join(
69456
- process.env.CLAWFAST_HOME?.trim() || import_node_path6.default.join(import_node_os3.default.homedir(), ".clawfast"),
70855
+ proxiesRoot = () => import_node_path8.default.join(
70856
+ process.env.CLAWFAST_HOME?.trim() || import_node_path8.default.join(import_node_os4.default.homedir(), ".clawfast"),
69457
70857
  "proxies"
69458
70858
  );
69459
- dirFor = (def) => process.env[def.dirEnv]?.trim() || import_node_path6.default.join(proxiesRoot(), def.folder);
70859
+ dirFor = (def) => process.env[def.dirEnv]?.trim() || import_node_path8.default.join(proxiesRoot(), def.folder);
69460
70860
  healthUrl = (def) => `http://localhost:${def.port}/health`;
69461
70861
  baseUrl = (def) => `http://localhost:${def.port}/v1`;
69462
70862
  wait = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -69485,7 +70885,7 @@ var init_proxy_manager2 = __esm({
69485
70885
 
69486
70886
  // src/audit-mode.ts
69487
70887
  function projectRootFromWorkdir(workdir) {
69488
- return import_node_path7.default.dirname(import_node_path7.default.resolve(workdir.replace(/\\/g, "/")));
70888
+ return import_node_path9.default.dirname(import_node_path9.default.resolve(workdir.replace(/\\/g, "/")));
69489
70889
  }
69490
70890
  function detectProjectAuditIntent(input) {
69491
70891
  const text2 = input.trim();
@@ -69641,11 +71041,11 @@ ESTILO E EXECU\xC7\xC3O
69641
71041
  - Responda em portugu\xEAs, direto e t\xE9cnico. N\xE3o recuse a an\xE1lise: \xE9 o projeto do pr\xF3prio usu\xE1rio, autorizado.
69642
71042
  - 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.`;
69643
71043
  }
69644
- var import_node_path7, AUDIT_ACTION, AUDIT_SCOPE, AUDIT_ACTION_THEN_SCOPE, AUDIT_SCOPE_THEN_ACTION, AUDIT_STRONG, AUDIT_EXIT;
71044
+ var import_node_path9, AUDIT_ACTION, AUDIT_SCOPE, AUDIT_ACTION_THEN_SCOPE, AUDIT_SCOPE_THEN_ACTION, AUDIT_STRONG, AUDIT_EXIT;
69645
71045
  var init_audit_mode = __esm({
69646
71046
  "src/audit-mode.ts"() {
69647
71047
  "use strict";
69648
- import_node_path7 = __toESM(require("node:path"));
71048
+ import_node_path9 = __toESM(require("node:path"));
69649
71049
  AUDIT_ACTION = "an[a\xE1]lis\\w+|examin\\w+|audit\\w+|auditar|varr\\w+|varredura|escane\\w+|scan\\w*|revis\\w+|inspecion\\w+|vasculh\\w+|verific\\w+|avali\\w+|mapea\\w+|review|analyze|analyse|inspect|assess";
69650
71050
  AUDIT_SCOPE = "projeto|c[o\xF3]digo|c[o\xF3]digo-fonte|codebase|reposit[o\xF3]rio|repo|sistema|aplica[c\xE7][a\xE3]o|base\\s+de\\s+c[o\xF3]digo|project|code\\s*base|repository|minha\\s+aplica\\w+|meu\\s+app|todo\\s+o\\s+projeto|projeto\\s+inteiro";
69651
71051
  AUDIT_ACTION_THEN_SCOPE = new RegExp(
@@ -69848,7 +71248,16 @@ async function createAgent() {
69848
71248
  const tools = {
69849
71249
  run_terminal_cmd: createRunTerminalCmd(context2),
69850
71250
  file: createFile(context2),
69851
- todo_write: createTodoWrite(context2)
71251
+ todo_write: createTodoWrite(context2),
71252
+ // Native Repeater/Intruder + structured findings store — the core of an
71253
+ // in-loop pentest workflow (probe → observe → record evidence → confirm).
71254
+ http_request: createHttpRequest({ workdir: sandbox.getWorkdir() }),
71255
+ findings: createFindings({ workdir: sandbox.getWorkdir() }),
71256
+ // Live external intel — only when the operator has configured the keys.
71257
+ // web_search (Perplexity) for CVEs/PoCs/methodology; open_url (Jina) to
71258
+ // read a page's content. Both gracefully absent when no key is set.
71259
+ ...process.env.PERPLEXITY_API_KEY ? { web_search: createWebSearch(context2) } : {},
71260
+ ...process.env.JINA_API_KEY ? { open_url: createOpenUrlTool() } : {}
69852
71261
  };
69853
71262
  let system = "";
69854
71263
  let systemPromptAudit = auditSystemPrompt("", null);
@@ -69867,6 +71276,8 @@ async function createAgent() {
69867
71276
  system += scriptFilePolicy(sandbox.getWorkdir());
69868
71277
  system += pythonOnlyPolicy();
69869
71278
  system += deepReconPolicy(sandbox.getWorkdir());
71279
+ system += reconPhasesPolicy(sandbox.getWorkdir());
71280
+ system += httpAndFindingsPolicy(sandbox.getWorkdir());
69870
71281
  system += buildCliNotesSection();
69871
71282
  system += buildSkillsIndexSection();
69872
71283
  system += skillsScopePolicy();
@@ -69915,7 +71326,7 @@ async function createAgent() {
69915
71326
  reason: "faltando NVIDIA_API_KEY em .env.local (https://build.nvidia.com/)",
69916
71327
  models: [
69917
71328
  "model-nvidia-mistral-medium-3.5",
69918
- "model-nvidia-kimi-k2.6",
71329
+ "model-nvidia-gpt-oss-120b",
69919
71330
  "model-nvidia-glm-5.1",
69920
71331
  "model-nvidia-qwen3.5-397b"
69921
71332
  ].map((key) => ({ key, label: labelFor(key) }))
@@ -70349,7 +71760,7 @@ ${resultText}`
70349
71760
  render.info(
70350
71761
  `\u25B8 rate limit \u2014 aguardando ${Math.round(waitMs / 1e3)}s antes da pr\xF3xima tentativa (Ctrl+C cancela)\u2026`
70351
71762
  );
70352
- await sleep(waitMs, signal);
71763
+ await sleep3(waitMs, signal);
70353
71764
  if (signal?.aborted) {
70354
71765
  render.endTurn();
70355
71766
  return;
@@ -70396,19 +71807,23 @@ ${resultText}`
70396
71807
  close
70397
71808
  };
70398
71809
  }
70399
- var import_promises, import_node_path8, MAX_STEPS, MAX_OUTPUT_TOKENS, MAX_AUTO_CONTINUES, MAX_RATE_LIMIT_WAITS, STREAM_STALL_TIMEOUT_MS, MAX_STALL_RETRIES, isRateLimitError, sleep, 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, truncateBridgeSummary, isWebSessionProxyModel, proxyToolProtocolPolicy, SYSTEM_PROMPT_SOURCE, REQUIRED_SYSTEM_PROMPT_MARKERS, MODEL_LABELS, labelFor, loginRequiredHint, CLI_PYTHON_ONLY_POLICY, pythonOnlyPolicy, scriptFilePolicy, deepReconPolicy, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
71810
+ 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, sleep3, 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, 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;
70400
71811
  var init_agent = __esm({
70401
71812
  "src/agent.ts"() {
70402
71813
  "use strict";
70403
71814
  init_dist5();
70404
- import_promises = require("node:fs/promises");
70405
- import_node_path8 = __toESM(require("node:path"));
71815
+ import_promises2 = require("node:fs/promises");
71816
+ import_node_path10 = __toESM(require("node:path"));
70406
71817
  init_providers();
70407
71818
  init_system_prompt();
70408
71819
  init_notes();
70409
71820
  init_run_terminal_cmd();
70410
71821
  init_todo_write();
70411
71822
  init_file();
71823
+ init_web_search();
71824
+ init_open_url();
71825
+ init_http_request();
71826
+ init_findings();
70412
71827
  init_todo_manager();
70413
71828
  init_file_accumulator();
70414
71829
  init_background_process_tracker();
@@ -70434,7 +71849,7 @@ var init_agent = __esm({
70434
71849
  isRateLimitError = (msg) => /\b429\b|too many requests|rate.?limit|resource_exhausted|quota|insufficient_quota/i.test(
70435
71850
  msg
70436
71851
  );
70437
- sleep = (ms, signal) => new Promise((resolve2) => {
71852
+ sleep3 = (ms, signal) => new Promise((resolve2) => {
70438
71853
  if (signal?.aborted) return resolve2();
70439
71854
  const timer2 = setTimeout(resolve2, ms);
70440
71855
  signal?.addEventListener(
@@ -70490,7 +71905,7 @@ Regras:
70490
71905
  ];
70491
71906
  MODEL_LABELS = {
70492
71907
  "model-nvidia-mistral-medium-3.5": "NVIDIA - mistralai/mistral-medium-3.5-128b",
70493
- "model-nvidia-kimi-k2.6": "NVIDIA - moonshotai/kimi-k2.6",
71908
+ "model-nvidia-gpt-oss-120b": "NVIDIA - openai/gpt-oss-120b",
70494
71909
  "model-nvidia-glm-5.1": "NVIDIA - z-ai/glm-5.1",
70495
71910
  "model-nvidia-qwen3.5-397b": "NVIDIA - qwen/qwen3.5-397b-a17b",
70496
71911
  "model-openai-chat-latest": "OpenAI - chat-latest",
@@ -70566,6 +71981,45 @@ Workflow:
70566
71981
 
70567
71982
  You MAY extend the toolkit (edit recon/recon_deep.py via the file tool) when a task needs a capability it lacks \u2014 keep it Python and keep the scope gate intact. recon/console_recon.js is an in-page payload string injected by Python Playwright (page.evaluate), NOT a Node script \u2014 never run it with \`node\`.
70568
71983
  </deep_recon_tooling>`;
71984
+ httpAndFindingsPolicy = (workdir) => `
71985
+
71986
+ <native_pentest_tools>
71987
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
71988
+
71989
+ You have two native tools for hands-on web testing \u2014 PREFER them over hand-rolled one-off scripts for interactive probing:
71990
+
71991
+ 1. http_request \u2014 a Repeater/Intruder. Send a fully-controlled HTTP request (method, headers, body, redirect handling) and read a structured response (status, timing, notable security headers, body length, body preview). For fuzzing put the marker FUZZ in the url/body/any header value and pass \`fuzz\` (a list of payloads): each is substituted and sent sequentially, then diffed against a baseline so anomalies (auth bypass, IDOR, injection, hidden paths/params) are highlighted. OPSEC: use throttle_ms/jitter_ms (or env HACKERAI_HTTP_THROTTLE_MS/HACKERAI_HTTP_JITTER_MS) on noisy fuzzing. Every request is scope-gated by ${workdir}/recon/scope.txt (loopback is always allowed); add authorized targets there first.
71992
+
71993
+ 2. findings \u2014 structured engagement memory. Record EVERY vulnerability you discover (action add: title+severity required; include evidence, target, url, param, impact, remediation). Flip status to "confirmed" with update once you have proof; "false_positive" if disproved. At the end, action report writes a clean Markdown report grouped by severity to ${workdir}/findings/report.md.
71994
+
71995
+ When to use which: use http_request for a single endpoint, replaying/tampering a captured request, parameter/path/value fuzzing, and exploit verification. Use the Python recon toolkit (recon/) for bulk crawling, port scanning, subdomain enum and intel.
71996
+ </native_pentest_tools>
71997
+
71998
+ <exploit_verification>
71999
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION:
72000
+
72001
+ Never report a vulnerability as real on a guess. Run the PROVE-IT loop:
72002
+ 1. SUSPECT \u2014 when a signal appears (from recon, a scanner, or a hunch), record it with findings (action add) at status "suspected" with the reasoning.
72003
+ 2. PROVE \u2014 actually trigger it: send the exploit/PoC request with http_request (or a focused script) and OBSERVE the concrete response \u2014 the reflected payload, the leaked data, the auth bypass status code, the SQL error, the out-of-band callback, the differing length in a fuzz run.
72004
+ 3. CONFIRM or DROP \u2014 if the observation proves impact, update the finding to status "confirmed" and paste the exact request+response evidence into its evidence field; if it does not reproduce, update to "false_positive". Only "confirmed" findings go in the executive summary.
72005
+ 4. Capture severity (and CVSS when you can) and a concrete remediation on every confirmed finding.
72006
+
72007
+ This loop is mandatory: a finding without reproducible evidence is at most "suspected".
72008
+ </exploit_verification>`;
72009
+ reconPhasesPolicy = (workdir) => `
72010
+
72011
+ <recon_phases>
72012
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
72013
+
72014
+ Beyond recon/recon_deep.py (HTTP/JS/secret/browser recon), the workspace at ${workdir}/recon is seeded with companion modules for the other recon phases. They are all Python, all scope-gated by recon/scope.txt, and all degrade gracefully without paid keys. Use them instead of hand-writing equivalents:
72015
+
72016
+ - Subdomain enumeration: \`python recon/recon_subdomains.py example.com\` \u2014 pulls Certificate Transparency logs (crt.sh) and optionally brute-forces (\`--wordlist words.txt\`), then resolves. Add \`--add-scope\` to append live subdomains to scope.txt automatically.
72017
+ - Port scan + banner grab: \`python recon/recon_ports.py host\` \u2014 async TCP connect scan of common ports (\`--ports 1-1000\` or \`--ports 22,80,443\`, \`--top N\`), with banner grabbing and OPSEC throttle (\`--throttle-ms\`, \`--jitter-ms\`).
72018
+ - Content/path discovery: \`python recon/recon_content.py https://target\` \u2014 wordlist-driven path discovery (\`--wordlist\`), soft-404 detection, status filtering, throttle/jitter. (Use http_request for fuzzing a SINGLE known parameter; use this for bulk path discovery.)
72019
+ - Wayback / archive mining + CVE intel: \`python recon/recon_intel.py <domain|product>\` \u2014 historical URLs from the Wayback Machine CDX API, and free CVE lookup from the NVD API (\`--cve\` keyword search, e.g. product/version). GitHub code search for leaked secrets is supported when GITHUB_TOKEN is set (\`--github "org filename:.env"\`).
72020
+
72021
+ 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.
72022
+ </recon_phases>`;
70569
72023
  skillsScopePolicy = () => `
70570
72024
 
70571
72025
  <skills_scope>
@@ -70633,9 +72087,9 @@ ${section}` : "";
70633
72087
  if (!envFlagEnabled(process.env.HACKERAI_CLI_DUMP_SYSTEM_PROMPT)) {
70634
72088
  return null;
70635
72089
  }
70636
- await (0, import_promises.mkdir)(workdir, { recursive: true });
70637
- const outputPath = import_node_path8.default.join(workdir, "system-prompt.txt");
70638
- await (0, import_promises.writeFile)(outputPath, system, "utf8");
72090
+ await (0, import_promises2.mkdir)(workdir, { recursive: true });
72091
+ const outputPath = import_node_path10.default.join(workdir, "system-prompt.txt");
72092
+ await (0, import_promises2.writeFile)(outputPath, system, "utf8");
70639
72093
  return outputPath;
70640
72094
  };
70641
72095
  auditSystemPrompt = (system, dumpPath) => {
@@ -70659,130 +72113,6 @@ ${section}` : "";
70659
72113
  }
70660
72114
  });
70661
72115
 
70662
- // src/ui.ts
70663
- var ui_exports = {};
70664
- __export(ui_exports, {
70665
- agentHeader: () => agentHeader,
70666
- buildBanner: () => buildBanner,
70667
- shellPrompt: () => shellPrompt,
70668
- shortCwd: () => shortCwd,
70669
- ui: () => ui,
70670
- vlen: () => vlen
70671
- });
70672
- function shortCwd() {
70673
- const cwd = process.cwd();
70674
- const home = import_node_os4.default.homedir();
70675
- const rel = cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd;
70676
- const norm = rel.replace(/\\/g, "/");
70677
- if (norm.length <= 30) return norm;
70678
- const parts = norm.split("/");
70679
- return parts.length > 3 ? `${parts[0]}/\u2026/${parts[parts.length - 1]}` : norm.slice(-30);
70680
- }
70681
- function asciiTitle(text2) {
70682
- const rows = 6;
70683
- const out3 = [];
70684
- for (let r = 0; r < rows; r++) {
70685
- out3.push(
70686
- [...text2].map((ch) => GLYPHS[ch] ? GLYPHS[ch][r] : " ").join(" ")
70687
- );
70688
- }
70689
- return out3;
70690
- }
70691
- function buildBanner(_systemPromptChars, version3 = "1") {
70692
- const user = (import_node_os4.default.userInfo().username || "hacker").toLowerCase();
70693
- const host = (import_node_os4.default.hostname() || "localhost").split(".")[0].toLowerCase();
70694
- const art = asciiTitle("CLAWFAST");
70695
- const artW = vlen(art[0]);
70696
- const slogan = "Com o clawfast voc\xEA faz tudo";
70697
- const header = [
70698
- "",
70699
- ...art.map((line) => `${C3.greenB}${C3.bold}${line}${C3.reset}`),
70700
- "",
70701
- `${C3.cyanB}${C3.bold}${center(slogan, artW)}${C3.reset}`,
70702
- ""
70703
- ];
70704
- const info = [
70705
- { text: "" },
70706
- { text: `Bem-vindo de volta, ${user}!`, accent: true },
70707
- { text: "" },
70708
- { text: "clawfast usa a pasta SPRIT/ para criar" },
70709
- { text: "arquivos, scripts e tudo mais." },
70710
- { text: "" },
70711
- { text: `${user}@${host} \xB7 sem limites` },
70712
- { text: shortCwd() },
70713
- { text: "" }
70714
- ];
70715
- const title = `clawfast v${version3}`;
70716
- const topFill = "\u2500".repeat(Math.max(0, BOX_W - vlen(title) - 5));
70717
- const top = `${C3.green}\u256D\u2500 ${C3.greenB}${C3.bold}${title}${C3.reset}${C3.green} ${topFill}\u256E${C3.reset}`;
70718
- const bottom = `${C3.green}\u2570${"\u2500".repeat(BOX_W - 2)}\u256F${C3.reset}`;
70719
- const cardLines = info.map(({ text: text2, accent }) => {
70720
- const color = accent ? `${C3.greenB}${C3.bold}` : C3.green;
70721
- const body = `${color}${padEndV(text2, INNER)}${C3.reset}`;
70722
- return `${C3.green}\u2502 ${C3.reset}${body}${C3.green} \u2502${C3.reset}`;
70723
- });
70724
- const card = [top, ...cardLines, bottom];
70725
- return "\n" + [...header, ...card].join("\n") + "\n";
70726
- }
70727
- function shellPrompt() {
70728
- const user = (import_node_os4.default.userInfo().username || "hacker").toLowerCase();
70729
- const host = (import_node_os4.default.hostname() || "localhost").split(".")[0].toLowerCase();
70730
- const W = 52;
70731
- const label = " \u2709 mensagem ";
70732
- const topFill = "\u2500".repeat(Math.max(0, W - vlen(label) - 3));
70733
- const top = `${C3.green}\u256D\u2500${C3.greenB}${C3.bold}${label}${C3.reset}${C3.green}${topFill}\u256E${C3.reset}`;
70734
- const id = `${user}\u327F${host} \xB7 ${shortCwd()}`;
70735
- const mid = `${C3.green}\u2502 ${C3.dim}${id}${C3.reset}`;
70736
- const bottom = `${C3.green}\u2570\u2500${C3.greenB}${C3.bold}\u276F${C3.reset} `;
70737
- return `
70738
- ${top}
70739
- ${mid}
70740
- ${bottom}`;
70741
- }
70742
- function agentHeader() {
70743
- return `${C3.magenta}\u256D\u2500 clawfast ${"\u2500".repeat(20)}${C3.reset}
70744
- `;
70745
- }
70746
- var import_node_os4, ESC, C3, INNER, BOX_W, vlen, padEndV, center, GLYPHS, ui;
70747
- var init_ui = __esm({
70748
- "src/ui.ts"() {
70749
- "use strict";
70750
- import_node_os4 = __toESM(require("node:os"));
70751
- ESC = "\x1B[";
70752
- C3 = {
70753
- reset: `${ESC}0m`,
70754
- bold: `${ESC}1m`,
70755
- dim: `${ESC}90m`,
70756
- green: `${ESC}32m`,
70757
- greenB: `${ESC}92m`,
70758
- cyan: `${ESC}36m`,
70759
- cyanB: `${ESC}96m`,
70760
- magenta: `${ESC}95m`,
70761
- yellow: `${ESC}93m`,
70762
- red: `${ESC}91m`
70763
- };
70764
- INNER = 42;
70765
- BOX_W = INNER + 4;
70766
- vlen = (s) => [...s.replace(/\x1b\[[0-9;]*m/g, "")].length;
70767
- padEndV = (s, w) => s + " ".repeat(Math.max(0, w - vlen(s)));
70768
- center = (s, w) => {
70769
- const pad = Math.max(0, w - vlen(s));
70770
- const left = Math.floor(pad / 2);
70771
- return " ".repeat(left) + s + " ".repeat(pad - left);
70772
- };
70773
- GLYPHS = {
70774
- C: [" \u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588"],
70775
- L: ["\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588\u2588\u2588\u2588\u2588"],
70776
- A: [" \u2588\u2588\u2588\u2588 ", "\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588\u2588"],
70777
- W: ["\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588 \u2588 \u2588\u2588", "\u2588\u2588 \u2588 \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588 \u2588\u2588 "],
70778
- F: ["\u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588\u2588\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 ", "\u2588\u2588 "],
70779
- S: [" \u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588 ", " \u2588\u2588", " \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588 "],
70780
- T: ["\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 "]
70781
- };
70782
- ui = { C: C3, buildBanner, shellPrompt, agentHeader };
70783
- }
70784
- });
70785
-
70786
72116
  // src/interactive-input.ts
70787
72117
  var interactive_input_exports = {};
70788
72118
  __export(interactive_input_exports, {
@@ -70822,6 +72152,9 @@ var init_interactive_input = __esm({
70822
72152
  // instead of the full message box + dropdown — used for the /skillcreator
70823
72153
  // step-by-step capture so each phase shows what it's asking for.
70824
72154
  this.promptLabel = null;
72155
+ // When true, the submitted value is NOT recorded in ↑ history (used for the
72156
+ // /api key prompt so a pasted secret can't be recalled with the up arrow).
72157
+ this.promptSecret = false;
70825
72158
  this.inputLineIndex = 2;
70826
72159
  import_node_readline3.default.emitKeypressEvents(paste.input);
70827
72160
  paste.input.on("keypress", (str, key) => {
@@ -70835,6 +72168,7 @@ var init_interactive_input = __esm({
70835
72168
  */
70836
72169
  prompt(opts = {}) {
70837
72170
  this.promptLabel = opts.label ?? null;
72171
+ this.promptSecret = opts.secret ?? false;
70838
72172
  const initial = this.promptLabel === null && this.pendingInitial ? this.pendingInitial : "";
70839
72173
  this.pendingInitial = null;
70840
72174
  this.buffer = initial;
@@ -70961,7 +72295,7 @@ var init_interactive_input = __esm({
70961
72295
  this.cursor = name25.length;
70962
72296
  }
70963
72297
  const value = this.paste.expand(this.buffer);
70964
- if (value.trim()) this.history.push(value);
72298
+ if (value.trim() && !this.promptSecret) this.history.push(value);
70965
72299
  this.finishLine();
70966
72300
  return { result: { type: "line", value } };
70967
72301
  }
@@ -71263,6 +72597,7 @@ async function main() {
71263
72597
  );
71264
72598
  const COMMANDS = [
71265
72599
  { name: "/model", desc: "trocar o modelo (seletor com setas)" },
72600
+ { name: "/api", desc: "trocar a chave NVIDIA (testa e ativa na hora)" },
71266
72601
  { name: "/skills", desc: "listar as skills instaladas" },
71267
72602
  { name: "/skillcreator", desc: "criar uma nova skill" },
71268
72603
  { name: "/system", desc: "salvar o system prompt (HTML) na \xC1rea de Trabalho" },
@@ -71297,13 +72632,13 @@ async function main() {
71297
72632
  const desktopDir = () => {
71298
72633
  const home = import_node_os6.default.homedir();
71299
72634
  const candidates = [
71300
- process.env.OneDrive ? import_node_path9.default.join(process.env.OneDrive, "Desktop") : null,
71301
- process.env.USERPROFILE ? import_node_path9.default.join(process.env.USERPROFILE, "Desktop") : null,
71302
- import_node_path9.default.join(home, "Desktop"),
71303
- import_node_path9.default.join(home, "\xC1rea de Trabalho"),
71304
- import_node_path9.default.join(home, "OneDrive", "Desktop")
72635
+ process.env.OneDrive ? import_node_path11.default.join(process.env.OneDrive, "Desktop") : null,
72636
+ process.env.USERPROFILE ? import_node_path11.default.join(process.env.USERPROFILE, "Desktop") : null,
72637
+ import_node_path11.default.join(home, "Desktop"),
72638
+ import_node_path11.default.join(home, "\xC1rea de Trabalho"),
72639
+ import_node_path11.default.join(home, "OneDrive", "Desktop")
71305
72640
  ].filter((p) => Boolean(p));
71306
- return candidates.find((p) => (0, import_node_fs8.existsSync)(p)) ?? home;
72641
+ return candidates.find((p) => (0, import_node_fs10.existsSync)(p)) ?? home;
71307
72642
  };
71308
72643
  const printFatal = (err) => {
71309
72644
  process.stderr.write(
@@ -71541,6 +72876,58 @@ ${C5.dim}ja disponivel para todos os modelos nesta sessao.${C5.reset}
71541
72876
  printFatal(err);
71542
72877
  }
71543
72878
  };
72879
+ const handleApiSwap = async (inlineKey) => {
72880
+ let raw = inlineKey;
72881
+ if (!raw) {
72882
+ process.stdout.write(
72883
+ `${C5.dim}Cole a nova chave NVIDIA (cria em ${C5.reset}${C5.cyan}https://build.nvidia.com/${C5.reset}${C5.dim}). Enter vazio ou Ctrl+C cancela.${C5.reset}
72884
+ `
72885
+ );
72886
+ const res = await inputUI.prompt({
72887
+ label: "nova NVIDIA API key",
72888
+ secret: true
72889
+ });
72890
+ if (res.type !== "line" || !res.value.trim()) {
72891
+ process.stdout.write(`${C5.dim}troca de chave cancelada${C5.reset}
72892
+ `);
72893
+ return;
72894
+ }
72895
+ raw = res.value;
72896
+ }
72897
+ const key = raw.replace(/\s+/g, "").match(/nvapi-[A-Za-z0-9_-]+/)?.[0] ?? "";
72898
+ if (!key) {
72899
+ process.stdout.write(
72900
+ `${C5.red}\u2717 n\xE3o encontrei uma chave NVIDIA no que foi colado${C5.reset} ${C5.dim}(esperado um token "nvapi-\u2026"; cole a chave completa).${C5.reset}
72901
+ `
72902
+ );
72903
+ return;
72904
+ }
72905
+ process.stdout.write(`${C5.dim}testando a chave na NVIDIA\u2026${C5.reset}
72906
+ `);
72907
+ const test = await testNvidiaKey(key);
72908
+ if (!test.ok) {
72909
+ if (test.status === 401 || test.status === 403) {
72910
+ process.stdout.write(
72911
+ `${C5.red}\u2717 a NVIDIA recusou a chave (${test.status}): ${test.detail}${C5.reset}
72912
+ ${C5.dim}chave N\xC3O salva \u2014 confira se copiou inteira e se a conta tem acesso de infer\xEAncia.${C5.reset}
72913
+ `
72914
+ );
72915
+ return;
72916
+ }
72917
+ process.stdout.write(
72918
+ `${C5.yellow}\u26A0 n\xE3o consegui validar (${test.status || "rede"}): ${test.detail}${C5.reset}
72919
+ ${C5.dim}salvando mesmo assim \u2014 o pr\xF3ximo pedido vai usar a chave nova.${C5.reset}
72920
+ `
72921
+ );
72922
+ }
72923
+ const file2 = swapNvidiaKey(key);
72924
+ const masked = `${key.slice(0, 9)}\u2026${key.slice(-4)} (${key.length} chars)`;
72925
+ process.stdout.write(
72926
+ `${C5.green}\u2713 chave NVIDIA trocada e ativa${C5.reset} ${C5.dim}${masked}${C5.reset}
72927
+ ${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
72928
+ `
72929
+ );
72930
+ };
71544
72931
  const handleLine = async (line) => {
71545
72932
  if (closing) return;
71546
72933
  const input = line.trim();
@@ -71577,7 +72964,7 @@ ${C5.dim}ja disponivel para todos os modelos nesta sessao.${C5.reset}
71577
72964
  const sysText = agent.getSystemPrompt();
71578
72965
  const audit = agent.getSystemPromptAudit();
71579
72966
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
71580
- const filePath = import_node_path9.default.join(
72967
+ const filePath = import_node_path11.default.join(
71581
72968
  desktopDir(),
71582
72969
  `clawfast-system-prompt_${stamp}.html`
71583
72970
  );
@@ -71585,7 +72972,7 @@ ${C5.dim}ja disponivel para todos os modelos nesta sessao.${C5.reset}
71585
72972
  const b64 = Buffer.from(sysText, "utf8").toString("base64");
71586
72973
  const html = `<!doctype html><html lang="pt-br"><head><meta charset="utf-8"><title>clawfast \u2014 system prompt</title><style>body{background:#0b0f0b;color:#bfffbf;font:14px/1.55 ui-monospace,Consolas,Menlo,monospace;margin:0;padding:28px}h1{color:#7CFC00;font-size:18px;margin:0 0 6px}.meta{color:#6f9a6f;margin:0 0 18px;font-size:12px}pre{white-space:pre-wrap;word-wrap:break-word;margin:0}</style></head><body><h1>clawfast \u2014 system prompt</h1><div class="meta">${meta3}</div><pre id="c"></pre><script>document.getElementById("c").textContent=decodeURIComponent(escape(atob("${b64}")));</script></body></html>`;
71587
72974
  try {
71588
- await (0, import_promises2.writeFile)(filePath, html, "utf8");
72975
+ await (0, import_promises3.writeFile)(filePath, html, "utf8");
71589
72976
  process.stdout.write(
71590
72977
  `${C5.green}\u2713 system prompt salvo${C5.reset} ${C5.dim}(${sysText.length.toLocaleString()} caracteres \u2014 abra no navegador)${C5.reset}
71591
72978
  ${C5.cyan}${filePath}${C5.reset}
@@ -71599,12 +72986,15 @@ ${C5.cyan}${filePath}${C5.reset}
71599
72986
  }
71600
72987
  return;
71601
72988
  }
72989
+ if (input === "/api" || input.startsWith("/api ")) {
72990
+ await handleApiSwap(input.replace(/^\/api\s*/, "").trim());
72991
+ return;
72992
+ }
71602
72993
  if (input === "/nov" || input === "/novidades") {
71603
- process.stdout.write(
71604
- `
71605
- ${C5.cyan}${renderNews(clawfastVersion())}${C5.reset}
71606
- `
71607
- );
72994
+ process.stdout.write(`
72995
+ ${renderNews(clawfastVersion())}
72996
+
72997
+ `);
71608
72998
  return;
71609
72999
  }
71610
73000
  if (!input) return;
@@ -71692,14 +73082,14 @@ ${C5.dim}interrompido \u2014 Ctrl+C de novo para fechar${C5.reset}
71692
73082
  }
71693
73083
  await shutdown();
71694
73084
  }
71695
- var import_node_os6, import_node_path9, import_node_fs8, import_promises2, deepseekEnabled, configuredModelProviders;
73085
+ var import_node_os6, import_node_path11, import_node_fs10, import_promises3, deepseekEnabled, configuredModelProviders;
71696
73086
  var init_index = __esm({
71697
73087
  "index.ts"() {
71698
73088
  "use strict";
71699
73089
  import_node_os6 = __toESM(require("node:os"));
71700
- import_node_path9 = __toESM(require("node:path"));
71701
- import_node_fs8 = require("node:fs");
71702
- import_promises2 = require("node:fs/promises");
73090
+ import_node_path11 = __toESM(require("node:path"));
73091
+ import_node_fs10 = require("node:fs");
73092
+ import_promises3 = require("node:fs/promises");
71703
73093
  init_paste_input();
71704
73094
  init_boot_ui();
71705
73095
  init_config();