Skip to content

JSON output (--json)

Machine-readable output for scripts and CI. Each command writes exactly one JSON object to stdout.

bash
nodehunter scan . --json
nodehunter size ~/work --json
nodehunter list --json
nodehunter find react --json
nodehunter why react --json
nodehunter inspect --json
nodehunter delete . --json --yes

Human-oriented logs ([info], [walk], banners) go to stderr and are fully suppressed with -j / --json or -s / --silent. Use the envelope (ok, summary.notes) and exit code instead.


Envelope shape

json
{
  "ok": true,
  "command": "size",
  "meta": {
    "apiVersion": "1.0.0",
    "schemaVersion": 3,
    "cwd": "/home/you/project"
  },
  "report": {
    "version": 1,
    "scannedAt": "2026-06-17T12:00:00.000Z",
    "root": "/home/you/project",
    "counts": {
      "matched": 2,
      "ignored": 1,
      "symlinksSkipped": 0
    },
    "totalSizeBytes": 99550745,
    "matches": [
      {
        "path": "/home/you/project/node_modules",
        "depth": 1,
        "sizeBytes": 99550732,
        "sizeProvider": "du"
      }
    ],
    "meta": {
      "sizeProvider": "du",
      "timings": { "walkMs": 20, "sizeMs": 500 }
    },
    "rollups": {
      "largest": { "path": "…", "sizeBytes": 99550732 },
      "totalHuman": "94.9 MB"
    }
  },
  "summary": {
    "timings": {
      "wallMs": 530,
      "analysis": { "walkMs": 20, "sizeMs": 500 }
    },
    "tips": [
      { "level": "tip", "text": "Largest project: `/home/you/Projects/foo` — run `nodehunter delete \"/home/you/Projects/foo\"` to reclaim space there." }
    ]
  }
}

Field guide

FieldMeaning
okWhether the command completed successfully
commandCanonical command id (scan, size, list, find, why, inspect, delete)
meta.apiVersionPublished SDK/CLI semver (@nodehunter/core / nodehunter)
meta.schemaVersionEnvelope shape version (integer)
meta.cwdHost working directory when the command ran
reportDiscovery/analysis facts (absent if the command failed before analysis)
report.versionProjectAnalysisReport schema version
report.countsWalker counters — source of truth for matched
report.rollupsDerived helpers (largest, totalHuman) on sized runs
summary.timings.wallMsTotal wall-clock time for the command
summary.timings.analysisCore walk/size/total phase timings (when available)
summary.labelHuman label when different from command (e.g. delete (refused))
summary.countsHost action counts only (deleted, …) — not discovery duplicates
summary.notesNon-fatal host messages (declined confirm, refused delete, …)
summary.tipsContextual guidance (level + text) — same content as human [tip] / [notice] channels
workspaceWorkspace query payload on list, find, why, inspect (see below)

Commands

CommandreportworkspaceNotes
scanDiscovery onlytotalSizeBytes is null
sizeDiscovery + per-match sizesIncludes rollups and stats; human lists capped unless --full
listDiscovery (physical sizes for projects)List payloadProjects: physical total; --package: per-install sizeBytes (attributed)
findDiscoveryFind payloadPer-install sizeBytes on aggregate (attributed)
whyDiscoveryWhy payloadPackage footprint with attributed installation sizes
inspectDiscoveryInspect payloadFocus-specific analysis (--focus duplicates today)
deleteDiscovery previewsummary.counts.deleted when deletion runs

workspace block (list, find, why, inspect)

list, find, why, and inspect include a workspace object alongside report (when discovery succeeded):

list — projects (default):

json
{
  "ok": true,
  "command": "list",
  "meta": { "apiVersion": "1.0.0", "schemaVersion": 3, "cwd": "…" },
  "report": { "…": "ProjectAnalysisReport" },
  "workspace": {
    "version": 1,
    "mode": "projects",
    "items": [
      {
        "projectPath": "/home/you/project",
        "nodeModulesPath": "/home/you/project/node_modules",
        "sizeBytes": 99550732
      }
    ]
  },
  "summary": { "timings": { "wallMs": 120 } }
}

list --package <name>:

json
{
  "workspace": {
    "version": 1,
    "mode": "packages",
    "filter": { "name": "react" },
    "items": [
      {
        "name": "react",
        "version": "18.2.0",
        "path": "/home/you/project/node_modules/react",
        "projectPath": "/home/you/project",
        "sizeBytes": 442368
      }
    ]
  }
}

find <package>:

json
{
  "workspace": {
    "version": 1,
    "name": "react",
    "found": true,
    "aggregate": {
      "name": "react",
      "occurrences": 2,
      "versions": ["18.2.0", "18.3.0"],
      "totalSizeBytes": 884736,
      "installations": [ { "name": "react", "version": "18.2.0", "path": "…", "projectPath": "…", "sizeBytes": 442368 } ]
    }
  }
}

When no installs match, find sets "found": false and omits aggregate.

why <package>:

json
{
  "workspace": {
    "version": 1,
    "name": "react",
    "found": true,
    "explain": {
      "name": "react",
      "projects": [{ "projectPath": "/home/you/project", "nodeModulesPath": "…" }],
      "versions": ["18.2.0", "18.3.0"],
      "installations": [
        { "name": "react", "version": "18.2.0", "path": "…", "projectPath": "…", "sizeBytes": 442368 }
      ],
      "totalSizeBytes": 884736
    }
  }
}

When no installs match, why sets "found": false and omits explain.

inspect --focus duplicates:

Registered presets: duplicates, versions, largest, frameworks, drift, risks — all available today.

json
{
  "workspace": {
    "version": 1,
    "focus": "duplicates",
    "entries": [
      {
        "name": "react",
        "versions": ["18.2.0", "18.3.0"],
        "occurrences": 2,
        "installations": [ { "name": "react", "version": "18.2.0", "path": "…", "projectPath": "…" } ]
      }
    ]
  }
}

Per-installation sizeBytes and aggregate totalSizeBytes are attributed (SizeProvider on each install path). They are not guaranteed to equal physical node_modules totals from size — especially on pnpm/npm layouts.


size stats block

Every sized size run includes a stats aggregate alongside the full report:

json
{
  "ok": true,
  "command": "size",
  "meta": { "apiVersion": "1.0.0", "schemaVersion": 3, "cwd": "…" },
  "report": { "…": "same ProjectAnalysisReport as a normal size run" },
  "stats": {
    "version": 1,
    "matched": 26,
    "totalSizeBytes": 1234567890,
    "totalHuman": "1.1 GB",
    "averageSizeBytes": 47483380,
    "averageHuman": "45.3 MB",
    "top": [
      {
        "path": "/home/you/Projects/foo/node_modules",
        "projectPath": "/home/you/Projects/foo",
        "sizeBytes": 500000000,
        "sizeHuman": "476.8 MB"
      }
    ]
  },
  "summary": { "timings": { "wallMs": 2800 } }
}

Pretty printing

bash
nodehunter scan . --json --json-pretty false   # compact (default when pretty unset)
nodehunter scan . --json --json-pretty true    # indented

Versioning

  • meta.apiVersion tracks the published package semver — it is not a separate docs-maintained number.
  • Bump meta.schemaVersion when the top-level envelope changes (currently 3 — adds optional workspace).
  • Bump report.version when analysis fields change.
  • Check release notes before upgrading SDK consumers.

Docs examples use sync-time version tokens from the root package.json (see apps/docs/README.md).