Files
olp/olp-plugin
fa2d1af130 feat+test: F4 CLI/plugin quota_v2 migration + v1.x #7 AUTH_MISSING test pin (#59)
## F4 — bin/olp.mjs + olp-plugin/index.js migrate to quota_v2

Codex post-v0.5.0 review Q4: both `olp usage` CLI and `/olp usage`
plugin handler read legacy `body.quota` shape, which never has
`percent_used` or meaningful `available` data → display always fell
through to "no quota api" even when anthropic live quota was visible
on the dashboard (D81/D82).

Authority: codex review Q4 (PR #58); ADR 0008 Amendment 2 (quota_v2
shape: { provider, status, utilization, reset, representative_claim,
fallback_percentage, overage, failure?, failure_kind?, backoff_until? }).

### bin/olp.mjs cmdUsage

- When `body.quota_v2` present (non-empty array, server v0.5.0+): render
  per-provider rows with status badge (live/stale/unreachable/unavailable),
  5h + 7d utilization % (color-coded: green <50% / yellow 50-80% / red ≥80%),
  reset countdowns, binding claim, ⚠ stale /  unreachable annotations.
- When `body.quota_v2` absent: fall through to existing legacy `body.quota`
  rendering (preserves backwards compat with pre-v0.5.0 servers).
- Added `formatResetCountdown(epochSeconds)` — 5-range formatter (past /
  <1h / <24h / <7d / ≥7d), ported from dashboard.html D82. Exported for
  tests. Kept in bin/olp.mjs (not lib/) per spec guidance.
- Added `formatAgo(diffMs)` — small helper for stale-row age display.

### olp-plugin/index.js fmtUsage()

- Same quota_v2 migration. Plain-text output (no ANSI), one line per
  provider. Legacy body.quota fallback preserved.
- Added `pluginFormatResetCountdown(epochSeconds)` — intentionally
  duplicated from bin/olp.mjs (olp-plugin ships as a separate package
  and must not import from bin/). Exported for tests.
- Also fixed fmtUsage to read `w.request_count` (dashboard-data shape)
  in addition to legacy `w.requests` (OCP-era shape) for completeness.

### Backwards compat

- Pre-v0.5.0 server returns body.quota only → both surfaces use legacy
  display (unchanged behaviour).
- v0.5.0+ server returns both quota and quota_v2 → both surfaces prefer
  quota_v2.
- Legacy code paths kept (5-10 lines each, not deleted).

## v1.x roadmap #7 — AUTH_MISSING tuple path test coverage — CLOSED

