Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nightward.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ sarif:
tool_name: Nightward
category: nightward
information_uri: https://github.com/JSONbored/nightward
semantic_version: 0.1.4
semantic_version: 0.1.11
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ Nightward is read-only by default, but it can run explicit, confirmation-gated l

[![Scrubbed Nightward OpenTUI walkthrough showing overview, findings, analysis, fix plan, inventory, backup, and help screens](site/public/demo/nightward-opentui.gif)](site/guide/tui.md)

The README uses a GIF so the preview renders directly on GitHub. The docs homepage uses the lighter [WebM loop](site/public/demo/tui/nightward-opentui.webm), and the [TUI guide](site/guide/tui.md) keeps the full seven-screen gallery.
The README uses a GIF so the preview renders directly on GitHub. The docs homepage uses the lighter [WebM loop](site/public/demo/tui/nightward-opentui.webm), and the [TUI guide](site/guide/tui.md) keeps the full section gallery.

## At A Glance

| Surface | What it does | Default write behavior |
| --- | --- | --- |
| TUI | Dashboard, inventory, findings, analysis, fix plan, backup preview, action queue | Read-only until a confirmed action is applied |
| CLI | Scriptable scan, doctor, policy, SARIF, snapshot, schedule, backup, and action commands | Read-only unless explicit output/export paths or `--confirm` actions are requested |
| MCP server | Stdio tools/resources/prompts for AI clients | Read-only; can list/preview actions, but writes must be applied in CLI/TUI/Raycast |
| MCP server | Stdio tools/resources/prompts for AI clients | Can request local action approvals; applies only the locally approved action once |
| Raycast | macOS companion commands plus confirmed Nightward Actions | Clipboard/report-folder actions plus confirmation-gated writes |
| GitHub Action | Workspace policy and SARIF checks | Writes only requested CI outputs |
| Trunk plugin | Local workspace policy/analyze linters | Emits SARIF to stdout |
Expand Down Expand Up @@ -296,7 +296,7 @@ Secret values are never emitted in scan JSON, findings output, fix-plan JSON, Ma

`nw analyze` turns scan findings and classifications into explainable signals. It does not claim a package, server, binary, or URL is safe. It reports what Nightward can prove from local structure, why it matters, and how confident the signal is.

Default analysis is offline and built in. Optional providers are discovered by `providers doctor`; Nightward does not call online services unless a user explicitly selects providers and opts into network-capable behavior. The CLI/TUI/Raycast action layer can install known provider CLIs after confirmation. MCP can list and preview those actions, but cannot apply local writes. Explicit local providers are `gitleaks`, `trufflehog`, `semgrep`, and `syft`. Online-capable providers are `trivy`, `osv-scanner`, `grype`, `scorecard`, and `socket`, and they require explicit online-provider opt-in. Socket support creates a remote Socket scan artifact from dependency manifest metadata; Nightward does not fetch or normalize remote Socket reports in v1.
Default analysis is offline and built-in. Optional providers are discovered by `providers doctor`; Nightward does not call online services unless a user explicitly selects providers and opts into network-capable behavior. The CLI/TUI/Raycast action layer can install known provider CLIs after confirmation. MCP can list and preview those actions, request a local approval ticket, and apply only the exact ticket after it is approved outside the MCP request. Explicit local providers are `gitleaks`, `trufflehog`, `semgrep`, and `syft`. Online-capable providers are `trivy`, `osv-scanner`, `grype`, `scorecard`, and `socket`, and they require explicit online-provider opt-in. Socket support creates a remote Socket scan artifact from dependency manifest metadata; Nightward does not fetch or normalize remote Socket reports in v1.

Provider runs use explicit skip/block/ready states, timeouts, bounded output capture, and redacted metadata only. Oversized provider stdout fails closed as a provider warning instead of being partially parsed. Semgrep execution requires a repo-local config file so Nightward does not use automatic rule discovery by default.

Expand Down Expand Up @@ -326,14 +326,15 @@ The default `nightward` / `nw` command opens the TUI:
- Fix Plan: safe/review/blocked remediation groups
- Backup Plan: private-dotfiles dry-run preview
- Actions: confirmation-gated provider, policy, schedule, backup, cleanup, and setup actions
- MCP Approvals: approve or deny exact MCP-requested action tickets

The TUI is now part of the Rust CLI binary and uses `opentui_rust` directly for the colored dashboard, filled panels, severity ribbons, and fixture-driven screenshots. Release archives and npm-downloaded binaries only need `nightward` and `nw`.

Keyboard shortcuts:

