Overview · Install

Install Nio.

One curl-piped-shell command installs Nio on every agent CLI you have. Auto-detect your installed agents, or pick one explicitly. macOS, Linux, or WSL.

Pick a platform

Auto-detect (recommended)

  • Node.js ≥ 18, curl, and unzip or python3
  • At least one supported agent CLI installed (Claude Code, Codex, OpenClaw, Hermes, Pi, or opencode)

The default mode. The installer scans $HOME for the six agent CLI directories — .claude, .codex, .openclaw, .hermes, .pi/agent, .config/opencode — and runs the matching per-platform setup for every one it finds. A directory only counts when the matching CLI (claude, codex, openclaw, hermes, pi, opencode) is also on PATH; anything skipped for that reason is named in the output.

curl -fsSL https://core0-io.github.io/nio/install.sh | bash

If no agent CLI is detected, the installer exits with a clear error pointing at this page so you can pick a tab and pass --platform NAME explicitly.

Onboarding with a shared config

If an operator handed you a pre-tuned nio.yaml (e.g. an org-wide external_analyser endpoint + collector setup), pass it at install time and skip the manual copy-into-~/.nio step:

curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --config /path/to/nio.yaml

The flag works on every form of the installer — auto-detect, per-platform (--platform claude-code --config ...), and the post-clone ./setup.sh --config .... NIO_CONFIG=/path/to/nio.yaml is honoured as a fallback when the flag isn't given.

Before touching disk, Nio runs the full /nio doctor probe suite against the incoming file (schema validation + external_analyser reachability + LLM key sanity). If any probe fails, the install aborts and your live config is not modified. On a clean pass, the previous ~/.nio/config.yaml is preserved as config.yaml.bak.<ISO-stamp> — keep multiple stamps to roll back manually.

File must be local

The --config argument is a path on your machine — not a URL. Download the file first. The path is resolved to absolute at the top of install.sh so the extracted setup.sh (running from /tmp/nio-install-XXXXXX/) can still find it.

Mutually exclusive with --reset-to-defaults

You can't use --config <path> and --reset-to-defaults in the same invocation — operationally that would mean "wipe the config then immediately overwrite it from the file," which is just --config. setup.sh refuses the combination and exits non-zero.

Claude Code

  • Claude Code CLI installed and claude on PATH
  • Node.js ≥ 18, curl, and unzip or python3
curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform claude-code

What setup.sh does

Registers the nio marketplace with Claude Code (claude plugin marketplace add), installs the plugin (claude plugin install nio@nio), syncs scripts and PreToolUse / SessionStart hooks into the Claude Code plugin cache, and writes ~/.nio/config.yaml from defaults.

Verify

  1. Open a fresh Claude Code session — hooks load on the next session, not the one in flight.
  2. Type /nio config show in chat. The current protection level prints.
  3. Try /nio scan ., then /nio report.

Each capability is also a focused skill on Claude Code — /nio-scan, /nio-config, /nio-external-score, … — or just ask in natural language. See Focused skills.

If /nio isn't available

Re-run the one-liner — Nio's setup.sh re-registers the marketplace idempotently. The path bound to the marketplace updates if the unzip directory moved.

Codex CLI

  • Codex CLI 0.128.0 — the only end-to-end-verified version. On macOS: brew install --cask codex.
  • Node.js ≥ 18, curl, and unzip or python3
curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform codex

What setup.sh does

Codex 0.128 has no codex plugin install command, so setup.sh performs a full nuke + cp install: wipes and rewrites ~/.codex/plugins/cache/nio/nio/<version>/ from the unzipped source, generates a Codex-valid marketplace catalog under ~/.nio/codex-marketplace/, and edits ~/.codex/config.toml to register the marketplace, enable the plugin, and set codex_hooks = true + plugin_hooks = true under [features].

Verify

  1. Open a fresh Codex session.
  2. codex "$nio config show" — current protection level prints.
  3. codex "$nio scan .", then codex "$nio report".
Caveat

plugin_hooks is still under-development in Codex 0.128, but it's the only feature flag that lets plugin-bundled hooks.json fire. Codex has no custom slash commands — use $nio as the explicit skill trigger. Each capability is also a focused skill: trigger $nio-scan, $nio-config, $nio-external-score, … or describe the task in natural language. See Focused skills.

OpenClaw

  • OpenClaw (ClawHub) CLI installed and openclaw on PATH
  • Node.js ≥ 18, curl, and unzip or python3
curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform openclaw

What setup.sh does

Registers the bundled plugin (plugin/plugin.js) with OpenClaw, mirrors the /nio skill directory so the slash command appears in the skill list, and writes ~/.nio/config.yaml from defaults.

