osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
# Plugin Protocol V1

This document defines the executable plugin protocol for `osp`.
Plugins are separate binaries discovered at runtime (Option B).

## Goals

- Keep plugin integration stable across independent releases.
- Let backbone CLI/repl render plugin output consistently.
- Avoid in-process ABI coupling.

## Process Model

- Backbone binary: `osp`
- Plugin binaries: `osp-<id>` (for example `osp-acme-inventory`)
- Transport: subprocess invocation + JSON over stdout

## Required Plugin Commands

- `--describe`
  - Prints `DescribeV1` JSON to stdout.
  - Stdout must contain only that JSON payload.
  - Human diagnostics belong on stderr.
  - Exit code 0 on success.
- Normal execution
  - Backbone resolves the plugin from the selected top-level command and then
    invokes the plugin as `<plugin-exe> <selected-command> <remaining-argv...>`.
  - Example: `osp inventory host web-01` invokes the plugin with argv
    `["inventory", "host", "web-01"]`.
  - Backbone also sets `OSP_COMMAND=<selected-top-level-command>`.
  - `OSP_COMMAND` and `argv[1]` carry the same selected command on purpose.
    Plugins may use either, but they should agree.
  - Stdout must contain exactly one `ResponseV1` JSON payload and nothing else.
  - Exit code 0 means backbone should parse stdout as protocol output.
  - Non-zero exit always means process-level failure, even if stdout contains
    JSON-like data.

## Help Delegation

- `osp <plugin-command> --help` and `osp <plugin-command> help` are passed
  through directly to the plugin process.
- For delegated help, backbone does not require `ResponseV1` JSON.
- Plugins may print plain help text to stdout and stderr in this mode.
- Exit code 0 is preferred for normal help output; exit code 2 is acceptable
  for usage-style failures.

## Describe Caching (Backbone Behavior)

- Backbone caches successful `--describe` payloads.
- Cache key is `(executable path, file size, file mtime)`.
- Default cache file:
  `<platform-cache-dir>/osp/describe-v1.json`.
- On Linux this is typically `~/.cache/osp/describe-v1.json`.
- If `XDG_CACHE_HOME` is set, the Linux cache path is
  `$XDG_CACHE_HOME/osp/describe-v1.json`.

## DescribeV1

```json
{
  "protocol_version": 1,
  "plugin_id": "acme-inventory",
  "plugin_version": "0.1.0",
  "min_osp_version": "0.1.0",
  "commands": [
    {
      "name": "inventory",
      "about": "Inventory lookups",
      "subcommands": [
        { "name": "host", "about": "Look up one host", "subcommands": [] },
        { "name": "service", "about": "Look up one service", "subcommands": [] }
      ]
    }
  ]
}
```

Rules:
- `protocol_version` must be exactly `1`.
- `plugin_id` must be unique within discovery scope.
- `commands[].name` is top-level command claimed by the plugin.

## ResponseV1

```json
{
  "protocol_version": 1,
  "ok": true,
  "data": {},
  "error": null,
  "messages": [
    { "level": "info", "text": "Using profile: uio" }
  ],
  "meta": {
    "format_hint": "table",
    "columns": ["uid", "cn"],
    "column_align": ["left", "default"]
  }
}
```

Rules:
- `protocol_version` must be exactly `1`.
- `ok=true` implies `error=null`.
- `ok=false` implies `error` is present.
- `data` is always present (empty object/array is allowed).
- `messages` is optional and defaults to an empty list.
- `meta.columns` controls column order when the payload is rendered as a table.
- `meta.column_align` is optional and follows `meta.columns` positionally.
  Allowed values: `default | left | center | right`.

Message levels:
- `error`
- `warning`
- `success`
- `info`
- `trace`

Backbone behavior:
- plugin `messages` are rendered by `osp-ui` on stderr using the same
  grouping/theme/verbosity rules as built-in commands.
- plugin data remains on stdout.
- `ok=false` is still a protocol-level response and must use exit code 0.
- Plugins should use `error` plus optional `messages` for application-level
  failures they want backbone to render cleanly.
- Plugins should reserve non-zero exits for process-level failures such as
  crashes, missing prerequisites, or transport/setup errors.

## Error Shape

```json
{
  "code": "AUTH_FAILED",
  "message": "Inventory backend unavailable",
  "details": {}
}
```

## Exit Code Guidance

- Inside the plugin process, use exit code `0` for successful protocol
  responses, including `ok=false` application-level failures.
- Use non-zero exits only for process-level failures such as crashes, missing
  prerequisites, or setup/transport problems.
- When a plugin is invoked through `osp`, non-zero plugin exits are collapsed
  into OSP's generic plugin-failure exit family rather than preserving a
  plugin-specific numeric taxonomy.
- If callers need to distinguish auth, config, upstream, or application
  failures, put that meaning in the structured `error.code` field, not in the
  process exit code.

## Compatibility Policy

- Backbone rejects unsupported `protocol_version`.
- Backbone may reject plugins below required `min_osp_version`.
- New fields must be additive and optional.

## Runtime Hints Environment

Backbone injects runtime hints into each plugin subprocess. Plugins can use
`osp_core::runtime::RuntimeHints::from_env()` to parse them.

Required hints:
- `OSP_UI_VERBOSITY=error|warning|success|info|trace`
- `OSP_DEBUG_LEVEL=0|1|2|3`
- `OSP_FORMAT=auto|json|table|md|mreg|value`
- `OSP_COLOR=auto|always|never`
- `OSP_UNICODE=auto|always|never`
- `OSP_TERMINAL_KIND=cli|repl|unknown`

Optional hints:
- `OSP_PROFILE=<active-profile>`
- `OSP_TERMINAL=<raw-TERM-value>`

Additional process env:
- `OSP_COMMAND=<selected-top-level-command>`

## Config-Driven Plugin Env

Backbone can also project selected config values into plugin subprocess env.
This is intended for plugin-specific settings owned by the app config, not for
the runtime hints above.

Config namespaces:
- shared across all plugins:
  - `extensions.plugins.env.<name> = <value>`
- scoped to one plugin id:
  - `extensions.plugins.<plugin-id>.env.<name> = <value>`

Env mapping rules:
- `<name>` is normalized to uppercase with non-alphanumeric characters replaced
  by `_`.
- Backbone prefixes the name with `OSP_PLUGIN_CFG_`.
- Example:
  - `extensions.plugins.env.api.url = "https://example"`
  - `extensions.plugins.acme-inventory.env.api.token = "sekrit"`
  - become `OSP_PLUGIN_CFG_API_URL` and `OSP_PLUGIN_CFG_API_TOKEN`
- plugin-specific values override shared values when they map to the same env
  name.
- app-owned plugin config is injected after runtime hints, so later
  `OSP_PLUGIN_CFG_*` values intentionally win if a plugin reuses the same env
  name across shared and plugin-specific config.
- scalar config values are stringified directly.
- list values are encoded as JSON arrays.

Examples:
- `extensions.plugins.env.api.url = "https://common.example"`
- `extensions.plugins.acme-inventory.env.api.token = "sekrit"`