- `1`-`8`: switch sections
- arrow keys or `h`/`j`/`k`/`l`: navigate
- `enter`: confirm selected action in the Actions view
- `enter`: confirm selected action in the Actions view or review a pending MCP approval
- `/`: search findings
- `s`: cycle severity
- `x`: clear filters
Expand All @@ -356,14 +357,14 @@ Nightward can expose local context and bounded Nightward action workflows to MCP
}
```

The server supports scan, doctor, findings, finding/signal explanation, analysis, fix-plan, policy-check, report history/diff, action list/preview, rules, providers, resources, and prompts. It uses stdio only, does not open a network listener, and cannot rewrite arbitrary MCP or agent config. MCP clients cannot apply local writes because tool-call arguments are not an out-of-band local confirmation channel; use the CLI, TUI, or Raycast extension to apply previewed actions.
The server supports scan, doctor, findings, finding/signal explanation, analysis, fix-plan, policy-check, report history/diff, action list/preview/request/status/apply-approved, rules, providers, resources, and prompts. It uses stdio only, does not open a network listener, and cannot rewrite arbitrary MCP or agent config. MCP clients cannot self-confirm writes because tool-call arguments are not an out-of-band local confirmation channel; they can request a bounded action approval, then apply only the exact one-time ticket after the user approves it in the CLI, TUI, or Raycast extension. Cached `nightward_action_apply` calls remain blocked.

## GitHub Action

Nightward can run as a local GitHub Action in scan, policy, or SARIF mode:

```yaml
- uses: JSONbored/nightward@v0.1.4
- uses: JSONbored/nightward@v0.1.11
with:
mode: sarif
output: nightward.sarif
Expand All @@ -388,7 +389,7 @@ See [docs/website.md](docs/website.md) for the page map, custom-domain notes, an
Nightward includes an in-repo `plugin.yaml` for Trunk Check. Import a pinned release tag and enable repo/workspace policy scans:

```sh
trunk plugins add --id nightward https://github.com/JSONbored/nightward v0.1.4
trunk plugins add --id nightward https://github.com/JSONbored/nightward v0.1.11
trunk check enable nightward-policy
```

Expand Down Expand Up @@ -419,7 +420,7 @@ Commands:
- `Export Nightward Analysis`
- `Open Nightward Reports`

The extension shells out to `nw` or `nightward`, renders redacted output, copies explicitly requested exports, and opens the local reports folder. Provider Doctor can enable/disable provider selection for Raycast Analysis and can preview/apply known provider installs only through the shared action registry. `Nightward Actions` uses that same registry as the CLI/TUI for confirmed provider, policy, schedule, backup, cleanup, and disclosure actions.
The extension shells out to `nw` or `nightward`, renders redacted output, copies explicitly requested exports, and opens the local reports folder. Provider Doctor can enable/disable provider selection for Raycast Analysis and can preview/apply known provider installs only through the shared action registry. `Nightward Actions` uses that same registry as the CLI/TUI for confirmed provider, policy, schedule, backup, cleanup, and disclosure actions. `Nightward MCP Approvals` lets the user approve or deny exact MCP-requested action tickets.

See [docs/raycast-extension.md](docs/raycast-extension.md) for preferences, validation, and read-only boundaries.