Verify

  1. Open OpenClaw — /nio appears in the skill list.
  2. /nio config show/nio scan ./nio report.
Caveat — no interactive confirm

OpenClaw doesn't expose an interactive "do you want to run this?" prompt. When the guard decides confirm, the confirm_action fallback fires (default: allow + warning in the audit log). For headless / CI environments, set it to deny in ~/.nio/config.yaml.

Hermes

  • Hermes Agent installed; shell-hook subsystem from upstream PR #13296 is required
  • Node.js ≥ 18, curl, and unzip or python3
curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform hermes

What setup.sh does

Merges seven shell-hook entries (pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, on_session_start, on_session_end, subagent_stop) into ~/.hermes/config.yaml, drops the /nio Python plugin into ~/.hermes/plugins/nio/, appends "nio" to plugins.enabled, and writes ~/.nio/config.yaml.

Verify

  1. hermes hooks doctor — expect "Checking 7 configured shell hook(s)…", all green.
  2. Restart any running gateway: hermes gateway run --replace.
  3. Send /nio config show in any Hermes channel — bot replies, no LLM in the loop.
Caveat — consent allowlist

Hermes refuses to fire unknown shell hooks until you consent. The interactive prompt at the end of setup.sh handles this — it reads from /dev/tty, so the prompt still fires under curl … | bash. For non-TTY runs (CI, Docker -d, systemd, nohup with </dev/null), pass --accept-hooks through the one-liner, or set HERMES_ACCEPT_HOOKS=1 before running. The allowlist is keyed on the exact command string, so any path change (move, upgrade, new machine) requires re-approval.

Pi

  • Pi coding agent installed; pi on PATH for the preferred install route
  • Node.js ≥ 18, curl, and unzip or python3
curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform pi

What setup.sh does

The release zip is a valid pi package, so setup.sh prefers pi install <dir> and lets Pi manage it with pi update / pi remove. With no pi on PATH it falls back to copying the bundle into ~/.pi/agent/extensions/nio/ and appending that path to the extensions array in ~/.pi/agent/settings.json, plus the skills directory to skills. Moving from the fallback to the CLI route later strips only the entries Nio itself wrote. It exports PI_CODING_AGENT_DIR before shelling out, so --pi-home is honoured by the pi CLI too, and writes ~/.nio/config.yaml from defaults.

Verify

  1. Start a fresh pi session — extensions load at startup.
  2. /nio config show — the current protection level prints, with no LLM in the loop (pi.registerCommand hooks straight into slash dispatch).
  3. /nio scan ., then /nio report.

Each capability is also a focused skillnio-scan, nio-config, nio-external-score, … — or just ask in natural language. See Focused skills.

Interactive confirm works here

Pi is the only platform where a confirm verdict opens a real dialog (ctx.ui.confirm) instead of folding to guard.confirm_action. Answer the prompt and the span records the resolved decision (confirm_allowed / confirm_denied).

Caveat — MCP and subagent spans

Pi core ships no MCP; the third-party pi-mcp-adapter package adds it and Nio gates those calls — run /nio doctor for the naming shapes and the one caveat (toolPrefix: "none" emits bare tool names Nio cannot identify as MCP). Pi also has no subagent concept, so it emits no task:execute spans; an empty task-span set is correct, not a dropped signal.

opencode

  • opencode installed and opencode on PATH
  • Node.js ≥ 18, curl, and unzip or python3
curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform opencode

What setup.sh does

Copies the bundled plugin to ~/.config/opencode/plugins/nio.js (opencode globs that directory at startup), drops the /nio command definition at ~/.config/opencode/commands/nio.md, mirrors the skills, and writes ~/.nio/config.yaml from defaults. The config dir follows $XDG_CONFIG_HOME/opencode when set; override it with --opencode-home.

Verify

  1. Open a fresh opencode session so the plugin loads.
  2. /nio config show — the current protection level prints.
  3. /nio scan ., then /nio report.

Each capability is also a focused skillnio-scan, nio-config, nio-external-score, … — or just ask in natural language. See Focused skills.

Behaviour change — writes under the opencode config dir are denied

