## TL;DR
Install syft, then run `syft scan . -o spdx-json=sbom.spdx.json` for a directory or `syft scan myimage:latest -o cyclonedx-json=sbom.cdx.json` for an image. syft v1 catalogs OS packages plus npm, pip, Go, Maven, and other ecosystems in one pass, no Docker daemon required.

## Verbatim output (what a successful run looks like)
```text
New version of syft is available: 1.42.3
 ✔ Vulnerability DB        [no update available]
 ✔ Indexed image
 ✔ Cataloged contents      [cfae98bc-cdf4-4479-90f2-f58ff35bc6e3]
   ├─ 25 packages

NAME            VERSION        TYPE
@babel/runtime  7.24.7         javascript
lodash          4.17.21        javascript
...
```

## Steps

### 1. Install syft
```bash
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
syft version
```
Expected: prints a version like `syft 1.42.3`. On macOS, `brew install syft` works too.

### 2. Scan a directory
```bash
syft scan . -o cyclonedx-json=sbom.cdx.json
```
Expected: syft indexes the directory, catalogs packages, writes `sbom.cdx.json`. Use `path/to/a/dir` for any directory.

### 3. Scan a container image
```bash
syft scan alpine:latest -o spdx-json=sbom.spdx.json
```
Expected: if no local Docker daemon is present, syft pulls the image from the registry itself and writes `sbom.spdx.json`. Prefix with `docker:` (e.g. `docker:myimage:tag`) to force scanning a local Docker daemon image.

### 4. Emit both formats in one run
```bash
syft scan . -o cyclonedx-json=sbom.cdx.json -o spdx-json=sbom.spdx.json
```
Expected: both files are written. CycloneDX is the friendliest to downstream tooling; SPDX is what regulators and some enterprise auditors expect.

## When to use this skill

- use when an agent or CI job needs a package inventory of a container image or source directory
- use when someone asks for an SBOM in a specific format (CycloneDX JSON, SPDX JSON, SPDX tag-value)
- use when preparing for license audits, EO 14028 compliance, or CVE triage feeding Grype

## When NOT to use this skill

- not for signing the SBOM or generating attestations (needs cosign or similar)
- not for vulnerability scanning itself; feed the SBOM to Grype for that
- not for cdxgen, Trivy, or package-manager-native SBOM output (`npm sbom`); those are different tools

## Tool and version compatibility

- syft v1.x (tested against v1.42.3, the current CLI reference)
- syft v0.x accepted `syft [source]` without the `scan` subcommand; v1 prefers `syft scan`
- No Docker daemon required; syft pulls registry images directly
- Output formats: cyclonedx-json, cyclonedx-xml, spdx-json, spdx, spdx-tag-value, json (syft native), table

## Variant phrasings

### syft: command not found
This is the install step missing. Run the install.sh one-liner in step 1, or `brew install syft` on macOS, then re-run.

### generate SPDX SBOM from a docker image
`syft scan docker:myimage:tag -o spdx-json=sbom.spdx.json`. The `docker:` prefix forces the local daemon image instead of a registry pull.

### syft scan directory for cyclonedx
`syft scan . -o cyclonedx-json=sbom.cdx.json`. Append `=filename` to `-o` to write the file instead of printing to stdout.

## Why it happens (root cause after the fix)

Regulators and auditors standardized on two SBOM formats, and Syft emits both from a single catalog pass: CycloneDX for machine consumption, SPDX for compliance. The v1 CLI kept the root command but made `scan` the documented subcommand; old one-liners without `scan` still parse but the reference now shows `syft scan` everywhere.

## Edge cases

- Scanning the whole filesystem (`syft scan /`) is slow and permission-noisy; scope to the app directory or image instead
- syft catalogs installed packages, not transitive source deps of compiled binaries without package metadata; binary-only scans can miss things
- `-o spdx-json` defaults to SPDX 2.3; pin `spdx-json@2.2` if the consumer requires the older spec
- Grype consumes syft output directly: `syft scan myimage:latest -o cyclonedx-json | grype` works via stdin for quick triage