diff --git a/CHANGELOG.md b/CHANGELOG.md index 54cb4d3..9456382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,63 @@ that may never merge. They are not releases and are not listed here. ## Unreleased +- `mapbox ev-charge-finder search`/`get`/`list-operators`, EV charging + stations near a point — searchable by connector type, operator, charging + power, availability, amenities and payment method — plus one station's + full detail and the list of known operators. Hand-authored into + `custom-openapi/` for the same reason this session's other additions + were: no upstream spec exists yet. Not verified live: this API is + Private Preview and this account isn't enrolled — confirmed directly (a + plain `curl` gets `401 invalid access token` here and `200` on every + other service with the same token), not assumed. + + Also fixes a real bug the new spec's `search`↔`get` pairing surfaced in + the detail/listing linker (`link_detail_operations` in `src/spec.rs`): + a listing with a required query parameter of its own — `search` needs + `latitude`/`longitude`/`distance` — was being suggested bare on a failed + detail lookup's 404, which `listing_command` (`remedy.rs`) has no way to + fill, since it only recovers path parameters. A listing with any + required query parameter is now excluded from that link entirely; no + other service had one, so nothing else changes. + +- `mapbox feedback list`/`get`, reading feedback submitted against Mapbox + API responses — filterable, sortable, paginated. Hand-authored into + `custom-openapi/` for the same reason the Navigation commands were: no + upstream spec exists yet. `feedback create`, the write side, is not a + command — confirmed directly against production that `user-feedback:write` + is silently dropped from a `POST /oauth/register` grant, the same + unregistrable shape `accounts create-token` and `styles + download-style-zip` already document, so no `mapbox auth login` token can + ever carry it. + +- `mapbox matrix compute`, travel time and/or distance between every pair in + a set of up to 25 coordinates in one call, for driving (with or without + live traffic), walking, or cycling. Hand-authored into `custom-openapi/` + for the same reason the other Navigation commands were: no upstream spec + exists yet. Reuses the `profile`-vs-`--profile` collision fix + (`ARG_NAME_OVERRIDES` gets a fourth row). `--sources`/`--destinations` + take semicolon-separated indices, not comma — verified against production + after the API answered a comma-separated list with a 422. + +- `mapbox map-matching match`, snapping a noisy GPS trace to the road + network and returning the route it most likely followed, for driving + (with or without live traffic), walking, or cycling. Hand-authored into + `custom-openapi/` for the same reason `mapbox directions route` and + `mapbox isochrone contours` were: no upstream spec exists yet. Reuses the + `profile`-vs-`--profile` collision fix (`ARG_NAME_OVERRIDES` gets a third + row). Excludes POST, for the same reason `directions route` does — the + API's own POST is for a trace too long for a URL, a real gap rather than + a design choice. + +- `mapbox isochrone contours`, how far you can get from a point in a given + time or distance, for driving (with or without live traffic), walking, or + cycling — as GeoJSON polygons or linestrings. Hand-authored into + `custom-openapi/` for the same reason `mapbox directions route` was: no + upstream spec exists yet. Reuses `directions route`'s fix for a spec + parameter named `profile` colliding with the global `--profile` flag + (`ARG_NAME_OVERRIDES` already covered the mechanism; this is a second row, + not a second fix). + - `mapbox directions route`, routes between 2-25 waypoints for driving (with or without live traffic), walking, or cycling. Hand-authored into `custom-openapi/` rather than waiting on an upstream spec — the whole diff --git a/README.md b/README.md index 1dd71af..da88da6 100644 --- a/README.md +++ b/README.md @@ -169,8 +169,13 @@ Each API is a top-level subcommand, one sub-subcommand per operation: ```sh mapbox accounts * mapbox directions * +mapbox ev-charge-finder * +mapbox feedback * mapbox fonts * mapbox geocoder * +mapbox isochrone * +mapbox map-matching * +mapbox matrix * mapbox search * mapbox sprites * mapbox static * diff --git a/custom-openapi/ev-charge-finder/openapi/ev-charge-finder.yaml b/custom-openapi/ev-charge-finder/openapi/ev-charge-finder.yaml new file mode 100644 index 0000000..462cc5a --- /dev/null +++ b/custom-openapi/ev-charge-finder/openapi/ev-charge-finder.yaml @@ -0,0 +1,265 @@ +openapi: "3.0.0" +# `parse_spec` turns `info.description` below into this service's clap +# `long_about`, so it also reaches `mapbox ev-charge-finder --help`, +# `--schema` and `generate-skills` output. Keep it to API prose only — the +# provenance below is for whoever edits this file, not for a CLI user: +# +# Hand-authored down to the parameters documented at +# docs.mapbox.com/api/navigation/ev-charge-finder. See +# `custom-openapi/README.md` for how a file like this is wired in. Private +# Preview per docs.mapbox.com — access may be gated per-account on top of +# whatever OAuth scope this CLI's login obtains; not yet verified against +# production, unlike this session's other custom specs. See this file's own +# tests for what was and wasn't checked. +info: + title: "Mapbox EV Charge Finder API" + description: >- + EV charging stations near a point — searchable by connector type, + operator, charging power, availability, amenities and payment method — + plus the full detail (tariffs, opening hours) for one station and the + list of known charge-point operators. + version: "0.0.0" +servers: + - url: https://api.mapbox.com + description: EV Charge Finder API +paths: + /ev/v1/locations: + get: + operationId: search + summary: Charging stations near a point. + description: >- + A GeoJSON `FeatureCollection`, one feature per station, each + carrying an OCPI `location` object and a `proximity` object. + parameters: + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + - name: "latitude" + in: query + required: true + description: "Search center latitude, -90 to 90." + schema: + type: number + minimum: -90 + maximum: 90 + - name: "longitude" + in: query + required: true + description: "Search center longitude, -180 to 180." + schema: + type: number + minimum: -180 + maximum: 180 + - name: "distance" + in: query + required: true + description: "Search radius in km, up to 100. Defaults to 10." + schema: + type: number + minimum: 0 + maximum: 100 + - name: "limit" + in: query + required: false + description: "Maximum results, up to 100. Defaults to 20." + schema: + type: integer + minimum: 1 + maximum: 100 + # Prose rather than an `enum`: comma-delimited lists, and the + # command builder turns a spec `enum` into a clap + # `PossibleValuesParser`, which accepts one value and would refuse + # a list. Same reasoning as `directions.yaml`'s `annotations`. + - name: "connector_types" + in: query + required: false + # The docs link trails its own sentence, not the lead one: a + # period inside "docs.mapbox.com" is indistinguishable from a + # sentence break to `first_sentence` in `src/main.rs`, which cuts + # a `--help` line at the first `.` — this left `--help` showing + # "...comma-delimited — see docs" before the fix. + description: >- + Limit to one or more connector types, comma-delimited. See + docs.mapbox.com/api/navigation/ev-charge-finder for the full + list (CCS/CHAdeMO/Tesla/domestic-socket variants and more, + close to 40 values). + schema: + type: string + example: "IEC_62196_T2_COMBO,CHADEMO" + - name: "operators" + in: query + required: false + description: >- + Limit to one or more operator names, comma-delimited — see + `mapbox ev-charge-finder list-operators` for the account's own + list. + schema: + type: string + - name: "exclude_operators" + in: query + required: false + description: "Exclude one or more operator names, comma-delimited." + schema: + type: string + - name: "min_charging_power" + in: query + required: false + description: "Minimum power in watts. Defaults to 0." + schema: + type: number + minimum: 0 + - name: "max_charging_power" + in: query + required: false + description: "Maximum power in watts. Defaults to 500000." + schema: + type: number + minimum: 0 + - name: "availability" + in: query + required: false + description: >- + Limit to one or more EVSE statuses, comma-delimited. Options + are `AVAILABLE`, `CHARGING`, `BLOCKED`, `INOPERATIVE`, + `OUTOFORDER`, `PLANNED`, `REMOVED`, `RESERVED`, `UNKNOWN`. + schema: + type: string + - name: "amenities" + in: query + required: false + description: >- + Limit to stations near one or more amenities, comma-delimited. + See docs.mapbox.com/api/navigation/ev-charge-finder for the + full list (`restaurant`, `parking_lot`, `wifi`, and about 25 + more). + schema: + type: string + - name: "exclude" + in: query + required: false + description: >- + Exclude stations with an attribute, comma-delimited. The only + documented value is `tesla_exclusive`. + schema: + type: string + - name: "payment_methods" + in: query + required: false + description: >- + Limit to one or more payment methods, comma-delimited. The + only documented value is `ad-hoc`. + schema: + type: string + - name: "opening_times" + in: query + required: false + description: >- + Limit by opening hours. The only documented value is + `twentyfourseven`. + schema: + type: string + - name: "eta_type" + in: query + required: false + description: >- + Enable a route-based ETA to each station, from `--origin-latitude`/ + `--origin-longitude`. Adds latency and Matrix API cost on + Mapbox's side. The only allowed value is `navigation`. + schema: + type: string + enum: ["navigation"] + - name: "origin_latitude" + in: query + required: false + description: >- + Origin point latitude for the ETA, -90 to 90. Requires + `--eta-type navigation`. + schema: + type: number + minimum: -90 + maximum: 90 + - name: "origin_longitude" + in: query + required: false + description: >- + Origin point longitude for the ETA, -180 to 180. Requires + `--eta-type navigation`. + schema: + type: number + minimum: -180 + maximum: 180 + responses: + "200": + description: >- + A GeoJSON `FeatureCollection` — one feature per station, each + with an OCPI `location` and a `proximity` object in + `properties`. + "400": + description: >- + Bad Request — the feature isn't enabled for this account, or a + required parameter is missing. + "401": + description: Unauthorized + + /ev/v1/locations/{location_id}: + get: + operationId: get + summary: One charging station's full detail. + description: >- + The same feature `search` returns, plus `tariffs` — pricing for + each connector. + parameters: + - name: "location_id" + in: path + required: true + # "location.id" contains a "." too, the same trap `connector_types` + # and `amenities` above hit with "docs.mapbox.com" — reworded to + # keep the id reference after the first real sentence break. + description: "A station id, as returned by `search`. Its OCPI `location.id` field." + schema: + type: string + minLength: 1 + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + responses: + "200": + description: >- + A GeoJSON `Feature` with `location`, `proximity`, and + `tariffs` (an array of OCPI tariff objects) in `properties`. + "401": + description: Unauthorized + "404": + description: Not Found — no station with that id. + + /ev/v1/operators: + get: + operationId: listOperators + summary: Known charge-point operators. + description: >- + Every operator `search`/`get` results can name in `operators`, + for filtering `search` by `--operators`/`--exclude-operators`. + parameters: + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + responses: + "200": + description: >- + A JSON object with a `data` array of `{party_id, name, + country_code}` — `party_id` is a 3-letter OCPI code, + `country_code` ISO 3166-1 alpha-2. + "401": + description: Unauthorized diff --git a/custom-openapi/feedback/openapi/feedback.yaml b/custom-openapi/feedback/openapi/feedback.yaml new file mode 100644 index 0000000..64dcc57 --- /dev/null +++ b/custom-openapi/feedback/openapi/feedback.yaml @@ -0,0 +1,223 @@ +openapi: "3.0.0" +# `parse_spec` turns `info.description` below into this service's clap +# `long_about`, so it also reaches `mapbox feedback --help`, `--schema` and +# `generate-skills` output. Keep it to API prose only — the provenance below +# is for whoever edits this file, not for a CLI user: +# +# Hand-authored down to the parameters documented at +# docs.mapbox.com/api/feedback. See `custom-openapi/README.md` for how a +# file like this is wired in. `createFeedbackItem` is declared here but +# never reaches the command surface: it needs `user-feedback:write`, which +# `POST /oauth/register` silently drops from the granted scope — confirmed +# directly against production (`curl -X POST +# https://api.mapbox.com/oauth/register?scope=user-feedback:write ...` +# returns a registration with that scope missing from the response, the +# same shape `tokens:write` and `styles:download` already document in +# `src/spec.rs`'s `UNSUPPORTED_OPERATIONS`). No `mapbox auth login` token +# can ever carry it, so `feedback create` is not a command today. +info: + title: "Mapbox Feedback API" + description: >- + Feedback submitted against Mapbox API responses — geocoding, search, + directions and the rest — filterable, sortable, and paginated. + version: "0.0.0" +servers: + - url: https://api.mapbox.com + description: Feedback API +paths: + /user-feedback/v1/feedback: + get: + operationId: list + summary: List feedback items. + description: >- + Every feedback item on the account, newest received first by + default — filterable by id, status, category, a free-text search + phrase, trace id, or a time window on when it was created, + received, or last updated. Paginated with `--after`/`start_cursor`/ + `end_cursor` the way every other listing on this CLI is. + parameters: + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + # Prose rather than an `enum`: several of these are comma-separated + # lists, and the command builder turns a spec `enum` into a clap + # `PossibleValuesParser`, which accepts one value and would refuse + # a list. Same reasoning as `directions.yaml`'s `annotations`. + - name: "feedback_id" + in: query + required: false + description: "One or more feedback ids to filter by, comma-separated." + schema: + type: string + - name: "after" + in: query + required: false + description: "A cursor from a previous response's `end_cursor`, to page forward." + schema: + type: string + - name: "limit" + in: query + required: false + description: "Maximum items to return, up to 1000." + schema: + type: integer + minimum: 1 + maximum: 1000 + - name: "sort_by" + in: query + required: false + description: >- + Which timestamp to sort by. Defaults to `received_at`. + schema: + type: string + enum: ["received_at", "created_at", "updated_at"] + - name: "order" + in: query + required: false + description: "Sort direction. Defaults to `asc`." + schema: + type: string + enum: ["asc", "desc"] + - name: "status" + in: query + required: false + description: >- + Limit to one or more statuses, comma-separated. Options are + `received`, `fixed`, `reviewed`, `out_of_scope`. + schema: + type: string + example: "received,reviewed" + - name: "category" + in: query + required: false + description: >- + Limit to one or more feedback categories, comma-separated — + categories are account-specific, so there is no fixed list here. + schema: + type: string + - name: "search" + in: query + required: false + description: "A phrase to match against feedback text." + schema: + type: string + - name: "trace_id" + in: query + required: false + description: >- + Limit to one or more caller-provided trace ids, comma-separated. + schema: + type: string + # "ISO 8601" trails each of these six rather than leads them: + # `first_sentence` in `src/main.rs` cuts a `--help` line at the + # first `.`, and "ISO 8601." on its own left `--help` showing just + # that. `--schema` and `docs/commands.md` still show each in full. + - name: "created_before" + in: query + required: false + description: "Only items the caller created before this time, ISO 8601." + schema: + type: string + - name: "created_after" + in: query + required: false + description: "Only items the caller created after this time, ISO 8601." + schema: + type: string + - name: "received_before" + in: query + required: false + description: "Only items Mapbox received before this time, ISO 8601." + schema: + type: string + - name: "received_after" + in: query + required: false + description: "Only items Mapbox received after this time, ISO 8601." + schema: + type: string + - name: "updated_before" + in: query + required: false + description: "Only items last updated before this time, ISO 8601." + schema: + type: string + - name: "updated_after" + in: query + required: false + description: "Only items last updated after this time, ISO 8601." + schema: + type: string + responses: + "200": + description: >- + A JSON object with an `items` array (each a feedback item: `id`, + `status`, `category`, `feedback`, `location`, timestamps), plus + `has_after`/`end_cursor` and `has_before`/`start_cursor` for + paging either direction. + "401": + description: Unauthorized + "403": + description: Forbidden + + post: + # Not `create`: this operation is never reachable (see below), so the + # user-facing name doesn't matter — but the withheld-operation guard + # in `generate_skills.rs` checks the bare command word against every + # generated file's text, and a bare `create` collides with the + # exposed, unrelated `styles create`. A multi-word operationId, the + # same shape every other disabled operation already has + # (`createToken`, `downloadStyleZip`, …), sidesteps that. + operationId: createFeedbackItem + summary: Submit a new feedback item. + description: >- + Not a command — see this file's own header comment for why. + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + "201": + description: The created feedback item. + "401": + description: Unauthorized + "403": + description: Forbidden + + /user-feedback/v1/feedback/{feedback_id}: + get: + operationId: get + summary: Retrieve one feedback item by id. + parameters: + - name: "feedback_id" + in: path + required: true + description: "The feedback item's id." + schema: + type: string + minLength: 1 + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + responses: + "200": + description: >- + The feedback item: `id`, `status`, `category`, `feedback`, + `location` (`place_name`, `lon`, `lat`), and its + created/received/updated timestamps. + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found — no feedback item with that id. diff --git a/custom-openapi/isochrone/openapi/isochrone.yaml b/custom-openapi/isochrone/openapi/isochrone.yaml new file mode 100644 index 0000000..538a9b0 --- /dev/null +++ b/custom-openapi/isochrone/openapi/isochrone.yaml @@ -0,0 +1,164 @@ +openapi: "3.0.0" +# `parse_spec` turns `info.description` below into this service's clap +# `long_about`, so it also reaches `mapbox isochrone --help`, `--schema` and +# `generate-skills` output. Keep it to API prose only — the provenance below +# is for whoever edits this file, not for a CLI user: +# +# Hand-authored down to the parameters documented at +# docs.mapbox.com/api/navigation/isochrone. See `custom-openapi/README.md` +# for how a file like this is wired in, and `directions.yaml`'s header for +# why `profile` needs `ARG_NAME_OVERRIDES` in `src/spec.rs` — the same +# reason applies here. +info: + title: "Mapbox Isochrone API" + description: >- + How far you can get from a point in a given time or distance, for + driving (with or without live traffic), walking, or cycling — as a + GeoJSON polygon or line per contour. + version: "0.0.0" +servers: + - url: https://api.mapbox.com + description: Isochrone API +paths: + /isochrone/v1/{profile}/{coordinates}: + get: + operationId: contours + summary: Isochrone contours around one point. + description: >- + Returns one contour per value in `--contours-minutes` or + `--contours-meters` (exactly one of the two is required; not + enforced before the request goes out — the API answers 422 if both + or neither are given), as GeoJSON linestrings or, with `--polygons`, + polygons. + parameters: + - name: "profile" + in: path + required: true + description: >- + The routing profile. `mapbox/driving-traffic` accounts for live + traffic conditions; `mapbox/driving` does not. + schema: + type: string + enum: + [ + "mapbox/driving-traffic", + "mapbox/driving", + "mapbox/walking", + "mapbox/cycling", + ] + example: "mapbox/driving" + - name: "coordinates" + in: path + required: true + description: "The isochrone center, `{longitude},{latitude}`." + schema: + type: string + minLength: 1 + example: "-122.42,37.78" + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + # Prose rather than an `enum`: up to 4 comma-separated integers, and + # the command builder turns a spec `enum` into a clap + # `PossibleValuesParser`, which accepts one value and would refuse a + # list. Same reasoning as `directions.yaml`'s `annotations`. + - name: "contours_minutes" + in: query + required: false + description: >- + Up to 4 times in minutes, 1-60, comma-separated and increasing — + one contour per value. Exactly one of this or + `--contours-meters` is required. + schema: + type: string + example: "5,10,15,20" + - name: "contours_meters" + in: query + required: false + description: >- + Up to 4 distances in meters, 1-100000, comma-separated and + increasing — one contour per value. Exactly one of this or + `--contours-minutes` is required. + schema: + type: string + example: "1000,5000" + - name: "contours_colors" + in: query + required: false + description: >- + A hex color per contour (no `#`), comma-separated — must match + the contour count. + schema: + type: string + example: "ff0000,00ff00" + - name: "polygons" + in: query + required: false + description: >- + Return each contour as a GeoJSON polygon instead of a + linestring. + schema: + type: boolean + - name: "denoise" + in: query + required: false + # A number before the range, not after: `first_sentence` in + # `src/main.rs` cuts a `--help` line at the first `.`, and + # `0.0-1.0:` on its own left `--help` showing just `0`. + # `--schema` and `docs/commands.md` still show it in full. + description: >- + A smaller value removes more of the smaller contours, 0.0-1.0, + defaulting to 1.0. + schema: + type: number + minimum: 0 + maximum: 1 + - name: "generalize" + in: query + required: false + description: >- + Douglas-Peucker simplification tolerance in meters. A higher + value is a coarser, smaller contour. + schema: + type: number + # Prose rather than an `enum`, for the reason given on + # `contours_minutes` above. + - name: "exclude" + in: query + required: false + description: >- + Road types to route around, comma-separated. Options are + `motorway`, `toll`, `ferry`, `unpaved`, `cash_only_tolls`. + schema: + type: string + example: "motorway,toll" + - name: "depart_at" + in: query + required: false + description: >- + Departure time, ISO 8601 — for `mapbox/driving-traffic`, which + live traffic conditions to route against. + schema: + type: string + responses: + "200": + description: >- + A GeoJSON `FeatureCollection`, one feature per contour, each + carrying `contour` (the minute or meter value), `metric` + (`time` or `distance`), and rendering hints (`color`, + `opacity`, and — with `--polygons` — `fill`/`fill-opacity`). + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found — an invalid profile. + "422": + description: >- + Unprocessable Entity — invalid coordinates, an out-of-range + contour value, or neither/both of `contours_minutes` and + `contours_meters` given. diff --git a/custom-openapi/map-matching/openapi/map-matching.yaml b/custom-openapi/map-matching/openapi/map-matching.yaml new file mode 100644 index 0000000..3460e8a --- /dev/null +++ b/custom-openapi/map-matching/openapi/map-matching.yaml @@ -0,0 +1,268 @@ +openapi: "3.0.0" +# `parse_spec` turns `info.description` below into this service's clap +# `long_about`, so it also reaches `mapbox map-matching --help`, `--schema` +# and `generate-skills` output. Keep it to API prose only — the provenance +# below is for whoever edits this file, not for a CLI user: +# +# Hand-authored down to the parameters documented at +# docs.mapbox.com/api/navigation/map-matching. See `custom-openapi/README.md` +# for how a file like this is wired in, and `directions.yaml`'s header for +# why `profile` needs `ARG_NAME_OVERRIDES` in `src/spec.rs` — the same +# reason applies here. Excludes POST, for the same reason `directions.yaml` +# does: this spec format has no way to say "GET or POST, caller's choice" +# for one operationId. The API's own POST is for a request too long for a +# URL (~8100 bytes) — a real gap for a very long trace, not a design choice. +info: + title: "Mapbox Map Matching API" + description: >- + Snaps a noisy GPS trace to the road network and returns the route it + most likely followed, for driving (with or without live traffic), + walking, or cycling. + version: "0.0.0" +servers: + - url: https://api.mapbox.com + description: Map Matching API +paths: + /matching/v5/{profile}/{coordinates}.json: + get: + operationId: match + summary: Match a GPS trace to the road network. + description: >- + Returns one or more matched routes (more than one where the trace is + ambiguous enough to split), each with a `confidence` the API assigns + itself, plus one tracepoint per input coordinate — `null` for a + coordinate too far from any candidate to match at all. + parameters: + - name: "profile" + in: path + required: true + description: >- + The routing profile. `mapbox/driving-traffic` accounts for live + traffic conditions; `mapbox/driving` does not. + schema: + type: string + enum: + [ + "mapbox/driving-traffic", + "mapbox/driving", + "mapbox/walking", + "mapbox/cycling", + ] + example: "mapbox/driving" + - name: "coordinates" + in: path + required: true + description: >- + 2-100 trace points, semicolon-separated, each + `{longitude},{latitude}` — or an OpenLR-encoded string of up to + 50 points, in which case use `--openlr-spec`/`--openlr-format` + to say which flavor. + schema: + type: string + minLength: 1 + example: "-122.42,37.78;-122.421,37.781;-122.422,37.782" + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + # Prose rather than an `enum`: this is a comma-separated list, and + # the command builder turns a spec `enum` into a clap + # `PossibleValuesParser`, which accepts one value and would refuse + # `distance,duration`. Same reasoning as `directions.yaml`'s + # `annotations`. + - name: "annotations" + in: query + required: false + description: >- + Segment-level metadata to add to each leg, comma-separated. + Requires `--overview full`. Options are `distance`, `duration`, + `speed`, `congestion`, `congestion_numeric`, `maxspeed`. + schema: + type: string + example: "duration,distance" + - name: "approaches" + in: query + required: false + description: >- + Which side of the road to approach each waypoint from, + semicolon-separated — `unrestricted` or `curb` per coordinate. + Requires `--steps`. + schema: + type: string + - name: "geometries" + in: query + required: false + description: "The route geometry's format. Defaults to `polyline`." + schema: + type: string + enum: ["geojson", "polyline", "polyline6"] + - name: "overview" + in: query + required: false + description: >- + How much geometry detail the response carries. Defaults to + `simplified`. + schema: + type: string + enum: ["full", "simplified", "false"] + - name: "radiuses" + in: query + required: false + description: >- + Maximum distance in meters, 0-50, a coordinate may snap to the + road network, semicolon-separated, one per coordinate. Defaults + to 5. + schema: + type: string + - name: "steps" + in: query + required: false + description: >- + Return turn-by-turn instructions. Several other parameters + (`approaches`, `banner_instructions`, `language`, + `roundabout_exits`, `voice_instructions`) only take effect when + this is set. + schema: + type: boolean + - name: "banner_instructions" + in: query + required: false + description: "Return banner objects for display. Requires `--steps`." + schema: + type: boolean + - name: "language" + in: query + required: false + description: >- + The language turn-by-turn instructions are written in. Defaults + to `en`. Requires `--steps`. + schema: + type: string + example: "en" + - name: "roundabout_exits" + in: query + required: false + description: >- + Emit a separate instruction for entering and exiting a + roundabout, rather than one instruction for the whole + maneuver. Requires `--steps`. + schema: + type: boolean + - name: "voice_instructions" + in: query + required: false + description: >- + Return SSML-marked-up voice guidance text. Requires `--steps`. + schema: + type: boolean + - name: "voice_units" + in: query + required: false + description: >- + Units for voice instructions. Requires `--steps` and + `--voice-instructions`. + schema: + type: string + enum: ["imperial", "british_imperial", "metric"] + - name: "tidy" + in: query + required: false + description: >- + Remove clusters and resample the trace before matching — useful + for a trace recorded at an inconsistent sample rate. + schema: + type: boolean + - name: "timestamps" + in: query + required: false + description: >- + A Unix timestamp per coordinate, semicolon-separated and + ascending — when the trace was actually recorded, rather than + assumed from even spacing. + schema: + type: string + - name: "waypoint_names" + in: query + required: false + description: >- + A name per waypoint, semicolon-separated, used in that + waypoint's arrival instruction instead of the road name. Up to + 500 characters total. Requires `--steps`. + schema: + type: string + - name: "waypoints" + in: query + required: false + description: >- + Zero-based indices into `coordinates` marking which ones get + their own arrival instruction — must include `0` and the last + index. Requires `--steps`. + schema: + type: string + example: "0,2" + # Prose rather than an `enum`, for the reason given on `annotations` + # above. + - name: "ignore" + in: query + required: false + description: >- + Restrictions to ignore while matching, comma-separated. + Options are `access`, `oneways`, `restrictions`. + `mapbox/driving` only. + schema: + type: string + - name: "linear_references" + in: query + required: false + description: >- + Return an OpenLR reference (base64) per matched leg, alongside + the ordinary geometry. + schema: + type: boolean + - name: "openlr_spec" + in: query + required: false + description: >- + Which OpenLR specification `coordinates` is encoded with, when + it is an OpenLR string rather than a coordinate list. Defaults + to `tomtom`. + schema: + type: string + enum: ["tomtom", "here"] + - name: "openlr_format" + in: query + required: false + description: >- + The OpenLR binary format `coordinates` is encoded in, when it + is an OpenLR string. Only `tomtom` exists today. + schema: + type: string + enum: ["tomtom"] + - name: "depart_at" + in: query + required: false + description: >- + Departure time, ISO 8601 — for `mapbox/driving-traffic`, which + live traffic conditions to route against. + schema: + type: string + responses: + "200": + description: >- + A JSON object with a `code`, a `matchings` array (each with + `confidence`, `distance`, `duration`, `geometry`, and `legs`), + and a `tracepoints` array — one entry per input coordinate, + `null` for one too far from any candidate to match. + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found — an invalid profile. + "422": + description: >- + Unprocessable Entity — invalid input, or more coordinates than + the profile allows (100 for a coordinate list, 50 for OpenLR). diff --git a/custom-openapi/matrix/openapi/matrix.yaml b/custom-openapi/matrix/openapi/matrix.yaml new file mode 100644 index 0000000..d23ebe7 --- /dev/null +++ b/custom-openapi/matrix/openapi/matrix.yaml @@ -0,0 +1,160 @@ +openapi: "3.0.0" +# `parse_spec` turns `info.description` below into this service's clap +# `long_about`, so it also reaches `mapbox matrix --help`, `--schema` and +# `generate-skills` output. Keep it to API prose only — the provenance below +# is for whoever edits this file, not for a CLI user: +# +# Hand-authored down to the parameters documented at +# docs.mapbox.com/api/navigation/matrix. See `custom-openapi/README.md` for +# how a file like this is wired in, and `directions.yaml`'s header for why +# `profile` needs `ARG_NAME_OVERRIDES` in `src/spec.rs` — the same reason +# applies here. +info: + title: "Mapbox Matrix API" + description: >- + Travel time and distance between every pair in a set of up to 25 + coordinates, in one call — for driving (with or without live traffic), + walking, or cycling. + version: "0.0.0" +servers: + - url: https://api.mapbox.com + description: Matrix API +paths: + /directions-matrix/v1/{profile}/{coordinates}: + get: + operationId: compute + summary: A travel time/distance matrix across a set of coordinates. + description: >- + Returns a `durations` and/or `distances` matrix in row-major order — + `durations[i][j]` is the time from the ith source to the jth + destination — across every source/destination pair. Defaults to + every coordinate as both a source and a destination (a full N×N + matrix); `--sources`/`--destinations` narrow either side to a + subset. Answers "which of these is reachable soonest", not a route + through all of them — see `directions route` for a route through + fixed stops in order, or the future Optimization API command for + the best order to visit them in. + parameters: + - name: "profile" + in: path + required: true + description: >- + The routing profile. `mapbox/driving-traffic` accounts for live + traffic conditions and caps at 10 coordinates instead of 25; + `mapbox/driving` does not. + schema: + type: string + enum: + [ + "mapbox/driving-traffic", + "mapbox/driving", + "mapbox/walking", + "mapbox/cycling", + ] + example: "mapbox/driving" + - name: "coordinates" + in: path + required: true + description: >- + 2-25 coordinates, semicolon-separated, each + `{longitude},{latitude}` — 10 max for `mapbox/driving-traffic`. + schema: + type: string + minLength: 1 + example: "-122.42,37.78;-122.45,37.91;-122.41,37.80" + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + - name: "annotations" + in: query + required: false + description: >- + Which matrix or matrices to return, comma-separated. Options + are `duration` (the default) and `distance` — both together + returns both matrices. + schema: + type: string + example: "duration,distance" + - name: "approaches" + in: query + required: false + description: >- + Which side of the road to approach each coordinate from, + semicolon-separated — `unrestricted` or `curb` per coordinate. + schema: + type: string + - name: "bearings" + in: query + required: false + description: >- + `{angle},{degrees}` per coordinate, semicolon-separated, + filtering the road segments considered by direction of travel. + schema: + type: string + - name: "sources" + in: query + required: false + description: >- + Which coordinates act as sources (matrix rows) — `all` + (the default), or zero-based indices, semicolon-separated. + Verified against production: a comma-separated list is a 422, + "may be \"all\" or semicolon-separated list of 0-based integer + indices" — unlike every other index/value list on this CLI's + Navigation commands, which are comma- or semicolon-separated + per parameter but never comma where this API wants semicolons. + schema: + type: string + example: "0;2" + - name: "destinations" + in: query + required: false + description: >- + Which coordinates act as destinations (matrix columns) — `all` + (the default), or zero-based indices, semicolon-separated. See + `sources` above — comma-separated is a 422 here specifically. + schema: + type: string + example: "1;3" + - name: "fallback_speed" + in: query + required: false + # Content before "Legacy", not after: `first_sentence` in + # `src/main.rs` cuts a `--help` line at the first `.`, and + # "Legacy." on its own left `--help` showing just that word. + # `--schema` and `docs/commands.md` still show it in full. + description: >- + Replaces a `null` (unreachable) cell with a straight-line + estimate at this speed, km/h, rather than leaving it `null`. + Legacy. + schema: + type: integer + minimum: 1 + - name: "depart_at" + in: query + required: false + description: >- + Departure time, ISO 8601, for future traffic conditions and + time-dependent road restrictions. + schema: + type: string + responses: + "200": + description: >- + A JSON object with a `code`, a `durations` and/or `distances` + matrix (row-major, seconds and meters respectively — `null` for + an unreachable pair), and the snapped `sources`/`destinations` + waypoints. + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found — an invalid profile. + "422": + description: >- + Unprocessable Entity — invalid input, or more coordinates than + the profile allows. diff --git a/docs/commands.md b/docs/commands.md index 899fdfa..ca6a79b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,6 +1,6 @@ # Implemented commands -Every command the CLI ships: four auth commands, 33 API operations across 10 +Every command the CLI ships: four auth commands, 40 API operations across 14 command groups, the tilesets-cli proxy, `completion` and `generate-skills`. Each is shown in both of its renderings. Which one you get is decided by `--output`, whose default (`auto`) reads stdout: a terminal gets the left column, a pipe or redirect @@ -10,13 +10,16 @@ gets the right one. See Account names, style ids and tokens in the examples are replaced; everything else is as the API sent it. -**29 of the 33 were run against the live API and show what came back:** 26 -on 2026-09-01, and `fonts list`, `fonts upload` and `fonts delete` on -2026-09-08, once `fonts:list`/`fonts:write` became -registrable. The write operations were exercised as round trips on -throwaway objects — a style created, updated, drafted and deleted; icons -uploaded to a sprite and taken out again; a font uploaded and deleted — -leaving the account as it was found. +**33 of the 40 were run against the live API and show what came back:** +`directions route`, `isochrone contours`, `map-matching match`, `matrix +compute`, `feedback list` and `feedback get` on 2026-09-24, once those +command groups existed at all, and the rest earlier — `fonts list`, +`fonts upload` and `fonts delete` on 2026-09-08 once +`fonts:list`/`fonts:write` became registrable, the remainder before that. +The write operations were exercised as round trips on throwaway objects — +a style created, updated, drafted and deleted; icons uploaded to a sprite +and taken out again; a font uploaded and deleted — leaving the account as +it was found. Every API command's **Outputs** block below is that snapshot rather than a live reading, and is re-taken by hand — nothing schedules it and nothing @@ -29,10 +32,14 @@ commands and the flags they take — is held to `mapbox --schema` on every `cargo test` run by `tests/docs_contract.rs`, so the half of this page that can be checked cannot fall behind the binary. -The remaining 4 give the response shape from the spec or the docs instead -of a live capture: they're all `search`'s — read-only and safe to run, but -the credentials used to write this page have no Search Box API access, so -every call answers 401 rather than a result. +The remaining 7 give the response shape from the spec or the docs instead +of a live capture. Four are `search`'s — read-only and safe to run, but the +credentials used to write this page have no Search Box API access, so +every call answers 401 rather than a result. The other three are +`ev-charge-finder`'s: that API is Private Preview, and this account isn't +enrolled — confirmed directly (a plain `curl` gets `401 invalid access +token` there and `200` on every other service with the same token), not +assumed. Each **Parameters** section lists only what is specific to its command. The globals every API command takes are @@ -74,6 +81,14 @@ nests, and is typed `mapbox styles draft get`. **[Directions](#directions)** — [directions.route](#mapbox-directions-route) +**[EV Charge Finder](#ev-charge-finder)** — +[ev-charge-finder.search](#mapbox-ev-charge-finder-search) · +[ev-charge-finder.get](#mapbox-ev-charge-finder-get) · +[ev-charge-finder.list-operators](#mapbox-ev-charge-finder-list-operators) + +**[Feedback](#feedback)** — [feedback.list](#mapbox-feedback-list) · +[feedback.get](#mapbox-feedback-get) + **[Fonts](#fonts)** — [fonts.list](#mapbox-fonts-list) · [fonts.upload](#mapbox-fonts-upload) · [fonts.delete](#mapbox-fonts-delete) @@ -82,6 +97,14 @@ nests, and is typed `mapbox styles draft get`. [geocoder.reverse](#mapbox-geocoder-reverse) · [geocoder.batch](#mapbox-geocoder-batch) +**[Isochrone](#isochrone)** — +[isochrone.contours](#mapbox-isochrone-contours) + +**[Map Matching](#map-matching)** — +[map-matching.match](#mapbox-map-matching-match) + +**[Matrix](#matrix)** — [matrix.compute](#mapbox-matrix-compute) + **[Search](#search)** — [search.forward](#mapbox-search-forward) · [search.reverse](#mapbox-search-reverse) · [search.category](#mapbox-search-category) · @@ -930,6 +953,285 @@ Both trimmed to one leg for length — the real response also carries `admins` (administrative boundaries traversed) and `notifications` (three tunnel alerts, on this particular route) per leg. +--- +## EV Charge Finder + +EV charging stations near a point — searchable by connector type, +operator, charging power, availability, amenities and payment method — +plus the full detail (tariffs, opening hours) for one station and the list +of known charge-point operators. Curated by hand down to the parameters +documented at docs.mapbox.com/api/navigation/ev-charge-finder — see +`custom-openapi/README.md` for why this command group doesn't come from +the vendored specs the way most others do. + +**Private Preview, and not verified live.** Every other command group this +session shipped was checked against a real response; this one wasn't — +the credentials used to write this page get `401 invalid access token` on +all three operations here, and the same token succeeds immediately against +every other service (confirmed directly, not assumed: a plain `curl` to +this API and to `isochrone` side by side, one 401 and one 200). That +reads as this account not being enrolled in the Private Preview, a +different kind of gate than an OAuth scope — nothing in this CLI's own +token handling can get around it. The commands below are built faithfully +to the documented request/response shape; treat the **Outputs** blocks as +what the docs say the shape is, not a captured response. + +### `mapbox ev-charge-finder search` + +Charging stations near a point, as GeoJSON — one feature per station, each +carrying an OCPI `location` object and a `proximity` object. + +#### Parameters + +`--latitude`, `--longitude` and `--distance` are required. + +| Parameter | Effect | +| --- | --- | +| `--latitude ` / `--longitude ` | Search center. | +| `--distance ` | Search radius, up to 100. Defaults to 10. | +| `--limit ` | Maximum results, up to 100. Defaults to 20. | +| `--connector-types ` | One or more connector types, comma-delimited — see the docs for the full list (close to 40 values). | +| `--operators ` | One or more operator names, comma-delimited — see `list-operators` for the account's own list. | +| `--exclude-operators ` | Exclude one or more operator names, comma-delimited. | +| `--min-charging-power` / `--max-charging-power ` | Power range. Defaults 0 / 500000. | +| `--availability ` | One or more EVSE statuses, comma-delimited (`AVAILABLE`, `CHARGING`, `BLOCKED`, `INOPERATIVE`, `OUTOFORDER`, `PLANNED`, `REMOVED`, `RESERVED`, `UNKNOWN`). | +| `--amenities ` | One or more nearby amenities, comma-delimited — see the docs for the full list (about 28 values). | +| `--exclude ` | Exclude an attribute. Only `tesla_exclusive` is documented. | +| `--payment-methods ` | Only `ad-hoc` is documented. | +| `--opening-times ` | Only `twentyfourseven` is documented. | +| `--eta-type navigation` + `--origin-latitude`/`--origin-longitude` | Add a route-based ETA to each station. Adds latency and Matrix API cost on Mapbox's side. | + +#### Examples + +```sh +mapbox ev-charge-finder search --latitude 37.78 --longitude -122.42 --distance 10 +mapbox ev-charge-finder search --latitude 37.78 --longitude -122.42 --distance 25 \ + --connector-types IEC_62196_T2_COMBO,CHADEMO --min-charging-power 50000 +``` + +#### Outputs + +Not captured live — see this section's own note above. Per the docs, a +GeoJSON `FeatureCollection`: + +```json +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { "type": "Point", "coordinates": ["…"] }, + "properties": { + "location": "{ OCPI location object }", + "proximity": "{ distance from the search point }" + } + } + ] +} +``` + +### `mapbox ev-charge-finder get` + +One charging station's full detail — the same feature `search` returns, +plus `tariffs`. + +#### Parameters + +`` (positional) is required — a station id, as returned by +`search`. + +#### Examples + +```sh +mapbox ev-charge-finder get +``` + +#### Outputs + +Not captured live — see this section's own note above. Per the docs, a +GeoJSON `Feature` with `tariffs` alongside `location`/`proximity`: + +```json +{ + "type": "Feature", + "geometry": { "type": "Point", "coordinates": ["…"] }, + "properties": { + "location": "{ OCPI location object }", + "proximity": "{ distance from the search point }", + "tariffs": ["{ OCPI tariff objects }"] + } +} +``` + +### `mapbox ev-charge-finder list-operators` + +Every charge-point operator this account's `search`/`get` results can +name — for filtering `search` by `--operators`/`--exclude-operators`. + +#### Parameters + +None. + +#### Examples + +```sh +mapbox ev-charge-finder list-operators +``` + +#### Outputs + +Not captured live — see this section's own note above. Per the docs: + +```json +{ + "data": [ + { "party_id": "ABC", "name": "Example Networks", "country_code": "US" } + ] +} +``` + +--- +## Feedback + +Feedback submitted against Mapbox API responses — geocoding, search, +directions and the rest — filterable, sortable, and paginated. Curated by +hand down to the parameters documented at docs.mapbox.com/api/feedback — +see `custom-openapi/README.md` for why this command group doesn't come from +the vendored specs the way most others do. + +**`feedback create`, the write side of this API, is not a command.** It +needs a `user-feedback:write` scope that `POST /oauth/register` silently +drops from the granted set — confirmed directly against production, the +same shape `accounts create-token` and `styles download-style-zip` already +document. No `mapbox auth login` token can ever carry it. + +### `mapbox feedback list` + +Every feedback item on the account, newest received first by default. + +#### Parameters + +| Parameter | Effect | +| --- | --- | +| `--feedback-id ` | One or more feedback ids, comma-separated. | +| `--after ` | Page forward from a previous response's `end_cursor`. | +| `--limit ` | Maximum items to return, up to 1000. | +| `--sort-by ` | Which timestamp to sort by. Defaults to `received_at`. | +| `--order ` | Sort direction. Defaults to `asc`. | +| `--status ` | One or more of `received`, `fixed`, `reviewed`, `out_of_scope`, comma-separated. | +| `--category ` | One or more feedback categories, comma-separated — account-specific, no fixed list. | +| `--search ` | A phrase to match against feedback text. | +| `--trace-id ` | One or more caller-provided trace ids, comma-separated. | +| `--created-before` / `--created-after ` | Window on when the caller created the item. | +| `--received-before` / `--received-after ` | Window on when Mapbox received it. | +| `--updated-before` / `--updated-after ` | Window on when it was last updated. | + +#### Examples + +```sh +mapbox feedback list --limit 5 +mapbox feedback list --status received --category positioning_issue +``` + +#### Outputs + +Captured live, two items: + + + + +
Terminal — -o textAgent — -o json
+ +```json +{ + "items": [ + { + "id": "01a06d61-17e4-74aa-b824-13baaf272670", + "status": "received", + "category": "positioning_issue", + "feedback": "This is a test feedback. …", + "location": { "lat": 0, "lon": 0 }, + "received_at": "2026-09-04T17:04:34.818Z" + }, + { + "id": "01a06d61-77cc-7649-8db6-5beb2de0278d", + "status": "received", + "category": "application_issue", + "feedback": "This is a test feedback. …", + "location": { + "lat": 37.779238, + "lon": -122.419359, + "place_name": "400 Van Ness Avenue, San Francisco, California 94103, United States" + }, + "received_at": "2026-09-04T17:04:59.466Z" + } + ], + "has_after": true, + "has_before": false, + "start_cursor": "…", + "end_cursor": "…" +} +``` + + + +```json +{"items":[{"id":"01a06d61-17e4-74aa-b824-13baaf272670","status":"received","category":"positioning_issue","feedback":"This is a test feedback. …","location":{"lat":0,"lon":0},"received_at":"2026-09-04T17:04:34.818Z"},{"id":"01a06d61-77cc-7649-8db6-5beb2de0278d","status":"received","category":"application_issue","feedback":"This is a test feedback. …","location":{"lat":37.779238,"lon":-122.419359,"place_name":"400 Van Ness Avenue, San Francisco, California 94103, United States"},"received_at":"2026-09-04T17:04:59.466Z"}],"has_after":true,"has_before":false,"start_cursor":"…","end_cursor":"…"} +``` + +
+ +Neither output mode has a bespoke rendering for this response — it isn't +GeoJSON — so both print the same JSON, `-o text` pretty-printed and `-o +json` on one line. Feedback text trimmed and `created_at`/`updated_at`/ +`has_screenshot` dropped per item, for length; the real response carries +them too. + +### `mapbox feedback get` + +One feedback item by id. + +#### Parameters + +`` (positional) is required. + +#### Examples + +```sh +mapbox feedback get 01a06d61-17e4-74aa-b824-13baaf272670 +``` + +#### Outputs + +Captured live, the same item `list` returned above — a single object this +time, not wrapped in `items`: + + + + +
Terminal — -o textAgent — -o json
+ +```json +{ + "id": "01a06d61-17e4-74aa-b824-13baaf272670", + "status": "received", + "category": "positioning_issue", + "feedback": "This is a test feedback. …", + "location": { "lat": 0, "lon": 0 }, + "received_at": "2026-09-04T17:04:34.818Z" +} +``` + + + +```json +{"id":"01a06d61-17e4-74aa-b824-13baaf272670","status":"received","category":"positioning_issue","feedback":"This is a test feedback. …","location":{"lat":0,"lon":0},"received_at":"2026-09-04T17:04:34.818Z"} +``` + +
+ +Same trimming as `list` above. + --- ## Fonts @@ -1232,6 +1534,309 @@ A query malformed enough that its own list can't be built falls the whole batch back to pretty-printed JSON, same as one broken feature does for a single query. +--- +## Isochrone + +How far you can get from a point in a given time or distance, for driving +(with or without live traffic), walking, or cycling. Curated by hand down to +the parameters documented at docs.mapbox.com/api/navigation/isochrone — see +`custom-openapi/README.md` for why this command group doesn't come from the +vendored specs the way most others do. + +### `mapbox isochrone contours` + +One contour per value in `--contours-minutes` or `--contours-meters`, as +GeoJSON around the given center point. + +#### Parameters + +`` and `` (both positional) are required. +`` is one of `mapbox/driving-traffic`, `mapbox/driving`, +`mapbox/walking`, `mapbox/cycling`. `` is one +`{longitude},{latitude}` pair — unlike `directions route`, this command +takes a single center point, not a list of waypoints. + +Exactly one of `--contours-minutes` or `--contours-meters` is required by +the API, though nothing here enforces it before the request goes out. + +| Parameter | Effect | +| --- | --- | +| `--contours-minutes ` | Up to 4 times in minutes, 1-60, comma-separated and increasing. One contour per value. | +| `--contours-meters ` | Up to 4 distances in meters, 1-100000, comma-separated and increasing. One contour per value. | +| `--contours-colors ` | A hex color per contour (no `#`), comma-separated — must match the contour count. | +| `--polygons` | Return each contour as a GeoJSON polygon instead of a linestring. | +| `--denoise <0.0-1.0>` | A smaller value removes more of the smaller contours. Defaults to 1.0. | +| `--generalize ` | Douglas-Peucker simplification tolerance — a higher value is a coarser, smaller contour. | +| `--exclude ` | Road types to route around, comma-separated (`motorway`, `toll`, `ferry`, `unpaved`, `cash_only_tolls`). | +| `--depart-at ` | For `mapbox/driving-traffic`, which live traffic conditions to route against. | + +#### Examples + +```sh +mapbox isochrone contours mapbox/driving "-122.42,37.78" --contours-minutes 5,10,15 +mapbox isochrone contours mapbox/walking "-122.42,37.78" --contours-minutes 5,10 --polygons +``` + +#### Outputs + +Captured live against `mapbox/walking`, two 5- and 10-minute contours as +polygons. This response is a real GeoJSON `FeatureCollection` — unlike +`directions route`'s response — but isochrone isn't one of the three +services (`search`, `geocoder`, `tilequery`) this CLI has a bespoke +list-per-feature rendering for yet (`output.rs`'s `list_rendering` is an +exact service allow-list, not a "looks like GeoJSON" test), so both output +modes print the same JSON, `-o text` pretty-printed and `-o json` on one +line — same shape as `directions route`'s Outputs section above: + + + + +
Terminal — -o textAgent — -o json
+ +```json +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "color": "#bf4040", + "contour": 10, + "fill": "#bf4040", + "fill-opacity": 0.33, + "fillColor": "#bf4040", + "fillOpacity": 0.33, + "metric": "time", + "opacity": 0.33 + }, + "geometry": { "type": "Polygon", "coordinates": "…" } + } + ] +} +``` + + + +```json +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"color":"#bf4040","contour":10,"fill":"#bf4040","fill-opacity":0.33,"fillColor":"#bf4040","fillOpacity":0.33,"metric":"time","opacity":0.33},"geometry":{"type":"Polygon","coordinates":"…"}}]} +``` + +
+ +Trimmed to one of the two features (the response has one per +`--contours-minutes` value) and the polygon's coordinates, for length. + +--- +## Map Matching + +Snaps a noisy GPS trace to the road network and returns the route it most +likely followed, for driving (with or without live traffic), walking, or +cycling. Curated by hand down to the parameters documented at +docs.mapbox.com/api/navigation/map-matching — see `custom-openapi/README.md` +for why this command group doesn't come from the vendored specs the way +most others do. Excludes POST, which this CLI's spec format has no way to +express alongside GET for the same operation — the API's own POST is for a +trace too long for a URL (~8100 bytes), a real gap rather than a design +choice. + +### `mapbox map-matching match` + +One or more matched routes — more than one where the trace is ambiguous +enough to split — each carrying a `confidence` the API assigns itself, plus +one tracepoint per input coordinate (`null` for one too far from any +candidate to match at all). + +#### Parameters + +`` and `` (both positional) are required. +`` is one of `mapbox/driving-traffic`, `mapbox/driving`, +`mapbox/walking`, `mapbox/cycling`. `` is 2-100 +`{longitude},{latitude}` trace points, semicolon-separated — or an +OpenLR-encoded string of up to 50 points (pair with `--openlr-spec`/ +`--openlr-format`). + +| Parameter | Effect | +| --- | --- | +| `--annotations ` | Segment-level metadata per leg, comma-separated (`distance`, `duration`, `speed`, `congestion`, `congestion_numeric`, `maxspeed`). Requires `--overview full`. | +| `--approaches ` | Which side of the road to approach each waypoint from. Requires `--steps`. | +| `--geometries ` | Route geometry format. Defaults to `polyline`. | +| `--overview ` | Geometry detail level. Defaults to `simplified`. | +| `--radiuses ` | Max snap distance, 0-50, one per coordinate. Defaults to 5. | +| `--steps` | Return turn-by-turn instructions. Several flags below only take effect with this set. | +| `--banner-instructions` | Return banner objects for display. Requires `--steps`. | +| `--language ` | Instruction language. Defaults to `en`. Requires `--steps`. | +| `--roundabout-exits` | Separate entry/exit instructions for a roundabout. Requires `--steps`. | +| `--voice-instructions` | Return SSML-marked voice guidance. Requires `--steps`. | +| `--voice-units ` | Requires `--steps` and `--voice-instructions`. | +| `--tidy` | Remove clusters and resample the trace before matching — for a trace recorded at an inconsistent sample rate. | +| `--timestamps ` | When the trace was recorded, per coordinate, ascending — rather than assumed from even spacing. | +| `--waypoint-names ` | A name per waypoint for its arrival instruction. Requires `--steps`. | +| `--waypoints ` | Which coordinates get their own arrival instruction — must include `0` and the last index. Requires `--steps`. | +| `--ignore ` | Restrictions to ignore, comma-separated (`access`, `oneways`, `restrictions`). `mapbox/driving` only. | +| `--linear-references` | Return an OpenLR reference (base64) per matched leg, alongside the ordinary geometry. | +| `--openlr-spec ` | Which OpenLR spec `coordinates` is encoded with, if it's an OpenLR string. Defaults to `tomtom`. | +| `--openlr-format tomtom` | The OpenLR binary format `coordinates` is encoded in, if it's an OpenLR string. | +| `--depart-at ` | For `mapbox/driving-traffic`, which live traffic conditions to route against. | + +#### Examples + +```sh +mapbox map-matching match mapbox/driving "-122.42,37.78;-122.421,37.781;-122.422,37.782" +mapbox map-matching match mapbox/driving "-122.42,37.78;-122.421,37.781;-122.422,37.782" \ + --steps --geometries geojson +``` + +#### Outputs + +Captured live: three trace points in San Francisco, one deliberately far +enough off the road network to leave its tracepoint `null`. + + + + +
Terminal — -o textAgent — -o json
+ +```json +{ + "code": "Ok", + "matchings": [ + { + "confidence": 0, + "distance": 353.157, + "duration": 92.702, + "geometry": "q|qeFndejVf@lJyDd@Y_E", + "legs": [ + { + "distance": 353.157, + "duration": 92.702, + "steps": [], + "summary": "McAllister Street, Franklin Street", + "weight": 126.614 + } + ], + "weight": 126.614, + "weight_name": "auto" + } + ], + "tracepoints": [ + { "name": "McAllister Street", "location": [-122.420084, 37.780093], "waypoint_index": 0 }, + { "name": "Golden Gate Avenue", "location": [-122.421141, 37.780946], "waypoint_index": 1 }, + null + ] +} +``` + + + +```json +{"code":"Ok","matchings":[{"confidence":0,"distance":353.157,"duration":92.702,"geometry":"q|qeFndejVf@lJyDd@Y_E","legs":[{"distance":353.157,"duration":92.702,"steps":[],"summary":"McAllister Street, Franklin Street","weight":126.614}],"weight":126.614,"weight_name":"auto"}],"tracepoints":[{"name":"McAllister Street","location":[-122.420084,37.780093],"waypoint_index":0},{"name":"Golden Gate Avenue","location":[-122.421141,37.780946],"waypoint_index":1},null]} +``` + +
+ +Like `directions route`, neither output mode has a bespoke rendering for +this response — it isn't GeoJSON at the top level — so both print the same +JSON, `-o text` pretty-printed and `-o json` on one line. Trimmed to one +matching and dropped `admins`/`via_waypoints`/`alternatives_count`/`uuid` +for length; the real response carries them too. + +--- +## Matrix + +Travel time and distance between every pair in a set of up to 25 +coordinates, in one call, for driving (with or without live traffic), +walking, or cycling. Curated by hand down to the parameters documented at +docs.mapbox.com/api/navigation/matrix — see `custom-openapi/README.md` for +why this command group doesn't come from the vendored specs the way most +others do. + +**vs. `directions route`**: this answers "how far/long between every pair", +not a route through all of them in order — `directions route` is a route +through fixed stops; this is an N×N table, useful for ranking or filtering +many candidates by reachability before committing to a route through any of +them. + +### `mapbox matrix compute` + +A `durations` and/or `distances` matrix in row-major order — +`durations[i][j]` is the time from the ith source to the jth destination — +across every source/destination pair, or a subset of either side. + +#### Parameters + +`` and `` (both positional) are required. +`` is one of `mapbox/driving-traffic`, `mapbox/driving`, +`mapbox/walking`, `mapbox/cycling`. `` is 2-25 +`{longitude},{latitude}` pairs, semicolon-separated — 10 max for +`mapbox/driving-traffic`. + +| Parameter | Effect | +| --- | --- | +| `--annotations ` | Which matrix or matrices to return, comma-separated. `duration` alone is the default; both together returns both. | +| `--approaches ` | Which side of the road to approach each coordinate from. | +| `--bearings ` | Filter road segments by direction of travel, one entry per coordinate. | +| `--sources ` | Which coordinates are matrix rows — `all` (the default) or zero-based indices, **semicolon**-separated. Verified against production: comma-separated is a 422 here, unlike most other index lists on these commands. | +| `--destinations ` | Which coordinates are matrix columns — same rules as `--sources`. | +| `--fallback-speed ` | Replaces a `null` (unreachable) cell with a straight-line estimate at this speed, rather than leaving it `null`. Legacy. | +| `--depart-at ` | For future traffic conditions and time-dependent road restrictions. | + +#### Examples + +```sh +mapbox matrix compute mapbox/driving "-122.42,37.78;-122.45,37.91;-122.41,37.80" +mapbox matrix compute mapbox/driving "-122.42,37.78;-122.45,37.91;-122.41,37.80" \ + --sources 0 --destinations "1;2" +``` + +#### Outputs + +Captured live: a full 3×3 matrix between three San Francisco points, both +`durations` (seconds) and `distances` (meters). + + + + +
Terminal — -o textAgent — -o json
+ +```json +{ + "code": "Ok", + "durations": [ + [0, 2381.5, 790.1], + [2593.8, 0, 2272.5], + [994.8, 2269.7, 0] + ], + "distances": [ + [0, 25766, 3348.3], + [26960.4, 0, 25382], + [3781.3, 25174.1, 0] + ], + "sources": [ + { "name": "Van Ness Avenue", "location": [-122.420122, 37.779978] }, + { "name": "Playa Verde", "location": [-122.461997, 37.89621] }, + { "name": "Columbus Avenue", "location": [-122.409926, 37.800067] } + ], + "destinations": [ + { "name": "Van Ness Avenue", "location": [-122.420122, 37.779978] }, + { "name": "Playa Verde", "location": [-122.461997, 37.89621] }, + { "name": "Columbus Avenue", "location": [-122.409926, 37.800067] } + ] +} +``` + + + +```json +{"code":"Ok","durations":[[0,2381.5,790.1],[2593.8,0,2272.5],[994.8,2269.7,0]],"distances":[[0,25766,3348.3],[26960.4,0,25382],[3781.3,25174.1,0]],"sources":[{"name":"Van Ness Avenue","location":[-122.420122,37.779978]},{"name":"Playa Verde","location":[-122.461997,37.89621]},{"name":"Columbus Avenue","location":[-122.409926,37.800067]}],"destinations":[{"name":"Van Ness Avenue","location":[-122.420122,37.779978]},{"name":"Playa Verde","location":[-122.461997,37.89621]},{"name":"Columbus Avenue","location":[-122.409926,37.800067]}]} +``` + +
+ +Neither output mode has a bespoke rendering for this response, same as +`directions route` and `map-matching match` — both print the same JSON, +`-o text` pretty-printed and `-o json` on one line. Dropped each waypoint's +own snap `distance` for length; the real response carries it too. + --- ## Search diff --git a/src/main.rs b/src/main.rs index b8227bc..e1cfc9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1641,12 +1641,15 @@ mod tests { .filter(|op| op.disabled_scope.is_some()) .map(|op| op.command()) .collect(); - // Every current UNSUPPORTED_OPERATIONS entry is also `disabled` (or - // `tbd`, which strips the same way) in the maintainer-only decision - // record, so this is legitimately empty now rather than a broken guard — the - // operations are absent from the bundled specs, not merely filtered - // here. Nothing left to assert on `disabled` itself; the loop below - // still holds for whatever, if anything, shows up. + // Every current UNSUPPORTED_OPERATIONS entry sourced from + // `MAPBOX_SPEC_ENTRIES` is also `disabled` (or `tbd`, which strips + // the same way) in the maintainer-only decision record, so those + // are absent from the bundled specs rather than merely filtered + // here. A custom spec has no such record to strip it at the + // source — `feedback.yaml`'s `createFeedback` is UNSUPPORTED_OPERATIONS' + // first entry that actually reaches this list non-empty — so the + // loop below is doing real work for it, not just standing guard + // over an empty case. let app = build_app(&specs); for (path, _) in leaf_commands(&app, &[]) { diff --git a/src/remedy.rs b/src/remedy.rs index 7520844..912de8f 100644 --- a/src/remedy.rs +++ b/src/remedy.rs @@ -79,11 +79,25 @@ const SERVICE_DOCS: &[(&str, &str)] = &[ "directions", "https://docs.mapbox.com/api/navigation/directions/", ), + ( + "ev-charge-finder", + "https://docs.mapbox.com/api/navigation/ev-charge-finder/", + ), + ("feedback", "https://docs.mapbox.com/api/feedback/"), ("fonts", "https://docs.mapbox.com/api/maps/fonts/"), ( "geocoder", "https://docs.mapbox.com/api/search/geocoding-v6/", ), + ( + "isochrone", + "https://docs.mapbox.com/api/navigation/isochrone/", + ), + ( + "map-matching", + "https://docs.mapbox.com/api/navigation/map-matching/", + ), + ("matrix", "https://docs.mapbox.com/api/navigation/matrix/"), ("search", "https://docs.mapbox.com/api/search/search-box/"), // Static Images and Static Tiles merged into one `static` command group // (#116); neither upstream page covers both, so this points at Static diff --git a/src/spec.rs b/src/spec.rs index 4dab2ab..bc8d9ac 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -106,7 +106,12 @@ const BODY_CONTENT_TYPE_OVERRIDES: &[(&str, &str, &str)] = &[("styles", "starFil /// Kept as a table rather than a branch, for the same reason /// [`BODY_CONTENT_TYPE_OVERRIDES`] is: the fix sits next to the operation /// it's for, and outgrowing a global name later is just deleting a row. -const ARG_NAME_OVERRIDES: &[(&str, &str, &str)] = &[("directions", "profile", "routing-profile")]; +const ARG_NAME_OVERRIDES: &[(&str, &str, &str)] = &[ + ("directions", "profile", "routing-profile"), + ("isochrone", "profile", "routing-profile"), + ("map-matching", "profile", "routing-profile"), + ("matrix", "profile", "routing-profile"), +]; /// The `arg_name` a parameter should present as, when its spec name collides /// with a global argument's id. See [`ARG_NAME_OVERRIDES`]. @@ -540,6 +545,14 @@ const UNSUPPORTED_OPERATIONS: &[(&str, &str, &str)] = &[ // itself, which is why it wasn't registered alongside the other two // fonts scopes. ("styles", "downloadStyleZip", "styles:download"), + // Confirmed 2026-09-24 with a direct POST /oauth/register against + // production requesting `user-feedback:write` alongside two scopes + // already known registrable — the response's granted `scope` carried + // the other two and silently dropped this one, the same shape + // `tokens:write` and `styles:download` above already document. + // `user-feedback:read` (list/get) is unaffected and already in + // `DEFAULT_SCOPES_LIST`. + ("feedback", "createFeedbackItem", "user-feedback:write"), ]; fn unsupported_scope_for(service_name: &str, operation_id: &str) -> Option<&'static str> { @@ -653,6 +666,26 @@ pub const CUSTOM_SPEC_ENTRIES: &[SpecEntry] = &[ name: "directions", yaml: include_str!("../custom-openapi/directions/openapi/directions.yaml"), }, + SpecEntry { + name: "isochrone", + yaml: include_str!("../custom-openapi/isochrone/openapi/isochrone.yaml"), + }, + SpecEntry { + name: "map-matching", + yaml: include_str!("../custom-openapi/map-matching/openapi/map-matching.yaml"), + }, + SpecEntry { + name: "matrix", + yaml: include_str!("../custom-openapi/matrix/openapi/matrix.yaml"), + }, + SpecEntry { + name: "feedback", + yaml: include_str!("../custom-openapi/feedback/openapi/feedback.yaml"), + }, + SpecEntry { + name: "ev-charge-finder", + yaml: include_str!("../custom-openapi/ev-charge-finder/openapi/ev-charge-finder.yaml"), + }, ]; /// The list the CLI actually generates commands from: [`MAPBOX_SPEC_ENTRIES`], @@ -1172,9 +1205,20 @@ fn link_detail_operations(operations: &mut [Operation]) { // real command gets named here — the pass above pairs on paths alone, // so an unexposed GET could otherwise hold a `detail` link while not // being runnable itself. + // + // Also excluded: a listing with a required query parameter of its own. + // `listing_command` (in `remedy.rs`) only ever fills path parameters it + // can recover from the failed detail call — a listing's required query + // parameters have no such source, so a bare `mapbox ` command + // would parse as a usage error, not run the query the caller needed. + // `ev-charge-finder`'s `search` (required `latitude`/`longitude`/ + // `distance`) is the first listing this repo has had that isn't safe to + // suggest bare; `every_suggestion_is_a_command_line_that_runs` is what + // caught it. let listings: Vec<(String, ListingOperation)> = operations .iter() .filter(|op| op.is_exposed()) + .filter(|op| op.query_params.iter().all(|p| !p.required)) .filter_map(|op| { let detail = op.detail.as_ref()?; Some(( @@ -1494,6 +1538,50 @@ paths: ); } + /// `listing_command` (`remedy.rs`) only ever fills a listing's path + /// parameters from the failed detail call — it has no source for a + /// listing's own required query parameters, so a listing that has any + /// must never be linked back to. The regression this guards: + /// `ev-charge-finder search` (required `latitude`/`longitude`) would + /// otherwise have been suggested, bare, for a failed + /// `ev-charge-finder get`, and `mapbox ev-charge-finder search` alone + /// is a usage error. + #[test] + fn a_listing_with_a_required_query_parameter_is_never_named() { + let svc = parse_spec( + "svc", + r#" +openapi: 3.0.0 +info: { title: T } +paths: + /locations: + get: + operationId: search + summary: Search + parameters: + - { name: latitude, in: query, required: true, schema: { type: number } } + /locations/{location_id}: + get: { operationId: get, summary: Get } +"#, + ) + .expect("fixture parses"); + + assert!( + operation(&svc, "get").listing.is_none(), + "search takes a required query parameter this 404 has no way to fill" + ); + // The forward link is unaffected — it's informational, not + // something a caller is asked to run bare. + assert_eq!( + operation(&svc, "search") + .detail + .as_ref() + .expect("forward link still holds") + .command, + "svc get" + ); + } + /// A listing that isn't itself a command must never be named as one. /// This fixture gives a listing-shaped operation an operationId from /// `UNSUPPORTED_OPERATIONS` (`getFontCoverage`, needing a scope no @@ -2043,12 +2131,142 @@ paths: ); } + /// Same regression as `the_directions_profile_parameter_does_not_collide…` + /// above, for the second spec that ran into it — `ARG_NAME_OVERRIDES` + /// taking effect is per-row, so a second entry earns its own proof + /// rather than trusting the first test to cover it. + #[test] + fn the_isochrone_profile_parameter_does_not_collide_with_the_global_flag() { + let spec = parse_spec( + "isochrone", + include_str!("../custom-openapi/isochrone/openapi/isochrone.yaml"), + ) + .expect("isochrone.yaml parses"); + + let contours = spec + .operations + .iter() + .find(|op| op.command_path == ["contours"]) + .expect("the contours operation exists"); + + let profile = contours + .path_params + .iter() + .find(|p| p.name == "profile") + .expect("a path parameter named profile"); + + assert_ne!( + profile.arg_name, "profile", + "must not collide with the global --profile id" + ); + } + + /// Same regression, for the third spec that ran into it. + #[test] + fn the_map_matching_profile_parameter_does_not_collide_with_the_global_flag() { + let spec = parse_spec( + "map-matching", + include_str!("../custom-openapi/map-matching/openapi/map-matching.yaml"), + ) + .expect("map-matching.yaml parses"); + + let matched = spec + .operations + .iter() + .find(|op| op.command_path == ["match"]) + .expect("the match operation exists"); + + let profile = matched + .path_params + .iter() + .find(|p| p.name == "profile") + .expect("a path parameter named profile"); + + assert_ne!( + profile.arg_name, "profile", + "must not collide with the global --profile id" + ); + } + + /// Same regression, for the fourth spec that ran into it. + #[test] + fn the_matrix_profile_parameter_does_not_collide_with_the_global_flag() { + let spec = parse_spec( + "matrix", + include_str!("../custom-openapi/matrix/openapi/matrix.yaml"), + ) + .expect("matrix.yaml parses"); + + let compute = spec + .operations + .iter() + .find(|op| op.command_path == ["compute"]) + .expect("the compute operation exists"); + + let profile = compute + .path_params + .iter() + .find(|p| p.name == "profile") + .expect("a path parameter named profile"); + + assert_ne!( + profile.arg_name, "profile", + "must not collide with the global --profile id" + ); + } + + /// `createFeedbackItem` needs `user-feedback:write`, confirmed + /// unregistrable via a direct `POST /oauth/register` against + /// production — see `UNSUPPORTED_OPERATIONS`'s own comment for that. + /// `list` and `get` need only `user-feedback:read`, already in + /// `DEFAULT_SCOPES_LIST`, so they must stay reachable. + #[test] + fn feedback_create_is_unreachable_but_list_and_get_are_not() { + let spec = parse_spec( + "feedback", + include_str!("../custom-openapi/feedback/openapi/feedback.yaml"), + ) + .expect("feedback.yaml parses"); + + let create = spec + .operations + .iter() + .find(|op| op.command_path == ["create-feedback-item"]) + .expect("the create-feedback-item operation exists in the spec"); + assert!( + create.disabled_scope.is_some(), + "createFeedbackItem must be disabled — user-feedback:write isn't registrable" + ); + assert!(!create.is_exposed()); + + for path in [["list"], ["get"]] { + let op = spec + .operations + .iter() + .find(|op| op.command_path == path) + .unwrap_or_else(|| panic!("the {path:?} operation exists in the spec")); + assert!(op.is_exposed(), "{path:?} needs only user-feedback:read"); + } + } + #[test] fn arg_name_override_only_fires_for_the_row_it_names() { assert_eq!( arg_name_override("directions", "profile"), Some("routing-profile") ); + assert_eq!( + arg_name_override("isochrone", "profile"), + Some("routing-profile") + ); + assert_eq!( + arg_name_override("map-matching", "profile"), + Some("routing-profile") + ); + assert_eq!( + arg_name_override("matrix", "profile"), + Some("routing-profile") + ); assert_eq!(arg_name_override("directions", "coordinates"), None); assert_eq!(arg_name_override("styles", "profile"), None); } diff --git a/tests/fixtures/api_command_surface.txt b/tests/fixtures/api_command_surface.txt index 6885a38..fa0f672 100644 --- a/tests/fixtures/api_command_surface.txt +++ b/tests/fixtures/api_command_surface.txt @@ -2,12 +2,20 @@ mapbox accounts list-scopes | aliases: (none) mapbox accounts list-tokens | aliases: (none) mapbox accounts retrieve-token | aliases: (none) mapbox directions route | aliases: (none) +mapbox ev-charge-finder get | aliases: (none) +mapbox ev-charge-finder list-operators | aliases: (none) +mapbox ev-charge-finder search | aliases: (none) +mapbox feedback get | aliases: (none) +mapbox feedback list | aliases: (none) mapbox fonts delete | aliases: (none) mapbox fonts list | aliases: (none) mapbox fonts upload | aliases: (none) mapbox geocoder batch | aliases: (none) mapbox geocoder forward | aliases: (none) mapbox geocoder reverse | aliases: (none) +mapbox isochrone contours | aliases: (none) +mapbox map-matching match | aliases: (none) +mapbox matrix compute | aliases: (none) mapbox search category | aliases: (none) mapbox search forward | aliases: (none) mapbox search list-category | aliases: (none)