Expand Down
76 changes: 73 additions & 3 deletions crates/nightward-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use nightward_core::inventory::{
};
use nightward_core::policy::{self, PolicyConfig};
use nightward_core::{
actions, backupplan, mcpserver, providers, reportdiff, reporthtml, rules, schedule, snapshot,
state,
actions, approvals, backupplan, mcpserver, providers, reportdiff, reporthtml, rules, schedule,
snapshot, state,
};
use serde::Serialize;
use std::env;
Expand Down Expand Up @@ -42,6 +42,7 @@ pub fn run() -> Result<()> {
"backup" => cmd_backup(&args),
"schedule" => cmd_schedule(&args),
"actions" => cmd_actions(&args),
"approvals" => cmd_approvals(&args),
"disclosure" => cmd_disclosure(&args),
"help" | "--help" | "-h" => {
print_help();
Expand Down Expand Up @@ -547,6 +548,52 @@ fn cmd_actions(args: &[String]) -> Result<()> {
}
}

fn cmd_approvals(args: &[String]) -> Result<()> {
let home = home_dir_from_env();
match args.first().map(String::as_str) {
Some("list") | None => print_json(&approvals::list(&home)?),
Some("show") | Some("status") => {
let id = args.get(1).ok_or_else(|| anyhow!("approval id required"))?;
print_json(&approvals::status(&home, id)?)
}
Some("request") => {
let action_id = args.get(1).ok_or_else(|| anyhow!("action id required"))?;
print_json(&approvals::request(
&home,
approvals::ApprovalRequestOptions {
action_id: action_id.to_string(),
action_options: approval_options_from_args(action_id, args),
requested_by: value_after(args, "--client")
.unwrap_or("nightward-cli")
.to_string(),
},
)?)
}
Some("approve") => {
let id = args.get(1).ok_or_else(|| anyhow!("approval id required"))?;
print_json(&approvals::approve(
&home,
id,
value_after(args, "--reason").unwrap_or("approved locally"),
)?)
}
Some("deny") => {
let id = args.get(1).ok_or_else(|| anyhow!("approval id required"))?;
print_json(&approvals::deny(
&home,
id,
value_after(args, "--reason").unwrap_or("denied locally"),
)?)
}
Some("apply") => {
let id = args.get(1).ok_or_else(|| anyhow!("approval id required"))?;
print_json(&approvals::apply_approved(&home, id)?)
}
Some("cleanup") => print_json(&approvals::cleanup(&home)?),
_ => Err(anyhow!("unknown approvals command")),
}
}

fn cmd_disclosure(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("status") | None => print_json(&state::disclosure_status(home_dir_from_env())),
Expand All @@ -563,6 +610,26 @@ fn cmd_disclosure(args: &[String]) -> Result<()> {
}
}

fn approval_options_from_args(
action_id: &str,
args: &[String],
) -> approvals::ApprovalActionOptions {
approvals::ApprovalActionOptions {
executable: if action_id == "schedule.install" {
current_executable()
} else {
String::new()
},
policy_path: value_after(args, "--policy")
.or_else(|| value_after(args, "--config"))
.unwrap_or("")
.to_string(),
finding_id: value_after(args, "--finding").unwrap_or("").to_string(),
rule: value_after(args, "--rule").unwrap_or("").to_string(),
reason: value_after(args, "--reason").unwrap_or("").to_string(),
}
}

fn selector(args: &[String]) -> Selector {
Selector {
all: has(args, "--all") || (!has(args, "--finding") && !has(args, "--rule")),
Expand Down Expand Up @@ -676,6 +743,9 @@ fn option_takes_value(option: &str) -> bool {
| "--to"
| "--finding"
| "--rule"
| "--reason"
| "--policy"
| "--client"
| "--format"
| "--input"
)
Expand All @@ -697,7 +767,7 @@ fn version() -> &'static str {

fn print_help() {
println!(
"Nightward audits AI agent state, MCP config, and dotfiles sync risk.\n\nUSAGE:\n nightward Open the TUI\n nightward tui --input scan.json Review a saved report in the TUI\n nightward tui --from old.json --to new.json\n nightward scan --json Scan HOME\n nightward scan --workspace . --json\n nightward analyze --all --with gitleaks --json\n nightward providers doctor --with trivy --online --json\n nightward providers enable gitleaks --confirm\n nightward providers install gitleaks --confirm\n nightward disclosure accept\n nightward fix plan --all --json\n nightward backup create --confirm\n nightward schedule install --confirm\n nightward actions list --json\n nightward actions apply backup.snapshot --confirm\n nightward actions apply reports.cleanup --confirm\n nightward actions apply cache.cleanup --confirm\n nightward actions apply policy.ignore --finding <id> --reason \"reviewed\" --confirm\n nightward report html --input scan.json --output report.html\n nightward report html --from old.json --to new.json --output report.html\n nightward policy check --json\n nightward mcp serve\n\nNightward is local-first and read-only by default. Write-capable actions require disclosure acceptance and explicit confirmation."
"Nightward audits AI agent state, MCP config, and dotfiles sync risk.\n\nUSAGE:\n nightward Open the TUI\n nightward tui --input scan.json Review a saved report in the TUI\n nightward tui --from old.json --to new.json\n nightward scan --json Scan HOME\n nightward scan --workspace . --json\n nightward analyze --all --with gitleaks --json\n nightward providers doctor --with trivy --online --json\n nightward providers enable gitleaks --confirm\n nightward providers install gitleaks --confirm\n nightward disclosure accept\n nightward fix plan --all --json\n nightward backup create --confirm\n nightward schedule install --confirm\n nightward actions list --json\n nightward actions apply backup.snapshot --confirm\n nightward actions apply reports.cleanup --confirm\n nightward actions apply cache.cleanup --confirm\n nightward actions apply policy.ignore --finding <id> --reason \"reviewed\" --confirm\n nightward approvals list --json\n nightward approvals approve <approval-id> --reason \"reviewed\"\n nightward approvals apply <approval-id>\n nightward report html --input scan.json --output report.html\n nightward report html --from old.json --to new.json --output report.html\n nightward policy check --json\n nightward mcp serve\n\nNightward is local-first and read-only by default. Write-capable actions require disclosure acceptance and explicit confirmation. Approval commands do not take --confirm: approve is the local confirmation step, and apply only consumes an already-approved one-time ticket."
);
}

Expand Down
Loading
Loading