## 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>
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 historyolp keys revoke— accidental misclick could lock out clientsolp restart— a misclick should not cycle the proxyolp 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/
Option B — symlink
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. Defaulthttp://127.0.0.1:4567(OLP's default since v0.4.0 / D60). Overridable viaOLP_PROXY_URLorOLP_PORTenv if you run the gateway under launchd / systemd with custom env. -
apiKey— owner-tier OLP API key. Required for the subcommands markedownerin 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-botexample above) so you cannpx 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 ifauth.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
apiKeyis 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
/healthdirectly withcurlto confirm reachability.
Port resolution priority
OLP_PROXY_URLenv (full URL) — useful when the gateway runs on a different host than OLP and you proxy in via Tailscale.OLP_PORTenv (port only; localhost assumed).- Plugin config
proxyUrl. - 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
- Local
olpCLI — the full mutating-capable surface. - OCP
/ocpplugin — the OCP predecessor this is ported from. - ADR 0010 — Phase 4 charter.
- ADR 0007 — multi-key auth model that gates owner-tier subcommands.