Skip to content

Command line

Use this when you want a one-off SBOM from a terminal, a Makefile target, or any shell script. The console script is installed under two names, loom and pitloom -- pick whichever reads better; they run the same tool.

Quick guide

pip install pitloom
loom project .     # SBOM for the Python project in the current dir

loom -h shows the full option list.

Installation

pip install pitloom

Install with AI model metadata extraction support:

pip install "pitloom[ai]"

Install with extra content type detection:

pip install "pitloom[content-type]"

Usage details

Generate an SBOM

Generate a Source SBOM for a Python project in the current directory:

loom project .
loom project /path/to/project -o sbom.spdx3.json

Limitation: the per-file inventory (which files are listed, their hashes, and the package's Merkle-root integrity hash) is currently discovered using Hatchling's own file-inclusion rules, regardless of the project's actual build backend. For a Hatchling project, or a non-Hatchling project whose layout happens to match Hatchling's conventions (a single top-level package, or a src/<name> layout, named after the normalized project name), this is accurate. For a setuptools, Poetry, PDM, or Flit project using backend-specific inclusion rules ([tool.setuptools.packages.find] where=, MANIFEST.in, Poetry's own packages config, etc.), the file list can be silently incomplete or mis-pathed. Project-level metadata (name, version, dependencies, license, authors) is unaffected -- it's read independently and isn't subject to this limitation. Tracked as a near-term roadmap priority.

Generate an Analyzed SBOM from a pre-built wheel (extracting bundled binaries as phantom dependencies):

loom wheel path/to/mypackage-1.0.0-py3-none-any.whl -o sbom.spdx3.json

Embed an SBOM into a wheel (PEP 770)

Generate and embed an SPDX 3 SBOM directly into one or more built .whl files (writing to .dist-info/sboms/ and updating .dist-info/RECORD):

loom embed-wheel dist/mypackage-1.0.0-py3-none-any.whl
loom embed-wheel dist/*.whl --project-dir .

With --project-dir, the file list and hashes always come from the wheel itself, so they're accurate regardless of build backend. What can still be affected by the Source SBOM limitation above is --content-type and --extract-file-header: on a non-Hatchling project whose layout doesn't match Hatchling's conventions, that per-file enrichment can silently fail to attach to any file (falls back to no content-type/header data for it, not a wrong one).

Or inject an existing pre-generated SBOM into built wheels:

loom embed-wheel dist/*.whl --sbom sbom.spdx3.json

Or use --embed directly on loom wheel:

loom wheel dist/mypackage-1.0.0-py3-none-any.whl --embed

Generate a Deployed SBOM reflecting the exact installed environment graph:

loom env -o env.spdx3.json

Generate an Analyzed SBOM for a single AI model file, without a Python project directory. Supported local formats: GGUF, ONNX, Safetensors, PyTorch (.pt/.pth), Keras, HDF5, NumPy, fastText -- see AI model formats for the full extension/install-extra table:

loom model path/to/model.safetensors -o model.spdx3.json
loom model path/to/model.gguf --pretty

Or pass a Hugging Face Hub URL or model ID directly -- no local file required (needs pip install pitloom[huggingface_hub]):

loom model https://huggingface.co/mistralai/Mistral-7B-v0.1
loom model Qwen/Qwen3-235B-A22B   # bare model ID also works

Or use the smart unified entrypoint, which auto-detects the target type:

loom generate . -o sbom.spdx3.json                           # project directory -> Source SBOM
loom generate path/to/model.safetensors -o model.spdx3.json  # AI model asset    -> Analyzed SBOM
loom generate env -o env.spdx3.json                          # installed venv    -> Deployed SBOM

-o/--output is required for generate: unlike project/wheel/ model/env, which each know their target type and so have an obvious default filename, generate dispatches across several target types with no single natural default -- pass -o explicitly, or use the target-specific command for its own default.

Enrich an SBOM

Fill AI-model metadata gaps (license, datasets) from a local README.md/MODEL_CARD.md's YAML frontmatter -- off by default, opt in with --enrich on loom model/loom project/loom generate, or run it standalone to produce a mergeable fragment:

loom model path/to/model.safetensors --enrich -o model.spdx3.json

# Standalone: writes a fragment, doesn't generate a full SBOM
loom enrich path/to/model.safetensors -o model.enrich.spdx3.json
# When merging into a project-level (not single-model) base SBOM, add:
loom enrich path/to/model.safetensors --project-dir . -o model.enrich.spdx3.json

Register the fragment under [tool.pitloom.fragment] and re-run loom project/loom generate to merge it in.

For prose-reading enrichment (an AI agent reading the actual README text, not just its frontmatter), see the Agent Skills page instead -- the sbom-enrich skill.

Merge fragments

loom merge .spdx3-fragments/ -o combined.spdx3.json

Pin ids across fragments

Fragments are written by independent runs, so the same dataset or model would normally get a different spdxId in each run. Pin ids ahead of time, or reuse ids already present in an SBOM:

pitloom ids generate data src --entity model      # pin ids before running
pitloom ids import existing-sbom.spdx3.json       # or reuse ids from an SBOM

project/wheel/env also auto-harvest newly-minted ids back into the resolved registry after each run (--update-registry/--no-update-registry, on by default) -- see Loom IDs across fragments for what's excluded (ai_AIPackage, dataset_DatasetPackage) and why.

Useful flags

  • -o FILE / --output FILE -- explicit output path.
  • --pretty -- indent the JSON for human reading (default: compact).
  • --offline -- forbid network access (PyPI/Hugging Face lookups).
  • -v / --verbose -- print effective options and where each came from.

See Enrich an SBOM above for --enrich/--no-enrich.

Every subcommand that writes an SBOM (project, model, env, wheel, embed-wheel) prints PITLOOM_SBOM_OUTPUT_PATH=<path> to stdout after writing it -- the resolved path, including when a command's own default-naming logic picked it rather than an explicit -o. Scripts and CI can parse this line instead of re-deriving the default-naming logic themselves.

Configuration

See Configuration for the full reference -- every [tool.pitloom] setting, its default, and its CLI/Action/API mapping. The sections below walk through the two settings with the most nuance.

Creator and creation metadata

These flags apply to project, AI model, and Hugging Face SBOM generation alike. --creator-name is repeatable -- each occurrence starts a new creator, in order; --creator-type (person default, organization, software-agent, agent) and --creator-email set the type/email of the most recently named creator. --creation-tool records what produced it (default "Pitloom", also repeatable; --no-creation-tool to omit); --creation-comment/--creation-datetime set free-text provenance and an ISO 8601 timestamp:

loom project . --creator-name "Alice" --creator-email "alice@example.com"
loom project . --creator-name "Acme Corp" --creator-type organization
loom project . --creation-datetime "2026-01-15T10:00:00Z" --creation-comment "CI run #123"

The same fields can be set in pyproject.toml under [[tool.pitloom.creator]] / [[tool.pitloom.creation-tool]] (CLI flags take precedence, replacing the whole list rather than merging):

[[tool.pitloom.creator]]
name = "Alice"
email = "alice@example.com"
type = "person"       # or "organization", "software-agent", "agent"

[[tool.pitloom.creation-tool]]
name = "MyCompany SBOM Wrapper"

[tool.pitloom.creation]
creation-datetime = "2026-01-15T10:00:00Z"
creation-comment = "Generated in CI pipeline #123"

See Creation metadata for what these fields record and why.

Metadata provenance

Controlled by [tool.pitloom.provenance] in pyproject.toml:

[tool.pitloom.provenance]
format = "both"                    # "annotation" | "comment" | "both" (default)
detail = "minimal"                 # "minimal" (default) | "full"
preserve-source-metadata = "auto"  # "auto" (default) | "always" | "never"

See Metadata provenance for what each setting does and worked examples.

See also