diff --git a/README.md b/README.md index 16eda25..090fdd1 100644 --- a/README.md +++ b/README.md @@ -185,9 +185,9 @@ Use `--config ` to add an explicit config file. See the generated [Config Managed plugins let you add detectors, matchers, and auditors without forking Bomly: ```bash -bomly plugin install github:bomly-dev/bomly-plugin-bun-lock-detector@v0.1.0 -bomly plugin enable bomly.examples.detector.bun-lock -bomly plugin verify bomly.examples.detector.bun-lock +bomly plugins install github:bomly-dev/bomly-plugin-bun-lock-detector@v0.1.0 +bomly plugins enable bomly.examples.detector.bun-lock +bomly plugins verify bomly.examples.detector.bun-lock ``` See [Plugins](docs/PLUGINS.md) for install, trust, and authoring guidance. diff --git a/dev-docs/ARCHITECTURE.md b/dev-docs/ARCHITECTURE.md index ef05aee..14832f7 100644 --- a/dev-docs/ARCHITECTURE.md +++ b/dev-docs/ARCHITECTURE.md @@ -259,7 +259,7 @@ Bomly uses a hybrid plugin model: - Built-in detectors, matchers, and auditors stay in-process by default. - External managed plugins are installed into `~/.bomly/plugins`. -- Runtime preparation loads enabled external plugins into the registry as adapters so the scan engine still owns orchestration. External plugins are disabled on install and become runnable only after `bomly plugin enable `. +- Runtime preparation loads enabled external plugins into the registry as adapters so the scan engine still owns orchestration. External plugins are disabled on install and become runnable only after `bomly plugins enable `. Managed plugins currently expose the same three runtime roles as core components: diff --git a/docs/DETECTORS.md b/docs/DETECTORS.md index 6de4d93..8115354 100644 --- a/docs/DETECTORS.md +++ b/docs/DETECTORS.md @@ -23,8 +23,8 @@ For example, the `npm` chain is `npm-detector` → `syft-detector`: Per-ecosystem chains are listed in [`detectors/ecosystems/`](detectors/ecosystems/). The full live list lives in the CLI: ```bash -bomly plugin list --detectors -bomly plugin list --detectors --json +bomly plugins list --detectors +bomly plugins list --detectors --json ``` ## Native vs. Syft diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index c0b302d..e14b83d 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -62,6 +62,6 @@ The vocabulary Bomly uses, with one-sentence definitions and pointers to the doc **Plugin** — An external binary that adds a detector, matcher, auditor, or analyzer over Bomly's v1 gRPC protocol. See [Plugins](PLUGINS.md). -**Built-in** — Components compiled into the Bomly binary. Listed by `bomly plugin list`. +**Built-in** — Components compiled into the Bomly binary. Listed by `bomly plugins list`. **`bomly` vs. `bomly-lite`** — `bomly` ships with Syft and Grype linked in; `bomly-lite` shells out to external `syft` and `grype` on `PATH`. Same flags, same outputs. diff --git a/docs/MATCHERS.md b/docs/MATCHERS.md index 2f87aa4..f4f8b24 100644 --- a/docs/MATCHERS.md +++ b/docs/MATCHERS.md @@ -15,8 +15,8 @@ Bomly is **offline-safe by default**. Matchers that use the network only run whe The full live list lives in the CLI: ```bash -bomly plugin list --matchers -bomly plugin list --matchers --json +bomly plugins list --matchers +bomly plugins list --matchers --json ``` ## Running matchers diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index cdcfffa..eb62c61 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -6,7 +6,7 @@ Bomly plugins let you extend scans without changing the Bomly binary. Today, man - **matchers** that enrich packages with vulnerabilities, licenses, lifecycle data, or other package metadata - **auditors** that turn graph and registry data into findings or risk scores -External analyzer plugins are not supported yet. `bomly plugin list --analyzers` can show built-in reachability analyzers, but the external plugin runtime currently serves only detectors, matchers, and auditors through `sdk.ServeDetector`, `sdk.ServeMatcher`, and `sdk.ServeAuditor`. +External analyzer plugins are not supported yet. `bomly plugins list --analyzers` can show built-in reachability analyzers, but the external plugin runtime currently serves only detectors, matchers, and auditors through `sdk.ServeDetector`, `sdk.ServeMatcher`, and `sdk.ServeAuditor`. ## Start Here @@ -55,10 +55,10 @@ Plugins do not get install hooks, post-install scripts, or automatic execution f Installed external plugins are disabled by default. They do not participate in scans until you enable them: ```bash -bomly plugin enable +bomly plugins enable ``` -Treat `bomly plugin enable` as the trust decision. When enabled, a plugin runs with the same user-level privileges as the Bomly process. It can read and write files, make network connections, spawn child processes, and access environment variables available to that user. +Treat `bomly plugins enable` as the trust decision. When enabled, a plugin runs with the same user-level privileges as the Bomly process. It can read and write files, make network connections, spawn child processes, and access environment variables available to that user. Repository-declared plugins are never executed automatically. The host must explicitly install and enable the plugin before it can run. @@ -71,36 +71,36 @@ git clone git@github.com:bomly-dev/bomly-plugin-bun-lock-detector.git cd bomly-plugin-bun-lock-detector go test ./... go build -o ./bin/bomly-plugin-bun-lock-detector . -bomly plugin install ./bin/bomly-plugin-bun-lock-detector --dev -bomly plugin enable bomly.examples.detector.bun-lock +bomly plugins install ./bin/bomly-plugin-bun-lock-detector --dev +bomly plugins enable bomly.examples.detector.bun-lock bomly scan --path ./my-bun-project --detectors bomly.examples.detector.bun-lock ``` The matcher and auditor examples use the same workflow: ```bash -bomly plugin enable clearlydefined-license-matcher +bomly plugins enable clearlydefined-license-matcher bomly scan --enrich --matchers +clearlydefined-license-matcher -bomly plugin enable bomly.examples.auditor.meme-deps +bomly plugins enable bomly.examples.auditor.meme-deps bomly scan --audit --auditors +bomly.examples.auditor.meme-deps ``` Check that Bomly can see any installed plugin: ```bash -bomly plugin list --external -bomly plugin info -bomly plugin verify -bomly plugin test -bomly plugin doctor +bomly plugins list --external +bomly plugins info +bomly plugins verify +bomly plugins test +bomly plugins doctor ``` Disable or uninstall it later: ```bash -bomly plugin disable -bomly plugin uninstall +bomly plugins disable +bomly plugins uninstall ``` ## Common Commands @@ -108,44 +108,44 @@ bomly plugin uninstall List plugins: ```bash -bomly plugin list -bomly plugin list --external -bomly plugin list --detectors -bomly plugin list --matchers --json -bomly plugin list --auditors +bomly plugins list +bomly plugins list --external +bomly plugins list --detectors +bomly plugins list --matchers --json +bomly plugins list --auditors ``` Show one plugin: ```bash -bomly plugin info -bomly plugin info --json +bomly plugins info +bomly plugins info --json ``` Install a plugin: ```bash -bomly plugin install ./dist/bomly-plugin-example.tar.gz -bomly plugin install ./bin/bomly-plugin-example --dev -bomly plugin install https://example.com/bomly-plugin-example.tar.gz --checksum sha256:... -bomly plugin install https://example.com/bomly-plugin-example.tar.gz --insecure-skip-checksum -bomly plugin install github:bomly-dev/bomly-plugin-bun-lock-detector@v0.1.0 +bomly plugins install ./dist/bomly-plugin-example.tar.gz +bomly plugins install ./bin/bomly-plugin-example --dev +bomly plugins install https://example.com/bomly-plugin-example.tar.gz --checksum sha256:... +bomly plugins install https://example.com/bomly-plugin-example.tar.gz --insecure-skip-checksum +bomly plugins install github:bomly-dev/bomly-plugin-bun-lock-detector@v0.1.0 ``` Check a plugin: ```bash -bomly plugin verify # manifest, checksum, binary, runtime descriptor -bomly plugin test # runtime readiness -bomly plugin doctor # verify + test +bomly plugins verify # manifest, checksum, binary, runtime descriptor +bomly plugins test # runtime readiness +bomly plugins doctor # verify + test ``` Enable, disable, or remove a plugin: ```bash -bomly plugin enable -bomly plugin disable -bomly plugin uninstall +bomly plugins enable +bomly plugins disable +bomly plugins uninstall ``` ## Select Plugins During A Scan @@ -266,7 +266,7 @@ For private GitHub Releases, set one of these environment variables before insta ```bash export BOMLY_GITHUB_TOKEN= # Also accepted: GITHUB_TOKEN, GH_TOKEN, GITHUB_AUTH_TOKEN -bomly plugin install github:bomly-dev/bomly-plugin-bun-lock-detector@v0.1.0 +bomly plugins install github:bomly-dev/bomly-plugin-bun-lock-detector@v0.1.0 ``` Bomly attaches the token only to `github:owner/repo@tag` metadata, checksum, and asset downloads. Direct URL installs do not receive GitHub auth headers. @@ -303,7 +303,7 @@ External plugins are native OS subprocesses. They are not sandboxed, not contain **Recommended practices:** - Always supply `--checksum` for direct URL installs. -- Run `bomly plugin verify ` before enabling any plugin installed from an external source. -- Treat `bomly plugin enable` as the explicit trust decision for granting execution privileges. +- Run `bomly plugins verify ` before enabling any plugin installed from an external source. +- Treat `bomly plugins enable` as the explicit trust decision for granting execution privileges. - Prefer `github:owner/repo@tag` installs when releases publish `SHA256SUMS`. - Do not enable plugins you did not build or obtain from a source you control. diff --git a/docs/plugins/how-to-implement-auditor.md b/docs/plugins/how-to-implement-auditor.md index 8d19f8e..ac2b6ec 100644 --- a/docs/plugins/how-to-implement-auditor.md +++ b/docs/plugins/how-to-implement-auditor.md @@ -137,8 +137,8 @@ For development, build and install the binary directly: ```bash go build -o ./bin/bomly-plugin-meme-auditor . -bomly plugin install ./bin/bomly-plugin-meme-auditor --dev -bomly plugin enable bomly.examples.auditor.meme-deps +bomly plugins install ./bin/bomly-plugin-meme-auditor --dev +bomly plugins enable bomly.examples.auditor.meme-deps ``` For distribution, package a package-only `bomly-plugin.json` manifest with the binary: @@ -157,9 +157,9 @@ The manifest contains package and install fields only. Bomly probes the binary a Check installation and runtime readiness: ```bash -bomly plugin verify bomly.examples.auditor.meme-deps -bomly plugin test bomly.examples.auditor.meme-deps -bomly plugin doctor bomly.examples.auditor.meme-deps +bomly plugins verify bomly.examples.auditor.meme-deps +bomly plugins test bomly.examples.auditor.meme-deps +bomly plugins doctor bomly.examples.auditor.meme-deps ``` Run only this auditor: diff --git a/docs/plugins/how-to-implement-detector.md b/docs/plugins/how-to-implement-detector.md index a4f0d1c..a51c692 100644 --- a/docs/plugins/how-to-implement-detector.md +++ b/docs/plugins/how-to-implement-detector.md @@ -128,8 +128,8 @@ For development, build and install the binary directly: ```bash go build -o ./bin/bomly-plugin-bun-lock-detector . -bomly plugin install ./bin/bomly-plugin-bun-lock-detector --dev -bomly plugin enable bomly.examples.detector.bun-lock +bomly plugins install ./bin/bomly-plugin-bun-lock-detector --dev +bomly plugins enable bomly.examples.detector.bun-lock ``` For distribution, package a package-only `bomly-plugin.json` manifest with the binary: @@ -148,9 +148,9 @@ The manifest contains package and install fields only: ID, kind, version, runtim Check installation and runtime readiness: ```bash -bomly plugin verify bomly.examples.detector.bun-lock -bomly plugin test bomly.examples.detector.bun-lock -bomly plugin doctor bomly.examples.detector.bun-lock +bomly plugins verify bomly.examples.detector.bun-lock +bomly plugins test bomly.examples.detector.bun-lock +bomly plugins doctor bomly.examples.detector.bun-lock ``` Run only this detector: diff --git a/docs/plugins/how-to-implement-matcher.md b/docs/plugins/how-to-implement-matcher.md index 325640f..bda03df 100644 --- a/docs/plugins/how-to-implement-matcher.md +++ b/docs/plugins/how-to-implement-matcher.md @@ -143,8 +143,8 @@ For development, build and install the binary directly: ```bash go build -o ./bin/bomly-plugin-clearlydefined-matcher . -bomly plugin install ./bin/bomly-plugin-clearlydefined-matcher --dev -bomly plugin enable clearlydefined-license-matcher +bomly plugins install ./bin/bomly-plugin-clearlydefined-matcher --dev +bomly plugins enable clearlydefined-license-matcher ``` For distribution, package a package-only `bomly-plugin.json` manifest with the binary: @@ -163,9 +163,9 @@ The manifest contains package and install fields only. Bomly probes the binary a Check installation and runtime readiness: ```bash -bomly plugin verify clearlydefined-license-matcher -bomly plugin test clearlydefined-license-matcher -bomly plugin doctor clearlydefined-license-matcher +bomly plugins verify clearlydefined-license-matcher +bomly plugins test clearlydefined-license-matcher +bomly plugins doctor clearlydefined-license-matcher ``` Run only this matcher during enrichment: diff --git a/internal/cli/help_cmd.go b/internal/cli/help_cmd.go index be18263..01a9042 100644 --- a/internal/cli/help_cmd.go +++ b/internal/cli/help_cmd.go @@ -44,7 +44,7 @@ func optionValuesHelpSection(cmd *cobra.Command) string { return "" } - return "\n\nExplore available detectors, matchers, and auditors with `bomly plugin list`." + return "\n\nExplore available detectors, matchers, and auditors with `bomly plugins list`." } func exitCodesHelpSection(cmd *cobra.Command) string { diff --git a/internal/cli/help_cmd_test.go b/internal/cli/help_cmd_test.go index 99f9d24..9eee79d 100644 --- a/internal/cli/help_cmd_test.go +++ b/internal/cli/help_cmd_test.go @@ -19,7 +19,7 @@ func TestOptionValuesHelpSection(t *testing.T) { cmd.Flags().String("ecosystems", "", "") got := optionValuesHelpSection(cmd) - if !strings.Contains(got, "bomly plugin list") { + if !strings.Contains(got, "bomly plugins list") { t.Fatalf("expected plugin list hint, got %q", got) } } diff --git a/internal/cli/plugin_cmd.go b/internal/cli/plugin_cmd.go index 395a038..43b309b 100644 --- a/internal/cli/plugin_cmd.go +++ b/internal/cli/plugin_cmd.go @@ -22,10 +22,11 @@ import ( func newPluginCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "plugin", - Short: "Manage Bomly managed plugins", - Example: " bomly plugin list --all\n" + - " bomly plugin info osv", + Use: "plugins", + Aliases: []string{"plugin"}, + Short: "Manage Bomly managed plugins", + Example: " bomly plugins list --all\n" + + " bomly plugins info osv", } cmd.AddCommand( newPluginListCmd(), @@ -55,7 +56,7 @@ func newPluginListCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list", Short: "List built-in and installed plugins", - Example: " bomly plugin list --detectors\n bomly plugin list --external --json", + Example: " bomly plugins list --detectors\n bomly plugins list --external --json", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { options, err := commandOptions(cmd) @@ -176,7 +177,7 @@ func newPluginInfoCmd() *cobra.Command { cmd := &cobra.Command{ Use: "info ", Short: "Show plugin metadata", - Example: " bomly plugin info osv\n bomly plugin info npm-native --json", + Example: " bomly plugins info osv\n bomly plugins info npm-native --json", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { options, err := commandOptions(cmd) @@ -910,7 +911,7 @@ func pluginInfoPackageManagers(info managedplugin.Info) []plugschema.PackageMana // pluginInfoLanguages returns the SupportedLanguages list for plugin // kinds that carry one. Today only Analyzer plugins do; other kinds -// return nil so `bomly plugin info` cleanly omits the Languages line. +// return nil so `bomly plugins info` cleanly omits the Languages line. func pluginInfoLanguages(info managedplugin.Info) []plugschema.Language { if info.Kind == plugschema.PluginKindAnalyzer && info.AnalyzerDescriptor != nil { return append([]plugschema.Language(nil), info.AnalyzerDescriptor.SupportedLanguages...) diff --git a/internal/cli/plugin_cmd_test.go b/internal/cli/plugin_cmd_test.go index 2a73571..e5cbfc7 100644 --- a/internal/cli/plugin_cmd_test.go +++ b/internal/cli/plugin_cmd_test.go @@ -321,6 +321,26 @@ func assertInOrder(t *testing.T, value string, parts []string) { } } +func TestPluginCommandAlias(t *testing.T) { + root := newPluginTestRoot(t) + + plural, _, err := root.Find([]string{"plugins"}) + if err != nil { + t.Fatalf("root.Find(plugins) error = %v", err) + } + if plural.Name() != "plugins" { + t.Fatalf("expected canonical command name %q, got %q", "plugins", plural.Name()) + } + + singular, _, err := root.Find([]string{"plugin"}) + if err != nil { + t.Fatalf("root.Find(plugin) error = %v", err) + } + if singular != plural { + t.Fatal("expected the plugin alias to resolve to the plugins command") + } +} + func newPluginTestRoot(t *testing.T) *cobra.Command { t.Helper() t.Setenv("HOME", t.TempDir()) diff --git a/internal/cli/root_cmd_test.go b/internal/cli/root_cmd_test.go index 1652338..35963fa 100644 --- a/internal/cli/root_cmd_test.go +++ b/internal/cli/root_cmd_test.go @@ -365,7 +365,7 @@ func TestRootHelp_IncludesAvailableOptionValuesSection(t *testing.T) { t.Fatalf("expected help output to contain %q, got:\n%s", expected, helpText) } } - if strings.Contains(helpText, "Explore available detectors, matchers, and auditors with `bomly plugin list`.") { + if strings.Contains(helpText, "Explore available detectors, matchers, and auditors with `bomly plugins list`.") { t.Fatalf("expected root help output to omit selector guidance, got:\n%s", helpText) } for _, nonGlobal := range []string{"--enrich", "--audit", "--analyze", "--ecosystems", "--detectors"} { @@ -423,7 +423,7 @@ func TestRootHelp_CommandExamplesRender(t *testing.T) { "bomly scan -o spdx=bomly.spdx.json", "bomly scan --url https://github.com/bomly-dev/bomly-cli --ref main --json", "bomly scan --container alpine:3.20", - "Explore available detectors, matchers, and auditors with `bomly plugin list`.", + "Explore available detectors, matchers, and auditors with `bomly plugins list`.", }, notInText: []string{"Exit Codes:"}, }, @@ -440,9 +440,15 @@ func TestRootHelp_CommandExamplesRender(t *testing.T) { notInText: []string{"Exit Codes:"}, }, { - name: "plugin", + name: "plugins", + args: []string{"plugins", "--help"}, + examples: []string{"Examples:", "bomly plugins list --all"}, + notInText: []string{"Exit Codes:"}, + }, + { + name: "plugin-alias", args: []string{"plugin", "--help"}, - examples: []string{"Examples:", "bomly plugin list --all"}, + examples: []string{"Examples:", "bomly plugins list --all"}, notInText: []string{"Exit Codes:"}, }, } diff --git a/internal/support/component_docs.go b/internal/support/component_docs.go index 9320693..43e06b7 100644 --- a/internal/support/component_docs.go +++ b/internal/support/component_docs.go @@ -64,8 +64,8 @@ For example, the `+"`npm`"+` chain is `+"`npm-detector`"+` → `+"`syft-detector Per-ecosystem chains are listed in [`+"`detectors/ecosystems/`"+`](detectors/ecosystems/). The full live list lives in the CLI: `+"```bash"+` -bomly plugin list --detectors -bomly plugin list --detectors --json +bomly plugins list --detectors +bomly plugins list --detectors --json `+"```"+` ## Native vs. Syft @@ -234,8 +234,8 @@ Bomly is **offline-safe by default**. Matchers that use the network only run whe The full live list lives in the CLI: `+"```bash"+` -bomly plugin list --matchers -bomly plugin list --matchers --json +bomly plugins list --matchers +bomly plugins list --matchers --json `+"```"+` ## Running matchers