mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-14 13:40:23 +02:00
feat(3.6.9): OpenCode Zen + Nous Research (Hermes) providers
Add two new model providers, both usable via API key or --subscription (local CLI login, no key): - opencode: OpenCode Zen gateway (OPENCODE_API_KEY, opencode.ai/zen/v1). Subscription mode drives the `opencode` CLI (`opencode run --auto`). Supports the Playwright MCP (--mcp): our .mcp.json is converted to OpenCode's own config schema and injected via OPENCODE_CONFIG. - nous: Nous Research / Hermes models (NOUS_API_KEY, inference-api.nousresearch.com/v1). Subscription mode drives the `hermes` CLI (NousResearch/hermes-agent) on the user's Nous Portal OAuth login (`hermes setup --portal`), via `hermes chat -q`. No CLI-level MCP hook — falls back to Hermes's own built-in toolsets (web/terminal/computer-use). Both wired into cli_binary_for, installed_cli_backends, cli_login_status (prompt passed as argv, not stdin — neither CLI reads stdin for this). Bump version 3.6.8 -> 3.6.9 across Cargo.toml, README, TUTORIAL, setup.sh, install.ps1, and in-binary version strings. README/.env.example updated with the new provider rows and subscription-login table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHFAVCHMvRkTy9Wgw7SayG
This commit is contained in:
co-authored by
Claude Opus 5
parent
1f8ccb6f9e
commit
69c5e3ddb9
@@ -51,6 +51,16 @@ TOGETHER_API_KEY=
|
||||
# openrouter: https://openrouter.ai/keys
|
||||
OPENROUTER_API_KEY=
|
||||
|
||||
# opencode: https://opencode.ai/auth (OpenCode Zen gateway)
|
||||
# Or skip the key entirely and use --subscription with the
|
||||
# `opencode` CLI logged into your own Zen/plan account.
|
||||
OPENCODE_API_KEY=
|
||||
|
||||
# nous: Nous Portal (https://portal.nousresearch.com) — Hermes models.
|
||||
# Or skip the key entirely and use --subscription with the
|
||||
# `hermes` CLI (`hermes setup --portal` for OAuth login).
|
||||
NOUS_API_KEY=
|
||||
|
||||
# ollama: local, no key needed. Override the endpoint if not default:
|
||||
#OLLAMA_BASE_URL=http://localhost:11434/v1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h1 align="center">🧠 NeuroSploit v3.6.8</h1>
|
||||
<h1 align="center">🧠 NeuroSploit v3.6.9</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/22624?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-22624" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/22624/daily?language=Python" alt="JoasASantos%2FNeuroSploit | Trendshift" width="250" height="55"/></a>
|
||||
@@ -12,7 +12,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Version-3.6.8-blue?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/Version-3.6.9-blue?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/Harness-Rust%20%7C%20tokio-e6b673?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/MD%20Agents-435-red?style=flat-square">
|
||||
@@ -433,6 +433,8 @@ export GROQ_API_KEY=... # groq:*
|
||||
export TOGETHER_API_KEY=... # together:*
|
||||
export MOONSHOT_API_KEY=... # moonshot:* (Kimi K3/K2)
|
||||
export OPENROUTER_API_KEY=... # openrouter:*
|
||||
export OPENCODE_API_KEY=... # opencode:* (OpenCode Zen gateway)
|
||||
export NOUS_API_KEY=... # nous:* (Nous Portal — Hermes)
|
||||
# ollama / llamacpp need no key (local)
|
||||
|
||||
# then run via API (note: NO --subscription)
|
||||
@@ -462,6 +464,8 @@ Or put the keys in a `.env` and source it (`cp .env.example .env`; edit; `set -a
|
||||
| `together:` | `TOGETHER_API_KEY` | api.together.xyz |
|
||||
| `moonshot:` | `MOONSHOT_API_KEY` | api.moonshot.ai |
|
||||
| `openrouter:` | `OPENROUTER_API_KEY` | openrouter.ai |
|
||||
| `opencode:` | `OPENCODE_API_KEY` | opencode.ai/zen (OpenCode Zen gateway) |
|
||||
| `nous:` | `NOUS_API_KEY` | inference-api.nousresearch.com (Hermes 4) |
|
||||
| `ollama:` | _(none)_ | localhost:11434 |
|
||||
| `llamacpp:` | _(none)_ | localhost:8080 |
|
||||
|
||||
@@ -484,6 +488,12 @@ install and log into one of the CLIs first:
|
||||
| `openai:` | `codex` | `codex` login |
|
||||
| `gemini:` | `gemini` | `gemini` login |
|
||||
| `xai:` | `grok` | `grok` login |
|
||||
| `opencode:` | `opencode` | `opencode auth login` (or `/connect` in the TUI) — Zen/plan account |
|
||||
| `nous:` | `hermes` | `hermes setup --portal` — Nous Portal OAuth |
|
||||
|
||||
`opencode:` also gets the Playwright MCP (`--mcp`) like anthropic/openai do.
|
||||
`nous:` relies on Hermes's own built-in toolsets (web/terminal/computer-use)
|
||||
instead — it has no CLI-level MCP hook.
|
||||
|
||||
```bash
|
||||
./target/release/neurosploit run http://testphp.vulnweb.com/ \
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# NeuroSploit — Tutorial & User Guide (v3.6.8)
|
||||
# NeuroSploit — Tutorial & User Guide (v3.6.9)
|
||||
|
||||
A complete, hands-on guide to installing, configuring and running NeuroSploit —
|
||||
the autonomous, multi-model penetration-testing harness.
|
||||
@@ -98,7 +98,7 @@ Agents **degrade gracefully**: if `rustscan` is absent they use `nmap`; if neith
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
neurosploit --version # neurosploit 3.6.8
|
||||
neurosploit --version # neurosploit 3.6.9
|
||||
neurosploit agents # {"vulns":241,...,"ai":30,...,"total":430}
|
||||
neurosploit models # all providers & models
|
||||
```
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ function Ok ($m) { Write-Host " + $m" -ForegroundColor Green }
|
||||
function Warn($m){ Write-Host " ! $m" -ForegroundColor Yellow }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " NeuroSploit installer (Windows) — v3.6.8" -ForegroundColor Cyan
|
||||
Write-Host " NeuroSploit installer (Windows) — v3.6.9" -ForegroundColor Cyan
|
||||
|
||||
# arch → asset arch (only x64 prebuilt today; arm64 falls back to source)
|
||||
$rawArch = $env:PROCESSOR_ARCHITECTURE
|
||||
@@ -29,7 +29,7 @@ $ref = $env:NEUROSPLOIT_REF
|
||||
if (-not $ref) {
|
||||
try { $ref = (Invoke-RestMethod "https://api.github.com/repos/$slug/releases/latest").tag_name } catch { }
|
||||
}
|
||||
if (-not $ref) { $ref = "v3.6.8" }
|
||||
if (-not $ref) { $ref = "v3.6.9" }
|
||||
Say "Release: $ref"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
||||
|
||||
Generated
+2
-2
@@ -871,7 +871,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "neurosploit"
|
||||
version = "3.6.8"
|
||||
version = "3.6.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -888,7 +888,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "neurosploit-harness"
|
||||
version = "3.6.8"
|
||||
version = "3.6.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
|
||||
@@ -3,7 +3,7 @@ members = ["crates/harness", "app"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.6.8"
|
||||
version = "3.6.9"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/JoasASantos/NeuroSploit"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.8 — interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
|
||||
//! NeuroSploit v3.6.9 — interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
|
||||
|
||||
mod repl;
|
||||
mod tui;
|
||||
@@ -11,9 +11,9 @@ use std::path::{Path, PathBuf};
|
||||
#[command(
|
||||
name = "neurosploit",
|
||||
version,
|
||||
about = "NeuroSploit v3.6.8 — multi-model autonomous pentest harness",
|
||||
long_about = "NeuroSploit v3.6.8 — a Rust multi-model harness that drives a pool of LLMs \
|
||||
(API key or local subscription: Claude/Codex/Gemini/Grok) to autonomously test a target. \
|
||||
about = "NeuroSploit v3.6.9 — multi-model autonomous pentest harness",
|
||||
long_about = "NeuroSploit v3.6.9 — a Rust multi-model harness that drives a pool of LLMs \
|
||||
(API key or local subscription: Claude/Codex/Gemini/Grok/OpenCode/Hermes) to autonomously test a target. \
|
||||
After recon it INTELLIGENTLY selects only the agents matching the discovered surface, runs \
|
||||
them in parallel, then validates every finding by cross-model voting before reporting.\n\n\
|
||||
Run with NO arguments for an interactive wizard.\n\n\
|
||||
@@ -54,7 +54,7 @@ enum Cmd {
|
||||
recon: usize,
|
||||
#[arg(long)]
|
||||
offline: bool,
|
||||
/// Use local agentic CLI subscription (Claude/Codex/Gemini/Grok login).
|
||||
/// Use local agentic CLI subscription (Claude/Codex/Gemini/Grok/OpenCode/Hermes login).
|
||||
#[arg(long)]
|
||||
subscription: bool,
|
||||
/// Enable Playwright MCP (auto-installed if missing; backends that don't
|
||||
@@ -765,7 +765,7 @@ pub(crate) fn spawn_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, mode:
|
||||
println!(" │ ua : {ua}");
|
||||
write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target));
|
||||
|
||||
println!(" ┌─ NeuroSploit v3.6.8 · by Joas A Santos & Red Team Leaders");
|
||||
println!(" ┌─ NeuroSploit v3.6.9 · by Joas A Santos & Red Team Leaders");
|
||||
println!(" │ run id : {run_id}");
|
||||
println!(" │ target : {}", cfg.target);
|
||||
println!(" │ models : {}", cfg.models.join(", "));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.8 — interactive session (Claude-Code / Codex / Cursor-CLI style).
|
||||
//! NeuroSploit v3.6.9 — interactive session (Claude-Code / Codex / Cursor-CLI style).
|
||||
//!
|
||||
//! Launched when `neurosploit` runs with no subcommand. A persistent REPL with
|
||||
//! real line editing (arrow-key history recall, Ctrl-A/E/K, paste), model
|
||||
@@ -371,7 +371,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
|
||||
let backends = harness::installed_cli_backends();
|
||||
println!("\x1b[1m");
|
||||
println!(" ███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗");
|
||||
println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.8");
|
||||
println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.9");
|
||||
println!(" ██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ interactive harness");
|
||||
println!(" ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║ by Joas A Santos");
|
||||
println!(" ██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝ & Red Team Leaders");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.8 — TUI "Mission Control" mode.
|
||||
//! NeuroSploit v3.6.9 — TUI "Mission Control" mode.
|
||||
//!
|
||||
//! Concurrent panels that update live while the engagement runs in the
|
||||
//! background, with a composer input that stays active during execution:
|
||||
|
||||
@@ -52,6 +52,21 @@ pub fn providers() -> Vec<Provider> {
|
||||
models: vec!["gpt-4o", "claude-3-7-sonnet", "gemini/gemini-2.5-pro"] },
|
||||
Provider { key: "openrouter", label: "OpenRouter", base_url: "https://openrouter.ai/api/v1", env_key: "OPENROUTER_API_KEY", kind: "api",
|
||||
models: vec!["anthropic/claude-opus-4-8", "qwen/qwen-2.5-coder-32b-instruct", "deepseek/deepseek-r1", "meta-llama/llama-3.3-70b-instruct"] },
|
||||
// OpenCode Zen — the curated OpenAI-compatible gateway behind the
|
||||
// `opencode` CLI (https://opencode.ai/zen). Works two ways, like
|
||||
// anthropic/openai/xai/gemini above: as a plain API-key provider here,
|
||||
// or (with --subscription) driven through the locally-installed
|
||||
// `opencode` agentic CLI on the user's own Zen/plan login — no key
|
||||
// needed in that mode. `kind: "cli"` reflects the latter.
|
||||
Provider { key: "opencode", label: "OpenCode Zen", base_url: "https://opencode.ai/zen/v1", env_key: "OPENCODE_API_KEY", kind: "cli",
|
||||
models: vec!["claude-opus-5", "claude-sonnet-5", "gpt-5.6-sol", "gpt-5.5", "gemini-3-pro", "grok-4.5", "deepseek-v4-pro", "qwen3.7-max", "kimi-k3"] },
|
||||
// Nous Research — Hermes models via the Nous Portal. As an API-key
|
||||
// provider here (OpenAI-compatible `inference-api.nousresearch.com`),
|
||||
// or (with --subscription) driven through the `hermes` CLI
|
||||
// (NousResearch/hermes-agent) on the user's OAuth Portal login
|
||||
// (`hermes setup --portal`) — 300+ routed frontier models, no key.
|
||||
Provider { key: "nous", label: "Nous Research (Hermes)", base_url: "https://inference-api.nousresearch.com/v1", env_key: "NOUS_API_KEY", kind: "cli",
|
||||
models: vec!["Hermes-4-405B", "Hermes-4-70B", "DeepHermes-3-Mistral-24B-Preview"] },
|
||||
// Azure OpenAI (OpenAI-compatible). Set AZURE_OPENAI_ENDPOINT (e.g.
|
||||
// https://<resource>.openai.azure.com), optionally AZURE_OPENAI_API_VERSION
|
||||
// (default 2024-10-21), and use `azure:<your-deployment-name>` as the model.
|
||||
@@ -226,6 +241,11 @@ impl ChatClient {
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(bin);
|
||||
// Most agentic CLIs here take the prompt on stdin; opencode and hermes
|
||||
// take it as a trailing positional argument instead — track which so we
|
||||
// don't also pipe it into stdin below (that would just hang the child
|
||||
// waiting on a request it already got as an argv value).
|
||||
let mut prompt_via_stdin = true;
|
||||
match bin {
|
||||
// Codex non-interactive exec (uses the ChatGPT/Codex login), prompt on stdin.
|
||||
"codex" => {
|
||||
@@ -250,13 +270,46 @@ impl ChatClient {
|
||||
"grok" => {
|
||||
cmd.arg("--model").arg(model);
|
||||
}
|
||||
// OpenCode CLI (`opencode run`) — non-interactive one-shot, prompt
|
||||
// as a positional arg, not stdin. `--auto` auto-approves anything
|
||||
// not explicitly denied (our equivalent of --dangerously-skip-permissions).
|
||||
// MCP (Playwright) is injected via a generated opencode.json pointed
|
||||
// at through OPENCODE_CONFIG rather than a CLI flag (opencode has none).
|
||||
"opencode" => {
|
||||
prompt_via_stdin = false;
|
||||
cmd.arg("run").arg("--model").arg(model).arg("--auto");
|
||||
if let Some(mcp) = mcp_config {
|
||||
match write_opencode_mcp_config(mcp) {
|
||||
Ok(cfg) => { cmd.env("OPENCODE_CONFIG", cfg); }
|
||||
Err(e) => eprintln!(" [!] opencode MCP config failed: {e}"),
|
||||
}
|
||||
}
|
||||
cmd.arg(&prompt);
|
||||
}
|
||||
// Hermes Agent CLI (NousResearch/hermes-agent) — single-query mode.
|
||||
// `-q` is the prompt-supplying flag (not a stdin read); `--provider
|
||||
// nous` pins the Nous Portal OAuth login; `-Q` quiets banner/spinner
|
||||
// for programmatic use; `--yolo` bypasses dangerous-command prompts.
|
||||
// No CLI-level MCP hook — Hermes falls back to its own built-in
|
||||
// toolsets (web/terminal/computer-use) rather than our Playwright MCP.
|
||||
"hermes" => {
|
||||
prompt_via_stdin = false;
|
||||
cmd.arg("chat").arg("-m").arg(model).arg("--provider").arg("nous")
|
||||
.arg("-Q").arg("--yolo").arg("-q").arg(&prompt);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).kill_on_drop(true);
|
||||
let mut child = cmd.spawn().map_err(|e| anyhow!("spawn {} failed: {}", bin, e))?;
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin.write_all(prompt.as_bytes()).await?;
|
||||
// Drop closes stdin so the CLI processes the prompt and exits.
|
||||
if prompt_via_stdin {
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin.write_all(prompt.as_bytes()).await?;
|
||||
// Drop closes stdin so the CLI processes the prompt and exits.
|
||||
}
|
||||
} else {
|
||||
// Prompt went in as an argv value; close stdin immediately so
|
||||
// nothing lingers waiting on it (opencode/hermes never read it).
|
||||
drop(child.stdin.take());
|
||||
}
|
||||
// Cap a single agentic CLI turn so a stuck tool-loop can't hang the run.
|
||||
let out = match tokio::time::timeout(Duration::from_secs(600), child.wait_with_output()).await {
|
||||
@@ -577,6 +630,8 @@ pub fn cli_binary_for(provider: &str) -> Option<&'static str> {
|
||||
"openai" => Some("codex"),
|
||||
"xai" => Some("grok"),
|
||||
"gemini" => Some("gemini"),
|
||||
"opencode" => Some("opencode"),
|
||||
"nous" => Some("hermes"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -590,7 +645,7 @@ pub fn binary_in_path(name: &str) -> bool {
|
||||
|
||||
/// Which subscription CLI backends are installed locally.
|
||||
pub fn installed_cli_backends() -> Vec<&'static str> {
|
||||
["claude", "codex", "grok", "gemini"].into_iter().filter(|b| binary_in_path(b)).collect()
|
||||
["claude", "codex", "grok", "gemini", "opencode", "hermes"].into_iter().filter(|b| binary_in_path(b)).collect()
|
||||
}
|
||||
|
||||
/// Login state of a subscription CLI backend.
|
||||
@@ -610,15 +665,23 @@ pub async fn cli_login_status(provider: &str) -> LoginStatus {
|
||||
let Some(bin) = cli_binary_for(provider) else { return LoginStatus::NotInstalled };
|
||||
if !binary_in_path(bin) { return LoginStatus::NotInstalled; }
|
||||
let mut cmd = Command::new(bin);
|
||||
// opencode/hermes take the probe prompt as an argv value, not stdin.
|
||||
let prompt_via_stdin = !matches!(bin, "opencode" | "hermes");
|
||||
match bin {
|
||||
"claude" => { cmd.arg("-p").arg("--output-format").arg("text").arg("--dangerously-skip-permissions"); }
|
||||
"codex" => { cmd.arg("exec").arg("--dangerously-bypass-approvals-and-sandbox").arg("-"); }
|
||||
"opencode" => { cmd.arg("run").arg("--auto").arg("Reply with exactly: OK"); }
|
||||
"hermes" => { cmd.arg("chat").arg("--provider").arg("nous").arg("-Q").arg("--yolo").arg("-q").arg("Reply with exactly: OK"); }
|
||||
_ => { cmd.arg("-p"); } // grok / gemini: prompt on stdin
|
||||
}
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).kill_on_drop(true);
|
||||
let mut child = match cmd.spawn() { Ok(c) => c, Err(_) => return LoginStatus::Unknown };
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
let _ = stdin.write_all(b"Reply with exactly: OK").await;
|
||||
if prompt_via_stdin {
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
let _ = stdin.write_all(b"Reply with exactly: OK").await;
|
||||
}
|
||||
} else {
|
||||
drop(child.stdin.take());
|
||||
}
|
||||
let out = match tokio::time::timeout(Duration::from_secs(45), child.wait_with_output()).await {
|
||||
Ok(Ok(o)) => o,
|
||||
@@ -642,10 +705,50 @@ pub async fn cli_login_status(provider: &str) -> LoginStatus {
|
||||
}
|
||||
|
||||
/// Does this provider's agentic CLI accept a Playwright MCP config?
|
||||
/// Claude Code and Codex do; Gemini/Grok CLIs don't take an MCP-config flag, so
|
||||
/// they fall back to their own built-in tools.
|
||||
/// Claude Code, Codex, and OpenCode do (OpenCode via a generated
|
||||
/// `opencode.json` + `OPENCODE_CONFIG`, see `write_opencode_mcp_config`).
|
||||
/// Gemini/Grok/Hermes have no CLI-level MCP hook, so they fall back to their
|
||||
/// own built-in tools (Hermes ships web/terminal/computer-use natively).
|
||||
pub fn mcp_supported(provider: &str) -> bool {
|
||||
matches!(provider, "anthropic" | "openai")
|
||||
matches!(provider, "anthropic" | "openai" | "opencode")
|
||||
}
|
||||
|
||||
/// Convert our `.mcp.json` (`{"mcpServers": {name: {command, args}}}`) into
|
||||
/// OpenCode's own config schema (`{"mcp": {name: {"type":"local","command":
|
||||
/// [command, ...args], "enabled": true}}}`) and write it next to the source
|
||||
/// file. OpenCode has no `--mcp-config` flag; it's pointed at a config file
|
||||
/// via the `OPENCODE_CONFIG` env var instead (set by the `opencode` arm of
|
||||
/// `chat_cli`), so this doesn't touch the user's own `opencode.json`.
|
||||
fn write_opencode_mcp_config(mcp_json_path: &str) -> Result<std::path::PathBuf> {
|
||||
let txt = std::fs::read_to_string(mcp_json_path)
|
||||
.map_err(|e| anyhow!("read {mcp_json_path}: {e}"))?;
|
||||
let v: serde_json::Value = serde_json::from_str(&txt)
|
||||
.map_err(|e| anyhow!("parse {mcp_json_path}: {e}"))?;
|
||||
let servers = v.get("mcpServers").cloned().unwrap_or(v);
|
||||
let mut mcp = serde_json::Map::new();
|
||||
if let Some(obj) = servers.as_object() {
|
||||
for (name, s) in obj {
|
||||
let command = s.get("command").and_then(|c| c.as_str()).unwrap_or("").to_string();
|
||||
if command.is_empty() { continue; }
|
||||
let mut argv = vec![serde_json::Value::String(command)];
|
||||
if let Some(args) = s.get("args").and_then(|a| a.as_array()) {
|
||||
argv.extend(args.iter().cloned());
|
||||
}
|
||||
mcp.insert(name.clone(), serde_json::json!({
|
||||
"type": "local",
|
||||
"command": argv,
|
||||
"enabled": true
|
||||
}));
|
||||
}
|
||||
}
|
||||
let cfg = serde_json::json!({
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": mcp
|
||||
});
|
||||
let path = std::path::Path::new(mcp_json_path).with_file_name("opencode.json");
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&cfg).unwrap_or_default())
|
||||
.map_err(|e| anyhow!("write {}: {e}", path.display()))?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Best-effort ensure the Playwright MCP server is available locally. Requires
|
||||
|
||||
@@ -28,7 +28,7 @@ cat <<'BANNER'
|
||||
|
||||
███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗
|
||||
████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit installer
|
||||
██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ v3.6.8 — Rust harness
|
||||
██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ v3.6.9 — Rust harness
|
||||
██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║ by Joas A Santos
|
||||
██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝ & Red Team Leaders
|
||||
╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝
|
||||
@@ -63,7 +63,7 @@ if [ -z "$REF" ]; then
|
||||
REF="$(dl "https://api.github.com/repos/${REPO_SLUG}/releases/latest" /dev/stdout 2>/dev/null \
|
||||
| grep -m1 '"tag_name"' | sed -E 's/.*"tag_name" *: *"([^"]+)".*/\1/' || true)"
|
||||
fi
|
||||
[ -z "$REF" ] && REF="v3.6.8"
|
||||
[ -z "$REF" ] && REF="v3.6.9"
|
||||
say "Release: $REF"
|
||||
|
||||
installed=0
|
||||
|
||||
Reference in New Issue
Block a user