Any agent write under ~/.config/opencode/ is a SENSITIVE_PATH finding and is denied at balanced, the default level — including benign-looking files like AGENTS.md and agent/*.md. The whole tree is covered because plugins/ is an auto-load directory opencode executes at startup, so a write there is arbitrary code execution on the next launch. Same treatment as .pi/ and .openclaw/. If you want the agent editing its own instruction files, move them out of the config dir or drop to permissive.

Caveat — shared plugins/ directory

Because that directory can hold other people's plugins, the installer writes the {"type": "module"} ESM sentinel only when no sibling plugin and no package.json already exist, and records a .nio-esm-sentinel marker so --uninstall removes only a sentinel Nio itself wrote. With siblings present the install warns and leaves the directory's module format untouched — declare ESM yourself if opencode then fails to load nio.js.

Caveat — spans for tools that throw

opencode does not fire tool.execute.after when a tool throws, so that tool's span is reclaimed at session.idle instead of closed precisely. It carries the full nio.guard.* attribution and is tagged nio.span.reclaimed=true, but its end timestamp is the turn flush and it has no gen_ai.tool.call.result. Don't read span status as a tool success rate on opencode — filter on nio.span.reclaimed instead. Full detail in COLLECTOR-SIGNALS.md.

After install

  1. Edit ~/.nio/config.yaml: set guard.protection_level, optional collector.endpoint for OTLP export.
  2. Start a new agent session so the hooks load (if you have a long-running daemon like hermes gateway, restart it).
  3. The audit log is at ~/.nio/audit.jsonl. Read recent entries via /nio report (or $nio report on Codex).
  4. Full config field reference: Configuration.
Nio guarding its own install

If Nio is already running on one agent and you re-invoke the one-liner to add another, the guard may surface a confirm prompt — curl … | bash matches one of the high-severity scan patterns. That's expected; allow it. The protection level controls the behaviour: balanced asks, strict denies, permissive allows.

Upgrade

Re-run the install one-liner — setup.sh is idempotent and picks up the latest release. To pin a specific tag, set NIO_VERSION first:

NIO_VERSION=v2.4.3 bash -c "$(curl -fsSL https://core0-io.github.io/nio/install.sh)"

After a release that changed the config schema, append --reset-to-defaults to overwrite ~/.nio/config.yaml with the new bundled template:

curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --reset-to-defaults
Heads up — --reset-to-defaults wipes your customisations

--reset-to-defaults replaces your existing config.yaml with the upgraded template, so any tuned fields (allowed commands, permitted tools, collector endpoint, scoring weights, …) are gone and have to be reapplied on top of the new defaults. Back the file up first if you're not sure what you customised: cp ~/.nio/config.yaml ~/.nio/config.yaml.bak.

To roll a new operator-tuned config in the same upgrade step, pass --config /path/to/new.yaml instead — the previous file is automatically preserved as config.yaml.bak.<ISO-stamp> and the install aborts if /nio doctor flags any issue:

curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --config /path/to/new.yaml

Uninstall

curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --uninstall

Removes Nio from every detected agent CLI. Add --platform NAME to limit scope.

Custom install paths

If you've moved an agent CLI's home directory (via CLAUDE_CONFIG_DIR, CODEX_HOME, OPENCLAW_STATE_DIR, PI_CODING_AGENT_DIR, XDG_CONFIG_HOME, or manually), thread the override past the installer's bash -s -- boundary — everything after -- forwards to the matching setup.sh:

curl -fsSL https://core0-io.github.io/nio/install.sh | bash -s -- --platform claude-code --cc-home /path/to/.claude

Available flags: --cc-home, --codex-home, --openclaw-home, --hermes-home, --pi-home, --opencode-home, --config <path>, --reset-to-defaults. Resolution order in each platform's setup.sh: flag > env var > default ($HOME/.<plat>). See Onboarding with a shared config above for what --config does.

Manual install or from source

The one-liner is just orchestration: it downloads the per-platform release zip, runs its setup.sh, and cleans up. To install a release manually, grab a zip from the Releases page, extract it (unzip, or python3 -m zipfile -e if unzip isn't installed), then cd into the directory and run ./setup.sh — or ./setup.sh --config /path/to/nio.yaml to apply an operator-provided config in the same step (doctor-gated). To install from a local checkout for development, see the Development section in the README.

./setup.sh auto-detects more loosely than install.sh

The two use deliberately different rules. The curl one-liner (install.sh) needs both the config directory and the CLI on PATH; a dir-only match is skipped with a notice, and you force it with --platform NAME. The top-level ./setup.sh in a checkout or unzipped release needs only the config directory — it installs even when the CLI isn't on PATH (it just prints the same notice), and any explicit --<platform>-home flag always wins. The reasoning: install.sh runs unattended against a machine it knows nothing about, so it errs toward doing nothing; ./setup.sh is run deliberately, and the stricter rule would refuse to install for people whose CLI lives behind an alias, a wrapper, or a not-yet-sourced ~/.profile. Neither script ever creates a config directory speculatively, so an unrelated binary on PATH can't trigger an install on its own.