@wrongstack/tools 0.276.4 → 0.277.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pack.js CHANGED
@@ -15,7 +15,7 @@ import * as ts from 'typescript';
15
15
  import { toErrorMessage as toErrorMessage$1 } from '@wrongstack/core/utils/error';
16
16
  import * as dns from 'node:dns/promises';
17
17
  import * as net from 'node:net';
18
- import { Agent } from 'undici';
18
+ import { Agent, fetch } from 'undici';
19
19
  import TurndownService from 'turndown';
20
20
  import { randomUUID } from 'node:crypto';
21
21
 
@@ -714,16 +714,28 @@ function resolvePowerShell(cmd) {
714
714
  }
715
715
  return resolved;
716
716
  }
717
- var WIN32_SHELL_META = /[&|<>\r\n\0]/;
717
+ var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
718
718
  function assertSafeWin32ShellArgs(args) {
719
- for (const a of args) {
720
- if (typeof a === "string" && WIN32_SHELL_META.test(a)) {
719
+ for (const arg of args) {
720
+ if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
721
721
  throw new Error(
722
- "win32 shell spawn: argument contains a shell metacharacter (one of & | < > or a newline) that could enable command injection through the .cmd/.bat wrapper \u2014 refusing to run. Offending argument: " + JSON.stringify(a)
722
+ 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
723
723
  );
724
724
  }
725
725
  }
726
726
  }
727
+ function buildWin32CmdShimInvocation(command, args = []) {
728
+ assertSafeWin32ShellArgs([command, ...args]);
729
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
730
+ return {
731
+ command: process.env["COMSPEC"] ?? "cmd.exe",
732
+ args: ["/d", "/c", line],
733
+ windowsVerbatimArguments: true
734
+ };
735
+ }
736
+ function quoteWin32CmdArg(arg) {
737
+ return `"${arg}"`;
738
+ }
727
739
 
728
740
  // src/_spawn-stream.ts
729
741
  var isWin = process.platform === "win32";
@@ -738,15 +750,16 @@ async function* spawnStream(opts) {
738
750
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
739
751
  const resolved = resolveWin32Command(opts.cmd);
740
752
  const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
741
- const cmd = needsShell ? opts.cmd : resolved;
742
- if (needsShell) assertSafeWin32ShellArgs(opts.args);
743
- const child = spawn(cmd, opts.args, {
753
+ const shim = needsShell ? buildWin32CmdShimInvocation(resolved, opts.args) : null;
754
+ const cmd = shim?.command ?? resolved;
755
+ const args = shim?.args ?? opts.args;
756
+ const child = spawn(cmd, args, {
744
757
  cwd: opts.cwd,
745
758
  env: buildChildEnv(),
746
759
  stdio: ["ignore", "pipe", "pipe"],
747
760
  windowsHide: true,
748
761
  ...isWin ? {} : { signal: opts.signal },
749
- ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
762
+ ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
750
763
  });
751
764
  const registry = getProcessRegistry();
752
765
  const pid = child.pid;
@@ -5408,9 +5421,9 @@ var designTool = {
5408
5421
  category: "Design",
5409
5422
  description: 'Browse, load, customize, and enforce curated frontend/mobile UI design kits. Use BEFORE writing UI code to commit to one coherent, modern, responsive, dark/light, accessible design. Actions: "list" (menu), "use" (load+pin a kit for a stack), "foundations" (baseline), "set" (override kit colors/tokens), "materialize" (write the tokens to a real theme file \u2014 CSS @theme/OKLCH or native), "verify" (scan UI files for off-palette colors).',
5410
5423
  usageHint: 'Flow: `design {action:"use", kit:"minimal-clarity", stack:"web"}` \u2192 optionally `design {action:"set", set:{primary:"oklch(62% 0.2 25)"}}` \u2192 `design {action:"materialize"}` to write tokens to disk \u2192 implement against them \u2192 `design {action:"verify"}`.',
5411
- permission: "auto",
5412
- mutating: false,
5413
- capabilities: [],
5424
+ permission: "confirm",
5425
+ mutating: true,
5426
+ capabilities: ["fs.write"],
5414
5427
  timeoutMs: 15e3,
5415
5428
  inputSchema: {
5416
5429
  type: "object",
@@ -6146,10 +6159,20 @@ var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
6146
6159
  // C / C++ / native build
6147
6160
  "make",
6148
6161
  "cmake",
6162
+ "ninja",
6163
+ "clang",
6164
+ "clang-cl",
6165
+ "gcc",
6166
+ "g++",
6167
+ "link",
6168
+ "msbuild",
6149
6169
  // containers / orchestration
6150
6170
  "docker",
6151
6171
  "podman",
6152
6172
  "kubectl",
6173
+ // network (read-only intent; destructive ops still blocked by BLOCKED_ARG_PATTERNS)
6174
+ "curl",
6175
+ "wget",
6153
6176
  // common POSIX file/text utilities
6154
6177
  "pwd",
6155
6178
  "ls",
@@ -6169,7 +6192,509 @@ var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
6169
6192
  "cp",
6170
6193
  "mv",
6171
6194
  "rm",
6172
- "touch"
6195
+ "touch",
6196
+ "tar",
6197
+ // Windows-native tooling (win32). All non-destructive binaries; per-arg
6198
+ // safety is still enforced by BLOCKED_ARG_PATTERNS + the destructive-ops
6199
+ // gate in bash-kill-guard.ts. `gh` is included even though it lives at
6200
+ // "C:\Program Files\GitHub CLI\gh.exe" — resolveWin32Command handles the
6201
+ // space in the path.
6202
+ "gh",
6203
+ "where",
6204
+ "tasklist",
6205
+ "systeminfo",
6206
+ "wmic",
6207
+ "sc",
6208
+ "netstat",
6209
+ "ipconfig",
6210
+ "nslookup",
6211
+ "tracert",
6212
+ "pathping",
6213
+ // Windows shell interpreters. Without these, `exec` cannot run cmd builtins
6214
+ // (`dir`, `type`, `copy`, …) or any PowerShell cmdlet on Windows — a major
6215
+ // gap since those are the platform's primary shells. Arbitrary execution
6216
+ // through `cmd /c …` / `powershell -c …` is NOT a new hole: the permission
6217
+ // policy reconstructs the full command line (shellCommandLineFromInput) and
6218
+ // still runs it through the YOLO destructive classifier, so a genuinely
6219
+ // destructive `cmd /c del /s /q C:\…` continues to require confirmation.
6220
+ "cmd",
6221
+ "cmd.exe",
6222
+ "powershell",
6223
+ "powershell.exe",
6224
+ "pwsh",
6225
+ "pwsh.exe",
6226
+ // [core] Extended default allowlist (added 4b3d18d1 + this commit). All
6227
+ // non-destructive, broadly-used dev binaries. Per-arg safety is still
6228
+ // enforced by BLOCKED_ARG_PATTERNS + bash-kill-guard.ts.
6229
+ // --- Archives & compression ---
6230
+ "7z",
6231
+ "7za",
6232
+ "bzip2",
6233
+ "gzip",
6234
+ "xz",
6235
+ "unzip",
6236
+ "zip",
6237
+ "gtar",
6238
+ "bsdtar",
6239
+ "star",
6240
+ "pax",
6241
+ "cpio",
6242
+ // --- Android / mobile dev ---
6243
+ "adb",
6244
+ "fastboot",
6245
+ "sdkmanager",
6246
+ // --- DevOps / config mgmt ---
6247
+ "ansible",
6248
+ "ansible-playbook",
6249
+ "ansible-vault",
6250
+ "ansible-lint",
6251
+ "ansible-galaxy",
6252
+ "molecule",
6253
+ // --- Cloud CLIs ---
6254
+ "aws",
6255
+ "aws-vault",
6256
+ "awslocal",
6257
+ "az",
6258
+ "azcopy",
6259
+ "gcloud",
6260
+ "gsutil",
6261
+ "doctl",
6262
+ "linode-cli",
6263
+ // --- Native / C / C++ / linker tools ---
6264
+ "clang++",
6265
+ "clang-format",
6266
+ "clang-tidy",
6267
+ "clangd",
6268
+ "lld",
6269
+ "lldb",
6270
+ "ctest",
6271
+ "gmake",
6272
+ "meson",
6273
+ "conan",
6274
+ "vcpkg",
6275
+ "cl",
6276
+ "rc",
6277
+ "mt",
6278
+ "dumpbin",
6279
+ "dotnet-format",
6280
+ // --- Image / media / binary tools ---
6281
+ "convert",
6282
+ "ffmpeg",
6283
+ "ffprobe",
6284
+ "magick",
6285
+ "gs",
6286
+ "exiftool",
6287
+ // --- HTTP / fetch ---
6288
+ "wget2",
6289
+ "aria2c",
6290
+ "axel",
6291
+ "httpie",
6292
+ "hey",
6293
+ "ab",
6294
+ "wrk",
6295
+ "http",
6296
+ // --- Diff / patch / merge ---
6297
+ "diff",
6298
+ "diff3",
6299
+ "patch",
6300
+ "meld",
6301
+ "kdiff3",
6302
+ "kompare",
6303
+ // --- Encoding / file inspection ---
6304
+ "dos2unix",
6305
+ "unix2dos",
6306
+ "iconv",
6307
+ "file",
6308
+ "stat",
6309
+ "xxd",
6310
+ "hexdump",
6311
+ "od",
6312
+ "base64",
6313
+ // --- SSH / crypto / signing ---
6314
+ "ssh",
6315
+ "ssh-add",
6316
+ "ssh-keygen",
6317
+ "ssh-keyscan",
6318
+ "scp",
6319
+ "sftp",
6320
+ "rsync",
6321
+ "gpg",
6322
+ "gpg2",
6323
+ "gpg-agent",
6324
+ "openssl",
6325
+ "step",
6326
+ "keytool",
6327
+ // --- Search ---
6328
+ "egrep",
6329
+ "fgrep",
6330
+ "ag",
6331
+ "ack",
6332
+ "sift",
6333
+ "ugrep",
6334
+ "fd",
6335
+ "fdfind",
6336
+ "jq",
6337
+ "yq",
6338
+ "xq",
6339
+ "fx",
6340
+ "gron",
6341
+ // --- K8s / container ecosystem ---
6342
+ "kubectl.exe",
6343
+ "kubeadm",
6344
+ "kubelet",
6345
+ "helm",
6346
+ "k9s",
6347
+ "kustomize",
6348
+ "skaffold",
6349
+ "tilt",
6350
+ "minikube",
6351
+ "kind",
6352
+ "k3d",
6353
+ "k3s",
6354
+ "docker-compose",
6355
+ "buildah",
6356
+ "skopeo",
6357
+ "nerdctl",
6358
+ "ctr",
6359
+ "ctr.exe",
6360
+ // --- Databases ---
6361
+ "sqlite3",
6362
+ "sqlite",
6363
+ "psql",
6364
+ "pg_dump",
6365
+ "pg_restore",
6366
+ "mysql",
6367
+ "mysqladmin",
6368
+ "mysqldump",
6369
+ "mariadb",
6370
+ "mariadb-dump",
6371
+ "redis-cli",
6372
+ "redis-server",
6373
+ "memcached",
6374
+ "etcdctl",
6375
+ "consul",
6376
+ "vault",
6377
+ "nomad",
6378
+ "mongosh",
6379
+ "mongo",
6380
+ "mongoexport",
6381
+ "mongoimport",
6382
+ "mongodump",
6383
+ "mongorestore",
6384
+ // --- Windows extended (read-mostly ops) ---
6385
+ "taskkill",
6386
+ "gpupdate",
6387
+ "gpresult",
6388
+ "hostname",
6389
+ "whoami",
6390
+ "who",
6391
+ "net",
6392
+ "net1",
6393
+ // --- VCS ecosystem ---
6394
+ "glab",
6395
+ "hub",
6396
+ "tea",
6397
+ "git-lfs",
6398
+ "tig",
6399
+ "lazygit",
6400
+ // --- POSIX text utilities (extended; duplicates of pwd/ls/cat/head/tail/wc/
6401
+ // grep/find/echo/awk/mkdir/cp/mv/rm/touch from the base list above are
6402
+ // omitted — the Set is de-duplicated at runtime but a clean literal is
6403
+ // easier to maintain) ---
6404
+ "gawk",
6405
+ "tr",
6406
+ "cut",
6407
+ "paste",
6408
+ "join",
6409
+ "comm",
6410
+ "expand",
6411
+ "unexpand",
6412
+ "fold",
6413
+ "fmt",
6414
+ "nl",
6415
+ "pr",
6416
+ "column",
6417
+ "tsort",
6418
+ "tty",
6419
+ "ul",
6420
+ "units",
6421
+ "factor",
6422
+ "seq",
6423
+ "shuf",
6424
+ "look",
6425
+ "yes",
6426
+ "true",
6427
+ "false",
6428
+ "test",
6429
+ "[",
6430
+ "printf",
6431
+ "env",
6432
+ "tree",
6433
+ "locate",
6434
+ "which",
6435
+ "whereis",
6436
+ "type",
6437
+ "hash",
6438
+ "pushd",
6439
+ "popd",
6440
+ "dirs",
6441
+ "history",
6442
+ "fc",
6443
+ "jobs",
6444
+ "bg",
6445
+ "fg",
6446
+ "wait",
6447
+ "ulimit",
6448
+ "umask",
6449
+ "nice",
6450
+ "nohup",
6451
+ "timeout",
6452
+ "time",
6453
+ "trap",
6454
+ "exit",
6455
+ "return",
6456
+ "source",
6457
+ ".",
6458
+ "alias",
6459
+ "unalias",
6460
+ "set",
6461
+ "unset",
6462
+ "export",
6463
+ "readonly",
6464
+ "typeset",
6465
+ "declare",
6466
+ "local",
6467
+ "eval",
6468
+ "exec",
6469
+ // --- Process / system inspection ---
6470
+ "htop",
6471
+ "top",
6472
+ "atop",
6473
+ "glances",
6474
+ "iotop",
6475
+ "nethogs",
6476
+ "iftop",
6477
+ "lsof",
6478
+ "strace",
6479
+ "ltrace",
6480
+ "sysstat",
6481
+ "vmstat",
6482
+ "iostat",
6483
+ "mpstat",
6484
+ "sar",
6485
+ "free",
6486
+ "df",
6487
+ "du",
6488
+ "mount",
6489
+ "umount",
6490
+ "lsblk",
6491
+ "blkid",
6492
+ "kill",
6493
+ "killall",
6494
+ "pkill",
6495
+ "pgrep",
6496
+ "pidof",
6497
+ "ps",
6498
+ "ps.exe",
6499
+ // --- Network inspection ---
6500
+ "ip",
6501
+ "ss",
6502
+ "route",
6503
+ "arp",
6504
+ "arping",
6505
+ "ping",
6506
+ "ping6",
6507
+ "hping3",
6508
+ "mtr",
6509
+ "tracepath",
6510
+ "tcpdump",
6511
+ "nmap",
6512
+ "netcat",
6513
+ "nc",
6514
+ "ncat",
6515
+ "socat",
6516
+ // --- Sync / backup ---
6517
+ "rclone",
6518
+ "restic",
6519
+ "borg",
6520
+ "duplicati",
6521
+ "duplicacy",
6522
+ "syncthing",
6523
+ "syncthing-cli",
6524
+ // --- Permissions / users / ACLs (POSIX) ---
6525
+ "useradd",
6526
+ "userdel",
6527
+ "usermod",
6528
+ "groupadd",
6529
+ "groupdel",
6530
+ "groupmod",
6531
+ "chown",
6532
+ "chmod",
6533
+ "chgrp",
6534
+ "getfacl",
6535
+ "setfacl",
6536
+ "setcap",
6537
+ "getcap",
6538
+ // --- Crypto / cert mgmt (extended) ---
6539
+ "certbot",
6540
+ "mkcert",
6541
+ "jarsigner",
6542
+ // --- Editors ---
6543
+ "subl",
6544
+ "code",
6545
+ "code-insiders",
6546
+ "cursor",
6547
+ "atom",
6548
+ "nano",
6549
+ "vim",
6550
+ "nvim",
6551
+ "vi",
6552
+ "emacs",
6553
+ "helix",
6554
+ "hx",
6555
+ "micro",
6556
+ "jed",
6557
+ "ed",
6558
+ "ex",
6559
+ "mg",
6560
+ // --- Terminal multiplexers ---
6561
+ "asciinema",
6562
+ "script",
6563
+ "scriptreplay",
6564
+ "expect",
6565
+ "screen",
6566
+ "tmux",
6567
+ "byobu",
6568
+ "dtach",
6569
+ "abduco",
6570
+ // --- Calculators / REPLs / scientific ---
6571
+ "bc",
6572
+ "dc",
6573
+ "calc",
6574
+ "qalc",
6575
+ "genius",
6576
+ "octave",
6577
+ "R",
6578
+ "Rscript",
6579
+ "julia",
6580
+ "irb",
6581
+ "pry",
6582
+ "ghci",
6583
+ "stack",
6584
+ "cabal",
6585
+ "ghc",
6586
+ // --- PHP / Lua / Perl / Ruby ecosystem (extended) ---
6587
+ "php8",
6588
+ "php7",
6589
+ "phpcs",
6590
+ "phpcbf",
6591
+ "phpmd",
6592
+ "phpstan",
6593
+ "psalm",
6594
+ "lua",
6595
+ "lua5.1",
6596
+ "lua5.2",
6597
+ "lua5.3",
6598
+ "lua5.4",
6599
+ "luarocks",
6600
+ "perl",
6601
+ "cpan",
6602
+ "prove",
6603
+ "plackup",
6604
+ "rake",
6605
+ "rspec",
6606
+ "jekyll",
6607
+ "node-gyp",
6608
+ "node-pre-gyp",
6609
+ // --- JS / TS toolchain (extended) ---
6610
+ "electron",
6611
+ "electron-builder",
6612
+ "electron-forge",
6613
+ "vite-preview",
6614
+ "swc",
6615
+ "swc-cli",
6616
+ "swcpack",
6617
+ "mocha",
6618
+ "chai",
6619
+ "jasmine",
6620
+ "puppeteer",
6621
+ "lighthouse",
6622
+ // --- Linters / formatters (extended) ---
6623
+ "tslint",
6624
+ "stylelint",
6625
+ "htmlhint",
6626
+ "jshint",
6627
+ "jslint",
6628
+ "jscs",
6629
+ // --- Document conversion ---
6630
+ "pandoc",
6631
+ "weasyprint",
6632
+ "wkhtmltopdf",
6633
+ "wkhtmltoimage",
6634
+ "prince",
6635
+ "mdp",
6636
+ "markdown",
6637
+ "multimarkdown",
6638
+ "cmark",
6639
+ "cmark-gfm",
6640
+ // --- Office / spreadsheet ---
6641
+ "soffice",
6642
+ "libreoffice",
6643
+ "unoconv",
6644
+ "abiword",
6645
+ "gnumeric",
6646
+ // --- Spell / grammar ---
6647
+ "aspell",
6648
+ "hunspell",
6649
+ "enchant",
6650
+ "languagetool",
6651
+ // --- Source-highlight / diff tools ---
6652
+ "delta",
6653
+ "bat",
6654
+ "ccat",
6655
+ "hl",
6656
+ "highlight",
6657
+ "source-highlight",
6658
+ "ansifilter",
6659
+ // --- Job schedulers ---
6660
+ "pueue",
6661
+ "task-spooler",
6662
+ "ts",
6663
+ "at",
6664
+ "atd",
6665
+ "anacron",
6666
+ "fcron",
6667
+ "cronie",
6668
+ "systemd-run",
6669
+ "systemd-cat",
6670
+ // --- Plotting / visualization ---
6671
+ "gnuplot",
6672
+ "gnuplot-nox",
6673
+ "veusz",
6674
+ "scidavis",
6675
+ "grace",
6676
+ "xmgrace",
6677
+ "labplot",
6678
+ // --- Security / recon (network + web) ---
6679
+ "masscan",
6680
+ "zmap",
6681
+ "rustscan",
6682
+ "amass",
6683
+ "subfinder",
6684
+ "httpx",
6685
+ "nuclei",
6686
+ "naabu",
6687
+ "katana",
6688
+ "dnsx",
6689
+ "assetfinder",
6690
+ "findomain",
6691
+ "gau",
6692
+ "waybackurls",
6693
+ "httprobe",
6694
+ "meg",
6695
+ "subjack",
6696
+ "sublert",
6697
+ "chaos"
6173
6698
  ]);
6174
6699
  var allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
6175
6700
  var normalizeCmd = (c) => c.trim();
@@ -6377,17 +6902,18 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
6377
6902
  const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
6378
6903
  const resolved = resolveWin32Command(cmd);
6379
6904
  const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
6380
- const spawnCmd = needsShell ? cmd : resolved;
6381
- if (needsShell) assertSafeWin32ShellArgs(args);
6905
+ const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
6906
+ const spawnCmd = shim?.command ?? resolved;
6907
+ const spawnArgs = shim?.args ?? args;
6382
6908
  let child;
6383
6909
  try {
6384
- child = spawn(spawnCmd, args, {
6910
+ child = spawn(spawnCmd, spawnArgs, {
6385
6911
  cwd,
6386
6912
  env: buildChildEnv(sessionId),
6387
6913
  stdio: ["ignore", "pipe", "pipe"],
6388
6914
  windowsHide: true,
6389
6915
  ...isWin3 ? {} : { signal },
6390
- ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
6916
+ ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
6391
6917
  });
6392
6918
  } catch (err) {
6393
6919
  spool.finalize();
@@ -6482,6 +7008,7 @@ TD.addRule("stripDangerousElements", {
6482
7008
  });
6483
7009
  var MAX_BYTES = 131072;
6484
7010
  var TIMEOUT_MS = 2e4;
7011
+ var nativeGlobalFetch = globalThis.fetch;
6485
7012
  var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
6486
7013
  if (ALLOW_PRIVATE && !process.env["CI"]) {
6487
7014
  console.warn(
@@ -6531,6 +7058,9 @@ function getPinnedDispatcher() {
6531
7058
  }
6532
7059
  return pinnedAgent;
6533
7060
  }
7061
+ function dispatcherFetch() {
7062
+ return globalThis.fetch === nativeGlobalFetch ? fetch : globalThis.fetch;
7063
+ }
6534
7064
  var _beforeExitRegistered = false;
6535
7065
  if (!_beforeExitRegistered) {
6536
7066
  _beforeExitRegistered = true;
@@ -6566,7 +7096,7 @@ async function guardedFetch(url, maxRedirects, signal, headers = {
6566
7096
  headers,
6567
7097
  dispatcher: getPinnedDispatcher()
6568
7098
  };
6569
- const res = await fetch(currentUrl, init);
7099
+ const res = await dispatcherFetch()(currentUrl, init);
6570
7100
  if (res.status < 300 || res.status > 399) {
6571
7101
  return res;
6572
7102
  }
@@ -6594,7 +7124,7 @@ var fetchTool = {
6594
7124
  category: "Network",
6595
7125
  description: "Fetch a URL and return its content. HTML pages are automatically converted to clean markdown. This tool has strong SSRF protections (private IPs, localhost, and cloud metadata endpoints are blocked by default).",
6596
7126
  usageHint: "Use this when you need external information (documentation, API responses, web pages, etc.).\n\nSecurity notes:\n- Only HTTPS is allowed by default.\n- Internal/private networks are blocked unless explicitly enabled via environment variable.\n- Redirects are followed but re-validated at each hop.\n- Output is capped (128KB by default) to avoid flooding context.\nPrefer this over raw `bash curl` or `bash wget`.",
6597
- permission: "confirm",
7127
+ permission: "auto",
6598
7128
  mutating: false,
6599
7129
  capabilities: ["net.outbound"],
6600
7130
  icon: "web",
@@ -6834,7 +7364,7 @@ var formatTool = {
6834
7364
  usageHint: "RUN REGULARLY:\n\n- Use on changed files before committing.\n- `check: true` verifies formatting without making changes (useful in CI-like flows).\nThis project has very consistent formatting expectations. Always ensure your changes are formatted.",
6835
7365
  permission: "confirm",
6836
7366
  mutating: true,
6837
- capabilities: ["fs.write", "shell.exec"],
7367
+ capabilities: ["fs.write", "shell.restricted"],
6838
7368
  icon: "code",
6839
7369
  timeoutMs: 6e4,
6840
7370
  inputSchema: {
@@ -8739,9 +9269,10 @@ function runOutdated(manager, args, cwd, signal) {
8739
9269
  const MAX = 1e5;
8740
9270
  const resolved = resolveWin32Command(manager);
8741
9271
  const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
8742
- const spawnCmd = needsShell ? manager : resolved;
8743
- if (needsShell) assertSafeWin32ShellArgs(args);
8744
- const child = spawn(spawnCmd, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true, ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {} });
9272
+ const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
9273
+ const spawnCmd = shim?.command ?? resolved;
9274
+ const spawnArgs = shim?.args ?? args;
9275
+ const child = spawn(spawnCmd, spawnArgs, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true, ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {} });
8745
9276
  child.stdout?.on("data", (c) => {
8746
9277
  if (stdout.length < MAX) stdout += c.toString();
8747
9278
  });
@@ -9782,7 +10313,7 @@ var searchTool = {
9782
10313
  category: "Search",
9783
10314
  description: "Perform a web search and return results with title, URL, and snippet. Use this when you need up-to-date external information that is not in the local codebase. Results are cached (5 min TTL) and deduplicated by URL.",
9784
10315
  usageHint: "Good for: API documentation, error messages, library usage examples, current best practices.\n\n- Prefer specific queries over very broad ones.\n- Results go through the guarded fetch system (same protections as the `fetch` tool).\n- Supports duckduckgo (default), google, and bing sources.\n- Set `skip_cache: true` to force a fresh search.\n- This is often better than the model trying to recall outdated knowledge.",
9785
- permission: "confirm",
10316
+ permission: "auto",
9786
10317
  mutating: false,
9787
10318
  capabilities: ["net.outbound"],
9788
10319
  icon: "search",
@@ -9845,15 +10376,15 @@ var searchTool = {
9845
10376
  };
9846
10377
  yield {
9847
10378
  type: "partial_output",
9848
- text: `${results.length} cached results from ${source}`,
9849
- data: { count: results.length, cached: true }
10379
+ text: `${results.length} cached results from ${entry.source}`,
10380
+ data: { count: results.length, cached: true, source: entry.source }
9850
10381
  };
9851
10382
  yield {
9852
10383
  type: "final",
9853
10384
  output: {
9854
10385
  query: input.query,
9855
10386
  results: results.slice(0, num),
9856
- source,
10387
+ source: entry.source,
9857
10388
  truncated: results.length >= num,
9858
10389
  cached: true
9859
10390
  }
@@ -9867,6 +10398,7 @@ var searchTool = {
9867
10398
  data: { source, query: input.query, cached: false }
9868
10399
  };
9869
10400
  let rawResults;
10401
+ let effectiveSource = source;
9870
10402
  switch (source) {
9871
10403
  case "duckduckgo":
9872
10404
  rawResults = await duckduckgoSearch(input.query, num, opts.signal);
@@ -9883,24 +10415,24 @@ var searchTool = {
9883
10415
  field: "source"
9884
10416
  });
9885
10417
  }
9886
- const seenUrls = /* @__PURE__ */ new Set();
9887
- const deduped = [];
9888
- for (const r of rawResults) {
9889
- const noQuery = r.url.split("?")[0] ?? r.url;
9890
- const normalized = noQuery.split("#")[0] ?? r.url;
9891
- if (!seenUrls.has(normalized) && r.url.startsWith("http")) {
9892
- seenUrls.add(normalized);
9893
- deduped.push(r);
9894
- }
10418
+ let ranked = rankSearchResults(rawResults, input.query);
10419
+ if (source !== "duckduckgo" && shouldFallbackToDuckDuckGo(ranked, input.query)) {
10420
+ yield {
10421
+ type: "log",
10422
+ text: `${source} returned no relevant static results; falling back to duckduckgo`,
10423
+ data: { source, fallback: "duckduckgo", query: input.query }
10424
+ };
10425
+ rawResults = await duckduckgoSearch(input.query, num, opts.signal);
10426
+ ranked = rankSearchResults(rawResults, input.query);
10427
+ effectiveSource = "duckduckgo";
9895
10428
  }
9896
- const ranked = scoreResults(deduped, input.query);
9897
10429
  const finalResults = ranked.slice(0, num);
9898
- cache.set(cacheKey, { results: ranked, timestamp: Date.now() });
10430
+ cache.set(cacheKey, { results: ranked, source: effectiveSource, timestamp: Date.now() });
9899
10431
  pruneStaleCacheEntries();
9900
10432
  yield {
9901
10433
  type: "partial_output",
9902
- text: `${finalResults.length} results from ${source}`,
9903
- data: { count: finalResults.length, cached: false }
10434
+ text: `${finalResults.length} results from ${effectiveSource}`,
10435
+ data: { count: finalResults.length, cached: false, source: effectiveSource }
9904
10436
  };
9905
10437
  yield {
9906
10438
  type: "final",
@@ -9911,7 +10443,7 @@ var searchTool = {
9911
10443
  url: r.url,
9912
10444
  snippet: r.snippet
9913
10445
  })),
9914
- source,
10446
+ source: effectiveSource,
9915
10447
  truncated: finalResults.length >= num,
9916
10448
  cached: false
9917
10449
  }
@@ -9924,6 +10456,19 @@ function pruneStaleCacheEntries() {
9924
10456
  if (entry.timestamp < cutoff) cache.delete(key);
9925
10457
  }
9926
10458
  }
10459
+ function rankSearchResults(results, query) {
10460
+ const seenUrls = /* @__PURE__ */ new Set();
10461
+ const deduped = [];
10462
+ for (const r of results) {
10463
+ const noQuery = r.url.split("?")[0] ?? r.url;
10464
+ const normalized = noQuery.split("#")[0] ?? r.url;
10465
+ if (!seenUrls.has(normalized) && r.url.startsWith("http")) {
10466
+ seenUrls.add(normalized);
10467
+ deduped.push(r);
10468
+ }
10469
+ }
10470
+ return scoreResults(deduped, query);
10471
+ }
9927
10472
  function scoreResults(results, query) {
9928
10473
  const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
9929
10474
  return results.map((r) => {
@@ -9937,6 +10482,15 @@ function scoreResults(results, query) {
9937
10482
  return { ...r, score };
9938
10483
  }).sort((a, b) => b.score - a.score);
9939
10484
  }
10485
+ function shouldFallbackToDuckDuckGo(results, query) {
10486
+ if (results.length === 0) return true;
10487
+ const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3);
10488
+ if (terms.length === 0) return false;
10489
+ return !results.some((r) => {
10490
+ const haystack = `${r.title} ${r.url} ${r.snippet}`.toLowerCase();
10491
+ return terms.some((term) => haystack.includes(term));
10492
+ });
10493
+ }
9940
10494
  async function duckduckgoSearch(query, num, signal) {
9941
10495
  const encoded = encodeURIComponent(query);
9942
10496
  const url = `https://lite.duckduckgo.com/lite/?q=${encoded}&kd=-1&kl=wt-wt`;
@@ -9961,14 +10515,19 @@ function takeFrom(iter, max) {
9961
10515
  }
9962
10516
  function parseDuckDuckGo(html, num) {
9963
10517
  const results = [];
9964
- const snippetRegex = /<a class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
9965
- const snippet2Regex = /<a class="result-snippet"[^>]*>([^<]+)<\/a>/gi;
10518
+ const linkRegex = /<a\b([^>]*\bclass=(["'])[^"']*\bresult-link\b[^"']*\2[^>]*)>([\s\S]*?)<\/a>/gi;
10519
+ const snippetRegex = /<([a-z0-9]+)\b([^>]*\bclass=(["'])[^"']*\bresult-snippet\b[^"']*\3[^>]*)>([\s\S]*?)<\/\1>/gi;
9966
10520
  const linkMatches = takeFrom(
9967
- [...html.matchAll(snippetRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags(expectDefined(m[2])) })),
10521
+ [...html.matchAll(linkRegex)].map((m) => {
10522
+ const attrs = expectDefined(m[1]);
10523
+ const href = getHtmlAttr(attrs, "href");
10524
+ const title = stripTags(expectDefined(m[3]));
10525
+ return href && title ? { url: normalizeDuckDuckGoUrl(href), title } : void 0;
10526
+ }).filter((m) => m !== void 0),
9968
10527
  num
9969
10528
  );
9970
10529
  const snippetMatches = takeFrom(
9971
- [...html.matchAll(snippet2Regex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
10530
+ [...html.matchAll(snippetRegex)].filter((m) => m[4]).map((m) => stripTags(expectDefined(m[4]))),
9972
10531
  num
9973
10532
  );
9974
10533
  for (let i = 0; i < linkMatches.length && i < num; i++) {
@@ -9984,6 +10543,24 @@ function parseDuckDuckGo(html, num) {
9984
10543
  }
9985
10544
  return results;
9986
10545
  }
10546
+ function getHtmlAttr(attrs, name) {
10547
+ const quoted = new RegExp(`\\b${name}\\s*=\\s*(["'])(.*?)\\1`, "i").exec(attrs);
10548
+ if (quoted?.[2]) return decodeHtmlEntities(quoted[2]);
10549
+ const unquoted = new RegExp(`\\b${name}\\s*=\\s*([^\\s>]+)`, "i").exec(attrs);
10550
+ return unquoted?.[1] ? decodeHtmlEntities(unquoted[1]) : void 0;
10551
+ }
10552
+ function normalizeDuckDuckGoUrl(raw) {
10553
+ if (raw.startsWith("//")) return `https:${raw}`;
10554
+ if (!raw.startsWith("/")) return raw;
10555
+ if (!raw.startsWith("/l/")) return raw;
10556
+ try {
10557
+ const url = new URL(raw, "https://duckduckgo.com");
10558
+ const uddg = url.searchParams.get("uddg");
10559
+ return uddg?.startsWith("http") ? uddg : url.toString();
10560
+ } catch {
10561
+ return raw;
10562
+ }
10563
+ }
9987
10564
  async function googleSearch(query, num, signal) {
9988
10565
  const encoded = encodeURIComponent(query);
9989
10566
  const url = `https://www.google.com/search?q=${encoded}&hl=en`;
@@ -10025,29 +10602,53 @@ async function bingSearch(query, num, signal) {
10025
10602
  }
10026
10603
  function parseBingResults(html, num) {
10027
10604
  const results = [];
10028
- const titleRegex = /<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>\s*<\/h2>/gi;
10029
- const snippetRegex = /<p[^>]*class="[^"]*b_paractl[^"]*"[^>]*>([^<]+)<\/p>/gi;
10030
- const entries = takeFrom(
10031
- [...html.matchAll(titleRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags(expectDefined(m[2])) })),
10032
- num
10033
- );
10034
- const snippets = takeFrom(
10035
- [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
10036
- num
10037
- );
10605
+ const blocks = [...html.matchAll(/<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\1[^>]*>([\s\S]*?)(?=<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\3|<\/ol>)/gi)].map((m) => expectDefined(m[2]));
10606
+ const candidates = blocks.length > 0 ? blocks : [html];
10607
+ const entries = takeFrom(candidates.flatMap((block) => {
10608
+ const titleMatch = /<h2[^>]*>\s*<a\b([^>]*)>([\s\S]*?)<\/a>\s*<\/h2>/i.exec(block);
10609
+ if (!titleMatch) return [];
10610
+ const href = getHtmlAttr(expectDefined(titleMatch[1]), "href");
10611
+ const title = stripTags(expectDefined(titleMatch[2]));
10612
+ if (!href || !title) return [];
10613
+ const snippetMatch = /<p\b[^>]*class=(["'])[^"']*\b(?:b_paractl|b_lineclamp\d*)\b[^"']*\1[^>]*>([\s\S]*?)<\/p>/i.exec(block) ?? /<p\b[^>]*>([\s\S]*?)<\/p>/i.exec(block);
10614
+ const snippet = snippetMatch ? stripTags(expectDefined(snippetMatch.at(-1))) : "";
10615
+ return [{ url: normalizeBingUrl(href), title, snippet, score: 1 }];
10616
+ }), num);
10038
10617
  for (let i = 0; i < entries.length; i++) {
10039
10618
  const entry = entries[i];
10040
10619
  if (entry) {
10041
10620
  results.push({
10042
10621
  title: entry.title ?? "",
10043
10622
  url: entry.url ?? "",
10044
- snippet: snippets[i] ?? "",
10623
+ snippet: entry.snippet ?? "",
10045
10624
  score: 1
10046
10625
  });
10047
10626
  }
10048
10627
  }
10049
10628
  return results;
10050
10629
  }
10630
+ function normalizeBingUrl(raw) {
10631
+ try {
10632
+ const url = new URL(raw);
10633
+ if (url.hostname.endsWith("bing.com") && url.pathname.startsWith("/ck/")) {
10634
+ const encoded = url.searchParams.get("u");
10635
+ const decoded = decodeBingTarget(encoded);
10636
+ if (decoded?.startsWith("http")) return decoded;
10637
+ }
10638
+ } catch {
10639
+ }
10640
+ return raw;
10641
+ }
10642
+ function decodeBingTarget(encoded) {
10643
+ if (!encoded) return void 0;
10644
+ const payload = encoded.startsWith("a1") ? encoded.slice(2) : encoded;
10645
+ try {
10646
+ const padded = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "=");
10647
+ return Buffer.from(padded, "base64").toString("utf8");
10648
+ } catch {
10649
+ return void 0;
10650
+ }
10651
+ }
10051
10652
  async function fetchWithTimeout(url, signal, timeoutMs) {
10052
10653
  const controller = new AbortController();
10053
10654
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -10075,7 +10676,10 @@ function anySignal(...signals) {
10075
10676
  return AbortSignal.any(signals);
10076
10677
  }
10077
10678
  function stripTags(html) {
10078
- return html.replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
10679
+ return decodeHtmlEntities(html.replace(/<[^>]+>/g, "")).trim();
10680
+ }
10681
+ function decodeHtmlEntities(text) {
10682
+ return text.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
10079
10683
  }
10080
10684
  var setWorkingDirTool = {
10081
10685
  name: "set_working_dir",