.cursor/skills/ndb2-api/SKILL.md
Describes how mission-control integrates with the Off-Nominal NDB2 HTTP API and webhooks: V2 (preferred) vs legacy V1 paths, shared client, error shapes, and webhook routes. Use when adding or changing NDB2 calls, webhooks, predictions, bets, votes, scores, or migrating from V1 to V2.
npx skillsauth add Off-Nominal/mission-control ndb2-apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
NDB2 is the predictions backend. This repo calls it over HTTP and receives webhooks. Prefer V2 for new work; V1 remains for endpoints not yet moved.
mcconfig.ndb2.baseUrl — API origin (NDB2_API_BASEURL).mcconfig.ndb2.clientId — Bearer token for API and webhook auth (NDB2_CLIENT_ID).All outbound calls use Authorization: Bearer <clientId>.
Shared contracts live in @offnominal/ndb2-api-types (see package.json version).
@offnominal/ndb2-api-types/v2 (e.g. Entities, Endpoints, Webhooks, Utils).src/providers/ndb2-client/types.ts under namespace NDB2API (and enums like PredictionLifeCycle).src/providers/ndb2-client/index.ts — class Ndb2Client, default export ndb2Client (constructed with mcconfig.ndb2.clientId, initialize() loads seasons from V2).
Path convention: new URL(baseURL) then url.pathname = "api/v2/..." or "api/..." (V1).
| Method | Path | Client method | Notes |
|--------|------|---------------|--------|
| GET | api/v2/seasons | getSeasons() | Cached on client after initialize() |
| GET | api/v2/predictions/:id | getPrediction(id) | Returns data only |
| POST | api/v2/predictions | addPrediction(body) | Body type: Endpoints.Predictions.POST_Predictions.Body |
| PATCH | api/v2/predictions/:id/retire | retirePrediction(id, discord_id) | V2 retire |
Successful V2 JSON responses are unwrapped to res.data.data in the client. Errors use handleError: response shape { success, errors: [{ code, message }], data }; thrown value is a [userMessage, detailMessage] tuple (same pattern as other providers).
Paths are under api/... (no v2). These methods use handleError_v1, which expects NDB2API.GeneralResponse: { success, errorCode, message, data }.
Still routed through Ndb2Client today:
POST api/predictions/:id/bets — addBetPOST api/predictions/:id/votes — addVotePOST api/predictions/:id/snooze_checks/:snoozeCheckId — addSnoozeVotePOST api/predictions/:id/trigger — triggerPredictionGET api/users/discord_id/:discord_id/scores — getScoresGET api/predictions/search — searchPredictions (SearchOptions, SortByOption)GET api/scores (with view=points|predictions|bets, optional season) — leaderboard helpersPATCH api/predictions/:id/snooze — snoozePredictionWhen migrating a call from V1 to V2, switch the path, response unwrapping (data vs full body), error handler (handleError vs handleError_v1), and types to the V2 package.
Service pattern: src/services/ndb2/add-prediction/index.ts builds NDB2API.Endpoints.Predictions.POST_Predictions.Body with discord_id, text, date (ISO string), driver ("date" | "event"), then ndb2Client.addPrediction(body).
Router: src/services/ndb2/webhooks/index.ts.
validateWebhookAuthorization — Authorization: Bearer must match mcconfig.ndb2.clientId.POST .../ndb2 — body must pass isNdb2WebhookEvent (src/services/ndb2/webhooks/v1/types.ts). Several event names are short-circuited as ignored before handleV1Webhook (see router): e.g. new_prediction, untriggered_prediction, unjudged_prediction, retired_prediction on this path.POST .../ndb2/v2 — validate payload with API.Webhooks.isWebhookPayloadV2 from @offnominal/ndb2-api-types/v2. Handler: src/services/ndb2/webhooks/v2/index.ts (e.g. untriggered_prediction, unjudged_prediction, retired_prediction).Middleware responds early with JSON "thank u"; do not assume the handler must send the HTTP response.
src/providers/ndb2-client/src/services/ndb2/webhooks/ (v1/, v2/, middleware.ts)src/services/ndb2/ (e.g. add-prediction, retire-prediction)When the OpenAPI or ndb2-api-types package changes, update call sites and this skill if behavior or paths shift.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.