The dedicated test was already shipped at D56 (test-features.mjs line
6255: 'engine: AUTH_MISSING terminates chain, fallbackDetail tuple
records trigger_type:"auth_missing" (D56, v1.x roadmap #7)'). This
commit closes the roadmap entry with a date stamp and PR reference.

Authority: docs/v1x-roadmap.md § "#7 — AUTH_MISSING tuple path test
coverage (D40 follow-up)".

## Tests

Suite 40 (9 new tests — 40a through 40i):
  40a — cmdUsage parses quota_v2 live rows (mock server)
  40b — cmdUsage falls back to legacy body.quota when quota_v2 absent
  40c — olp-plugin fmtUsage parses quota_v2 live row
  40d — olp-plugin fmtUsage falls back to legacy body.quota
  40e — formatResetCountdown covers all 5 time ranges
  40f — pluginFormatResetCountdown covers past/<1h/<24h/<7d/≥7d
  40g — cmdUsage renders quota_v2 stale row with ⚠ stale note
  40h — cmdUsage renders quota_v2 unreachable row with  indicator
  40i — cmdUsage renders quota_v2 unavailable rows correctly

759 → 768 tests, 0 fail.

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:24:46 +10:00
..

olp-plugin

OpenClaw gateway plugin that exposes a /olp slash command on Telegram and Discord, with subcommand parity to the local olp CLI (bin/olp.mjs) minus mutating operations.

Authority: ADR 0010 § Phase 4 D71-D73.

Status

Shipped at v0.4.0 (read-only subset of olp CLI).

What you can do from chat

Slash command Maps to Tier
/olp status GET /v0/management/status owner
/olp health GET /health public
/olp usage GET /v0/management/dashboard-data owner
/olp models GET /v1/models public
/olp cache GET /cache/stats owner
/olp providers local registry view public
/olp chain show [model] local chain view (empty unless wired) public
/olp doctor informational only (HTTP doctor endpoint not yet shipped)
/olp help usage text

What you can NOT do from chat (by design)

The following olp CLI subcommands are deliberately not ported to the chat surface, because Telegram + Discord are shared / persistent message streams and key material or raw audit logs should not be flowing across them:

  • olp keys keygen — key material would land in chat history
  • olp keys revoke — accidental misclick could lock out clients
  • olp restart — a misclick should not cycle the proxy
  • olp logs — audit content may carry PII

Use SSH to the host running OLP and the local olp CLI for those.

Install

The plugin is shipped inside the OLP repo at olp-plugin/. Two install paths:

Option A — OpenClaw CLI

openclaw plugins install /path/to/olp/olp-plugin/
mkdir -p ~/.openclaw/extensions/
ln -s /path/to/olp/olp-plugin/ ~/.openclaw/extensions/olp

Either path makes the plugin discoverable; restart the gateway to pick it up:

openclaw gateway restart

Configure

Edit ~/.openclaw/openclaw.json and add a config block for the olp plugin:

{
  "plugins": {
    "olp": {
      "proxyUrl": "http://127.0.0.1:4567",
      "apiKey": "olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
  }
}
  • proxyUrl — full URL of the OLP proxy. Default http://127.0.0.1:4567 (OLP's default since v0.4.0 / D60). Overridable via OLP_PROXY_URL or OLP_PORT env if you run the gateway under launchd / systemd with custom env.

  • apiKeyowner-tier OLP API key. Required for the subcommands marked owner in the table above. Create one with:

    # On the OLP host, NOT in chat:
    npx olp-keys keygen --owner --name=openclaw-bot
    # Capture the plaintext token from the output — it is printed exactly once.
    

    Use a dedicated bot key (the --name=openclaw-bot example above) so you can npx olp-keys revoke --id=<id> later without affecting the maintainer's personal key.

Use

In Telegram or Discord, after the gateway picks up the plugin:

/olp status
/olp usage
/olp models
/olp help

Output is wrapped in a monospace code block. Long responses are truncated to fit Telegram's ~4096-char per-message limit; a ... [truncated, use SSH for full] suffix marks where the cut happened.

Authorization model

The plugin sends Authorization: Bearer <apiKey> on every request. OLP's server enforces:

  • public-tier endpoints (/health, /v1/models) accept any non-revoked key (or no key at all if auth.allow_anonymous: true).
  • owner-tier endpoints (/v0/management/*, /cache/stats) reject any non-owner key with 403.

If you see 401 unauthorized or 403 forbidden in chat:

  • Verify the configured apiKey is a non-revoked owner-tier key.
  • Verify the key was created on the same host running the OLP server (keys are stored under ~/.olp/keys/ and validated by hash on the server side).
  • Check the OLP server's /health directly with curl to confirm reachability.

Port resolution priority

  1. OLP_PROXY_URL env (full URL) — useful when the gateway runs on a different host than OLP and you proxy in via Tailscale.
  2. OLP_PORT env (port only; localhost assumed).
  3. Plugin config proxyUrl.
  4. Fallback http://127.0.0.1:4567.

Why no Telegram/Discord SDK dependency

OpenClaw provides the transport (Telegram bot + Discord bot are gateway features). This plugin only registers a slash command — it does not open its own websocket / long-poll connection. That means:

  • No new npm dependency.
  • No bot tokens stored in plugin config.
  • Plugin works for any OpenClaw-supported chat surface (currently Telegram + Discord; future surfaces inherit automatically).

Cross-references