@cliwant/mcp-sam-gov 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +5 -5
- package/README.ko.md +5 -5
- package/README.md +19 -7
- package/dist/arcgis-feature.d.ts +68 -0
- package/dist/arcgis-feature.d.ts.map +1 -0
- package/dist/arcgis-feature.js +206 -0
- package/dist/arcgis-feature.js.map +1 -0
- package/dist/arcgis-hub.d.ts +90 -0
- package/dist/arcgis-hub.d.ts.map +1 -0
- package/dist/arcgis-hub.js +210 -0
- package/dist/arcgis-hub.js.map +1 -0
- package/dist/bonfire.d.ts +69 -0
- package/dist/bonfire.d.ts.map +1 -0
- package/dist/bonfire.js +212 -0
- package/dist/bonfire.js.map +1 -0
- package/dist/cbp-border.d.ts +9 -5
- package/dist/cbp-border.d.ts.map +1 -1
- package/dist/cbp-border.js +31 -11
- package/dist/cbp-border.js.map +1 -1
- package/dist/gao.d.ts.map +1 -1
- package/dist/gao.js +25 -0
- package/dist/gao.js.map +1 -1
- package/dist/grants.js +1 -1
- package/dist/grants.js.map +1 -1
- package/dist/gsa-perdiem.d.ts +16 -2
- package/dist/gsa-perdiem.d.ts.map +1 -1
- package/dist/gsa-perdiem.js +25 -4
- package/dist/gsa-perdiem.js.map +1 -1
- package/dist/integrity.d.ts.map +1 -1
- package/dist/integrity.js +18 -3
- package/dist/integrity.js.map +1 -1
- package/dist/lda.d.ts.map +1 -1
- package/dist/lda.js +17 -4
- package/dist/lda.js.map +1 -1
- package/dist/nhtsa.d.ts +9 -5
- package/dist/nhtsa.d.ts.map +1 -1
- package/dist/nhtsa.js +90 -26
- package/dist/nhtsa.js.map +1 -1
- package/dist/nist-controls.d.ts +3 -1
- package/dist/nist-controls.d.ts.map +1 -1
- package/dist/nist-controls.js +34 -12
- package/dist/nist-controls.js.map +1 -1
- package/dist/ofac.d.ts.map +1 -1
- package/dist/ofac.js +10 -1
- package/dist/ofac.js.map +1 -1
- package/dist/opengov.d.ts +96 -0
- package/dist/opengov.d.ts.map +1 -0
- package/dist/opengov.js +244 -0
- package/dist/opengov.js.map +1 -0
- package/dist/pricing.d.ts +1 -1
- package/dist/sam-gov/client.d.ts.map +1 -1
- package/dist/sam-gov/client.js +13 -4
- package/dist/sam-gov/client.js.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +199 -8
- package/dist/server.js.map +1 -1
- package/dist/socrata.d.ts +27 -16
- package/dist/socrata.d.ts.map +1 -1
- package/dist/socrata.js +149 -18
- package/dist/socrata.js.map +1 -1
- package/package.json +11 -3
- package/src/arcgis-feature.ts +232 -0
- package/src/arcgis-hub.ts +270 -0
- package/src/bonfire.ts +249 -0
- package/src/cbp-border.ts +30 -9
- package/src/gao.ts +27 -0
- package/src/grants.ts +1 -1
- package/src/gsa-perdiem.ts +28 -4
- package/src/integrity.ts +18 -3
- package/src/lda.ts +21 -5
- package/src/nhtsa.ts +92 -27
- package/src/nist-controls.ts +58 -9
- package/src/ofac.ts +12 -1
- package/src/opengov.ts +309 -0
- package/src/sam-gov/client.ts +13 -4
- package/src/server.ts +213 -8
- package/src/socrata.ts +153 -18
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* arcgis-hub.ts — ArcGIS Hub dataset DISCOVERY (`hub.arcgis.com/api/v3/datasets`),
|
|
3
|
+
* a NEW keyless-first SLED-discovery platform (loop cycle 9).
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: a large fraction of US state/local/regional/tribal (SLED) open
|
|
6
|
+
* data — especially GIS / infrastructure / permits / boundaries / procurement —
|
|
7
|
+
* is published on ArcGIS Hub, NOT on Socrata or CKAN. This tool DISCOVERS those
|
|
8
|
+
* datasets (keyword search over the whole Hub) so a B2G researcher can find what
|
|
9
|
+
* fragmented local data exists. It is the ArcGIS sibling of
|
|
10
|
+
* `socrata_discover_datasets` / `datagov_search_datasets`.
|
|
11
|
+
*
|
|
12
|
+
* The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
|
|
13
|
+
* (redirect:"error") / `driftError` / `num`·`str` (coerce.ts, null-never-0 /
|
|
14
|
+
* null-never-empty-string) / `withMeta`·`buildMeta`, mirroring datagov-catalog.ts.
|
|
15
|
+
* KEYLESS: no key seam (no token is ever read/sent).
|
|
16
|
+
*
|
|
17
|
+
* ★ SSRF: the host is a compile-time literal (`ARCGIS_HUB_HOST`). Every input
|
|
18
|
+
* (`q` / `filter[openData]` / `page[size]` / `page[start]`) rides in a
|
|
19
|
+
* MODULE-BUILT `URLSearchParams` assembled key-by-key from validated typed args
|
|
20
|
+
* — NO raw-query / raw-host passthrough (this is a FIXED-host catalog search, so
|
|
21
|
+
* there is no per-jurisdiction host vector like socrata_query has). A
|
|
22
|
+
* post-construction hostname/protocol assertion + `redirect:"error"` lock it.
|
|
23
|
+
*
|
|
24
|
+
* ★ PROVENANCE (the crux — different trust posture from our other sources): ArcGIS
|
|
25
|
+
* Hub is a GLOBAL, OPEN publishing platform. Anyone — a US city, a foreign
|
|
26
|
+
* government, an NGO, a company — can publish, so results are NOT all US and NOT
|
|
27
|
+
* all governmental. This tool is therefore explicitly a DISCOVERY aid, NOT a
|
|
28
|
+
* curated official-source allowlist (unlike `socrata_query`, whose hosts are a
|
|
29
|
+
* vetted allowlist). Each result surfaces `owner`/`orgName`/`source`/`region`
|
|
30
|
+
* VERBATIM so the consumer can VET the publisher, and every response carries the
|
|
31
|
+
* global-platform disclosure. `openDataOnly` (default true) biases toward items
|
|
32
|
+
* the publisher designated as open data. DISCOVERY ONLY — to read rows, follow
|
|
33
|
+
* the dataset on its own ArcGIS endpoint (a guarded row-query tool is a planned
|
|
34
|
+
* addition; ArcGIS feature services live on arbitrary hosts, which needs its own
|
|
35
|
+
* SSRF design).
|
|
36
|
+
*
|
|
37
|
+
* ★ HONESTY PILLARS (captured live 2026-07-19):
|
|
38
|
+
* P1 (real total): the v3 response carries `meta.total` (== `meta.stats.totalCount`)
|
|
39
|
+
* — the EXACT match count across the Hub. `totalAvailable = num(meta.total)`
|
|
40
|
+
* (NEVER data.length); a page is `truncated` when `offset + returned < total`.
|
|
41
|
+
* Pagination is a 1-based record offset (`page[start] = offset + 1`).
|
|
42
|
+
* P2: getJson→fetchWithRetry THROWS on 429 (rate_limited) / 5xx
|
|
43
|
+
* (upstream_unavailable) / timeout — NEVER a fake empty. A genuine no-match
|
|
44
|
+
* (`data:[]`, total 0) ⇒ honest empty (datasets:[], returned:0, total:0,
|
|
45
|
+
* complete:true).
|
|
46
|
+
* P3: every scalar via `str` (null-never-empty) / booleans preserved / `num`
|
|
47
|
+
* for counts (null-never-0). A missing owner/source/region is null, never "".
|
|
48
|
+
* P4: `body.data` absent/non-array ⇒ driftError; a 200 non-JSON body ⇒
|
|
49
|
+
* schema_drift via the catch-ladder (ToolErrorCarrier rethrow FIRST so a
|
|
50
|
+
* 429/5xx keeps its taxonomy → SyntaxError→driftError → bare rethrow).
|
|
51
|
+
*/
|
|
52
|
+
import { ToolErrorCarrier } from "./errors.js";
|
|
53
|
+
import { getJson, driftError } from "./datasource.js";
|
|
54
|
+
import { num, str } from "./coerce.js";
|
|
55
|
+
import { withMeta } from "./meta.js";
|
|
56
|
+
// Re-export the shared honesty coercion (single audited copy in ./coerce.js) so
|
|
57
|
+
// the fault suite's num-parity guard resolves `num` from this module too.
|
|
58
|
+
export { num };
|
|
59
|
+
// ─── Fixed endpoint (SSRF core — compile-time CONSTANTS) ──────────
|
|
60
|
+
export const ARCGIS_HUB_HOST = "hub.arcgis.com";
|
|
61
|
+
const ARCGIS_HUB_PATH = "/api/v3/datasets";
|
|
62
|
+
// HOST+path label — surfaces in ToolError.upstreamEndpoint. Keyless: no token can
|
|
63
|
+
// ever appear here (none is read).
|
|
64
|
+
const ARCGIS_HUB_LABEL = "arcgis-hub:/api/v3/datasets";
|
|
65
|
+
const ARCGIS_HUB_SOURCE = "hub.arcgis.com via ArcGIS Hub (keyless; GLOBAL open platform — vet each publisher)";
|
|
66
|
+
// The load-bearing provenance caveat carried on EVERY response.
|
|
67
|
+
const ARCGIS_HUB_PROVENANCE_NOTE = "ArcGIS Hub is a GLOBAL, OPEN publishing platform — results include non-US and non-governmental publishers. This is a DISCOVERY aid, NOT a curated official-source allowlist (unlike socrata_query): VET each result's owner/orgName/source/region before relying on it. DISCOVERY ONLY (metadata + links) — to read rows, follow the dataset on its own ArcGIS endpoint.";
|
|
68
|
+
/** A string[] from a mixed value (drops null/empty via str), else [] when absent/non-array. */
|
|
69
|
+
function strArray(x) {
|
|
70
|
+
if (!Array.isArray(x))
|
|
71
|
+
return [];
|
|
72
|
+
return x.map((v) => str(v)).filter((v) => v !== null);
|
|
73
|
+
}
|
|
74
|
+
/** null-preserving boolean coercion — a real true/false survives; anything else → null (never a fabricated false). */
|
|
75
|
+
function boolOrNull(x) {
|
|
76
|
+
return typeof x === "boolean" ? x : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Map ONE `data[]` row → the curated dataset shape. The Hub's JSON:API row is
|
|
80
|
+
* `{ id, type, attributes:{…} }`. Every scalar via `str` (null-never-empty);
|
|
81
|
+
* booleans null-preserving; keywords via strArray. The publisher-identifying
|
|
82
|
+
* fields (owner/orgName/source/region) are surfaced VERBATIM for vetting.
|
|
83
|
+
*/
|
|
84
|
+
function mapDataset(row) {
|
|
85
|
+
const r = (row ?? {});
|
|
86
|
+
const a = r.attributes ?? {};
|
|
87
|
+
return {
|
|
88
|
+
id: str(r.id),
|
|
89
|
+
name: str(a.name),
|
|
90
|
+
description: str(a.searchDescription ?? a.description ?? a.snippet),
|
|
91
|
+
owner: str(a.owner),
|
|
92
|
+
orgName: str(a.orgName ?? a.organization),
|
|
93
|
+
source: str(a.source),
|
|
94
|
+
region: str(a.region),
|
|
95
|
+
type: str(a.type),
|
|
96
|
+
sector: str(a.sector),
|
|
97
|
+
keywords: strArray(a.tags),
|
|
98
|
+
downloadable: boolOrNull(a.downloadable),
|
|
99
|
+
hasApi: boolOrNull(a.hasApi),
|
|
100
|
+
created: str(a.created),
|
|
101
|
+
modified: str(a.modified),
|
|
102
|
+
landingPage: str(a.landingPage ?? a.url),
|
|
103
|
+
itemId: str(a.itemId),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect) ──
|
|
107
|
+
/**
|
|
108
|
+
* GET one ArcGIS Hub v3 JSON resource. All caller params ride in `params`
|
|
109
|
+
* (URLSearchParams, encoded). Builds `https://${ARCGIS_HUB_HOST}${ARCGIS_HUB_PATH}?…`
|
|
110
|
+
* on the FIXED host, asserts the CONSTRUCTED URL's hostname === the host over
|
|
111
|
+
* https (belt-and-suspenders), sets `redirect:"error"` (an off-host 3xx must NOT
|
|
112
|
+
* be followed). KEYLESS — no headers/token.
|
|
113
|
+
*/
|
|
114
|
+
async function getHub(params) {
|
|
115
|
+
const qs = params.toString();
|
|
116
|
+
const url = `https://${ARCGIS_HUB_HOST}${ARCGIS_HUB_PATH}${qs ? `?${qs}` : ""}`;
|
|
117
|
+
const built = new URL(url);
|
|
118
|
+
if (built.hostname !== ARCGIS_HUB_HOST || built.protocol !== "https:") {
|
|
119
|
+
throw new ToolErrorCarrier({
|
|
120
|
+
kind: "invalid_input",
|
|
121
|
+
message: `Constructed ArcGIS Hub URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the fixed host ${JSON.stringify(ARCGIS_HUB_HOST)} over https — refusing to fetch (SSRF safety).`,
|
|
122
|
+
retryable: false,
|
|
123
|
+
upstreamEndpoint: ARCGIS_HUB_LABEL,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return getJson(url, { label: ARCGIS_HUB_LABEL, redirect: "error" });
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Search ArcGIS Hub datasets by keyword. Filters: `query` (→q, REQUIRED, ≥2
|
|
130
|
+
* non-whitespace chars), `openDataOnly` (default true → filter[openData]=true,
|
|
131
|
+
* biases toward publisher-designated open data), `limit` (→page[size]), `offset`
|
|
132
|
+
* (→page[start]=offset+1, 1-based). The query is MODULE-BUILT from validated typed
|
|
133
|
+
* args (NO raw passthrough). Returns curated dataset rows + honest `_meta`:
|
|
134
|
+
* totalAvailable = the EXACT Hub match count (meta.total, P1), the global-platform
|
|
135
|
+
* provenance disclosure, and per-row publisher fields for vetting.
|
|
136
|
+
*/
|
|
137
|
+
export async function discoverDatasets(args) {
|
|
138
|
+
const limit = args.limit ?? 20;
|
|
139
|
+
const offset = args.offset ?? 0;
|
|
140
|
+
const openDataOnly = args.openDataOnly ?? true;
|
|
141
|
+
// Belt-and-suspenders (behind the server's Zod): a <2-char / whitespace-only q
|
|
142
|
+
// would scan the whole global Hub — reject pre-fetch (0 network call).
|
|
143
|
+
const q = args.query ?? "";
|
|
144
|
+
if (q.trim().length < 2) {
|
|
145
|
+
throw new ToolErrorCarrier({
|
|
146
|
+
kind: "invalid_input",
|
|
147
|
+
message: `ArcGIS Hub 'query' must be at least 2 non-whitespace characters (a broad scan of the entire global Hub is refused).`,
|
|
148
|
+
retryable: false,
|
|
149
|
+
upstreamEndpoint: ARCGIS_HUB_LABEL,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Build the query from VALIDATED typed args, key-by-key (SSRF: no raw passthrough).
|
|
153
|
+
const params = new URLSearchParams();
|
|
154
|
+
const filtersApplied = ["query"];
|
|
155
|
+
params.set("q", q);
|
|
156
|
+
params.set("page[size]", String(limit));
|
|
157
|
+
params.set("page[start]", String(offset + 1)); // Hub page[start] is 1-based.
|
|
158
|
+
if (openDataOnly) {
|
|
159
|
+
params.set("filter[openData]", "true");
|
|
160
|
+
filtersApplied.push("openDataOnly");
|
|
161
|
+
}
|
|
162
|
+
// The typed catch-ladder (datagov-catalog shape, VERBATIM): preserve the
|
|
163
|
+
// 429/404/5xx/400/timeout ToolErrorCarrier taxonomy FIRST; reclassify a 200
|
|
164
|
+
// non-JSON SyntaxError to schema_drift SECOND; bare-rethrow LAST.
|
|
165
|
+
let body;
|
|
166
|
+
try {
|
|
167
|
+
body = await getHub(params);
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
if (e instanceof ToolErrorCarrier)
|
|
171
|
+
throw e;
|
|
172
|
+
if (e instanceof SyntaxError)
|
|
173
|
+
throw driftError(ARCGIS_HUB_LABEL, "ArcGIS Hub returned a non-JSON body at HTTP 200 — schema drift.");
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
const b = (body ?? {});
|
|
177
|
+
// P4: `data` MUST be an array (a missing/string/null data is drift, never a
|
|
178
|
+
// fabricated empty — a TypeError must never mask drift as upstream_unavailable).
|
|
179
|
+
if (!Array.isArray(b.data)) {
|
|
180
|
+
throw driftError(ARCGIS_HUB_LABEL, "ArcGIS Hub shape drift — /api/v3/datasets response.data must be an array.");
|
|
181
|
+
}
|
|
182
|
+
const datasets = b.data.map(mapDataset);
|
|
183
|
+
const returned = datasets.length;
|
|
184
|
+
// P1: totalAvailable = the EXACT Hub match count (meta.total), NEVER data.length.
|
|
185
|
+
// meta.total (== meta.stats.totalCount) is the primary total; if absent → null
|
|
186
|
+
// (best-effort, never a fabricated count). num() is null-never-0.
|
|
187
|
+
const meta = (b.meta ?? {});
|
|
188
|
+
const totalAvailable = num(meta.total ?? meta.stats?.totalCount);
|
|
189
|
+
const hasMore = totalAvailable !== null ? offset + returned < totalAvailable : returned >= limit;
|
|
190
|
+
const nextOffset = hasMore ? offset + returned : null;
|
|
191
|
+
const notes = [ARCGIS_HUB_PROVENANCE_NOTE];
|
|
192
|
+
notes.push(openDataOnly
|
|
193
|
+
? "openDataOnly=true: results are filtered to items the publisher designated as open data (filter[openData]=true). Set openDataOnly=false to broaden to all shared items."
|
|
194
|
+
: "openDataOnly=false: ALL shared Hub items are searched (not only designated open data) — vetting the publisher matters even more.");
|
|
195
|
+
if (totalAvailable === null) {
|
|
196
|
+
notes.push("totalAvailable is unknown (the Hub omitted meta.total on this response); completeness is inferred from page fullness (returned < limit ⇒ last page).");
|
|
197
|
+
}
|
|
198
|
+
return withMeta({ query: q, openDataOnly, datasets }, {
|
|
199
|
+
source: ARCGIS_HUB_SOURCE,
|
|
200
|
+
keylessMode: true,
|
|
201
|
+
returned,
|
|
202
|
+
totalAvailable,
|
|
203
|
+
filtersApplied,
|
|
204
|
+
filtersDropped: [],
|
|
205
|
+
fieldsUnavailable: [],
|
|
206
|
+
pagination: { offset, limit, hasMore, nextOffset },
|
|
207
|
+
notes,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=arcgis-hub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arcgis-hub.js","sourceRoot":"","sources":["../src/arcgis-hub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bonfire.ts — Bonfire (Euna Solutions) per-organization open-solicitation RSS,
|
|
3
|
+
* a keyless-first SLED bid source (loop — SLED bid campaign, 2026-07-19).
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: Bonfire hosts the open-bid portals of thousands of US state/local
|
|
6
|
+
* governments; each org exposes a KEYLESS RSS 2.0 feed of its currently-open
|
|
7
|
+
* opportunities at `https://{org}.bonfirehub.com/opportunities/rss` (live-verified
|
|
8
|
+
* on Dallas/Harris County/Utah/Bernalillo/…). One of the two highest-reach keyless
|
|
9
|
+
* SLED bid feeds (with OpenGov Procurement).
|
|
10
|
+
*
|
|
11
|
+
* ★ NO keyless directory API: Bonfire's authoritative org list
|
|
12
|
+
* (`GET common-production-api-global.bonfirehub.com/v1.0/organizations/external`)
|
|
13
|
+
* is AUTH-GATED (a free vendor-account token) — OUT OF BOUNDS (we never sign in).
|
|
14
|
+
* So this ships a CURATED, live-verified SEED directory (187 US orgs; §BONFIRE_
|
|
15
|
+
* ORGS) as `bonfire_list_organizations`, and documents the keyless RSS-probe
|
|
16
|
+
* refresh method (no catch-all: `{slug}.bonfirehub.com/opportunities/rss` returns
|
|
17
|
+
* 200 <rss> for a real org, a connection failure for a non-provisioned slug). The
|
|
18
|
+
* seed is a PARTIAL directory (Euna markets up to ~900 US orgs) — disclosed.
|
|
19
|
+
*
|
|
20
|
+
* The module REUSES `getText` (shared XML/RSS fetch, redirect:"error") /
|
|
21
|
+
* `driftError` / `str`·`num` / `withMeta`·`buildMeta`, mirroring fpds/gao. KEYLESS.
|
|
22
|
+
*
|
|
23
|
+
* ★ SSRF: org is charclass-validated (`^[a-z0-9-]{1,64}$` — no dots), the URL is
|
|
24
|
+
* built on the FIXED `.bonfirehub.com` suffix, and a post-construction assertion
|
|
25
|
+
* requires `hostname === {org}.bonfirehub.com` (over https) BEFORE the fetch;
|
|
26
|
+
* `redirect:"error"`.
|
|
27
|
+
*
|
|
28
|
+
* ★ HONESTY PILLARS:
|
|
29
|
+
* P1: the RSS is the COMPLETE current open-opportunity set for the org (no server
|
|
30
|
+
* pagination), so totalAvailable = the parsed item count (the true total, NOT a
|
|
31
|
+
* page length); client-side limit/offset page over it.
|
|
32
|
+
* P2: getText THROWS on 429 / 5xx / 404 / timeout — NEVER a fake empty. An empty
|
|
33
|
+
* channel (0 items) ⇒ honest empty (the org has no open opportunities now).
|
|
34
|
+
* P3: every field via `str` (null-never-empty); dates surfaced verbatim.
|
|
35
|
+
* P4: a 200 body that is not RSS (no `<rss`/`<channel`) ⇒ schema_drift (never
|
|
36
|
+
* parsed as an empty set).
|
|
37
|
+
*/
|
|
38
|
+
import { num } from "./coerce.js";
|
|
39
|
+
import { type MetaBundle } from "./meta.js";
|
|
40
|
+
export { num };
|
|
41
|
+
export declare const BONFIRE_ORG_RE: RegExp;
|
|
42
|
+
export type BonfireOrg = {
|
|
43
|
+
org: string;
|
|
44
|
+
name: string;
|
|
45
|
+
state: string;
|
|
46
|
+
};
|
|
47
|
+
export declare const BONFIRE_ORGS: readonly BonfireOrg[];
|
|
48
|
+
export type BonfireOpportunity = {
|
|
49
|
+
referenceNumber: string | null;
|
|
50
|
+
name: string | null;
|
|
51
|
+
description: string | null;
|
|
52
|
+
closeDate: string | null;
|
|
53
|
+
link: string | null;
|
|
54
|
+
pubDate: string | null;
|
|
55
|
+
};
|
|
56
|
+
export type BonfireListArgs = {
|
|
57
|
+
state?: string;
|
|
58
|
+
query?: string;
|
|
59
|
+
limit?: number;
|
|
60
|
+
offset?: number;
|
|
61
|
+
};
|
|
62
|
+
export declare function listOrganizations(args: BonfireListArgs): Promise<MetaBundle>;
|
|
63
|
+
export type BonfireSearchArgs = {
|
|
64
|
+
org: string;
|
|
65
|
+
limit?: number;
|
|
66
|
+
offset?: number;
|
|
67
|
+
};
|
|
68
|
+
export declare function searchOpportunities(args: BonfireSearchArgs): Promise<MetaBundle>;
|
|
69
|
+
//# sourceMappingURL=bonfire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bonfire.d.ts","sourceRoot":"","sources":["../src/bonfire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH,OAAO,EAAE,GAAG,EAAO,MAAM,aAAa,CAAC;AACvC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAEzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAIf,eAAO,MAAM,cAAc,QAAsB,CAAC;AAiDlD,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACtE,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAG5C,CAAC;AAcH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAsCF,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAiClF;AAGD,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiDtF"}
|
package/dist/bonfire.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bonfire.ts — Bonfire (Euna Solutions) per-organization open-solicitation RSS,
|
|
3
|
+
* a keyless-first SLED bid source (loop — SLED bid campaign, 2026-07-19).
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: Bonfire hosts the open-bid portals of thousands of US state/local
|
|
6
|
+
* governments; each org exposes a KEYLESS RSS 2.0 feed of its currently-open
|
|
7
|
+
* opportunities at `https://{org}.bonfirehub.com/opportunities/rss` (live-verified
|
|
8
|
+
* on Dallas/Harris County/Utah/Bernalillo/…). One of the two highest-reach keyless
|
|
9
|
+
* SLED bid feeds (with OpenGov Procurement).
|
|
10
|
+
*
|
|
11
|
+
* ★ NO keyless directory API: Bonfire's authoritative org list
|
|
12
|
+
* (`GET common-production-api-global.bonfirehub.com/v1.0/organizations/external`)
|
|
13
|
+
* is AUTH-GATED (a free vendor-account token) — OUT OF BOUNDS (we never sign in).
|
|
14
|
+
* So this ships a CURATED, live-verified SEED directory (187 US orgs; §BONFIRE_
|
|
15
|
+
* ORGS) as `bonfire_list_organizations`, and documents the keyless RSS-probe
|
|
16
|
+
* refresh method (no catch-all: `{slug}.bonfirehub.com/opportunities/rss` returns
|
|
17
|
+
* 200 <rss> for a real org, a connection failure for a non-provisioned slug). The
|
|
18
|
+
* seed is a PARTIAL directory (Euna markets up to ~900 US orgs) — disclosed.
|
|
19
|
+
*
|
|
20
|
+
* The module REUSES `getText` (shared XML/RSS fetch, redirect:"error") /
|
|
21
|
+
* `driftError` / `str`·`num` / `withMeta`·`buildMeta`, mirroring fpds/gao. KEYLESS.
|
|
22
|
+
*
|
|
23
|
+
* ★ SSRF: org is charclass-validated (`^[a-z0-9-]{1,64}$` — no dots), the URL is
|
|
24
|
+
* built on the FIXED `.bonfirehub.com` suffix, and a post-construction assertion
|
|
25
|
+
* requires `hostname === {org}.bonfirehub.com` (over https) BEFORE the fetch;
|
|
26
|
+
* `redirect:"error"`.
|
|
27
|
+
*
|
|
28
|
+
* ★ HONESTY PILLARS:
|
|
29
|
+
* P1: the RSS is the COMPLETE current open-opportunity set for the org (no server
|
|
30
|
+
* pagination), so totalAvailable = the parsed item count (the true total, NOT a
|
|
31
|
+
* page length); client-side limit/offset page over it.
|
|
32
|
+
* P2: getText THROWS on 429 / 5xx / 404 / timeout — NEVER a fake empty. An empty
|
|
33
|
+
* channel (0 items) ⇒ honest empty (the org has no open opportunities now).
|
|
34
|
+
* P3: every field via `str` (null-never-empty); dates surfaced verbatim.
|
|
35
|
+
* P4: a 200 body that is not RSS (no `<rss`/`<channel`) ⇒ schema_drift (never
|
|
36
|
+
* parsed as an empty set).
|
|
37
|
+
*/
|
|
38
|
+
import { ToolErrorCarrier } from "./errors.js";
|
|
39
|
+
import { getText, driftError } from "./datasource.js";
|
|
40
|
+
import { num, str } from "./coerce.js";
|
|
41
|
+
import { withMeta } from "./meta.js";
|
|
42
|
+
export { num };
|
|
43
|
+
// ─── Fixed suffix (SSRF core) + org grammar ───────────────────────
|
|
44
|
+
const BONFIRE_SUFFIX = ".bonfirehub.com";
|
|
45
|
+
export const BONFIRE_ORG_RE = /^[a-z0-9-]{1,64}$/;
|
|
46
|
+
const bonfireLabel = (org) => `bonfire:${org}/opportunities/rss`;
|
|
47
|
+
const BONFIRE_SOURCE = (org) => `${org}.bonfirehub.com via Bonfire (Euna) open-opportunities RSS (keyless)`;
|
|
48
|
+
const BONFIRE_SEED_NOTE = "This directory is a CURATED, live-verified SEED (Bonfire has NO keyless org-list API; the authoritative list is auth-gated and out of bounds). Euna markets up to ~900 US orgs, so the seed is partial — probe `{slug}.bonfirehub.com/opportunities/rss` (200 <rss> = real org) to extend. Feed a result's `org` to bonfire_search_opportunities.";
|
|
49
|
+
// ─── The curated 187-org US seed directory (live-verified 2026-07-19) ──
|
|
50
|
+
// "slug|Entity|ST" — the slug is the RSS subdomain. Non-US (.ca / cayman / etc.)
|
|
51
|
+
// deliberately excluded.
|
|
52
|
+
const BONFIRE_SEED_RAW = [
|
|
53
|
+
"littlerock|City of Little Rock|AR",
|
|
54
|
+
"buckeyeaz|City of Buckeye|AZ", "goodyearaz|City of Goodyear|AZ", "peoriaaz|City of Peoria|AZ", "scottsdaleaz|City of Scottsdale|AZ", "yumaaz|City of Yuma|AZ", "pinalcountyaz|Pinal County|AZ", "susd|Scottsdale USD|AZ", "tollesonuhsd|Tolleson Union HSD|AZ",
|
|
55
|
+
"cityofirvine|City of Irvine|CA", "oaklandca|City of Oakland|CA", "alamedacounty|Alameda County|CA", "solanocounty|Solano County|CA", "ventura|County of Ventura|CA", "yolocounty|Yolo County|CA", "mtc|Metropolitan Transportation Commission|CA", "azusa|Azusa USD|CA", "stocktonusd|Stockton USD|CA", "weta|SF Bay Area Water Emergency Transportation Authority|CA", "acwd|Alameda County Water District|CA", "wrd|Water Replenishment District of Southern CA|CA", "actransit|AC Transit|CA", "marintransit|Marin Transit|CA", "mst|Monterey-Salinas Transit|CA", "omnitrans|Omnitrans|CA", "smctd|San Mateo County Transit District|CA", "sonomamarintrain|Sonoma-Marin Area Rail Transit|CA", "ggbhtd|Golden Gate Bridge Highway & Transportation District|CA",
|
|
56
|
+
"bouldercounty|Boulder County|CO",
|
|
57
|
+
"crcog|Capitol Region Council of Governments|CT", "easternct|Eastern Connecticut State University|CT",
|
|
58
|
+
"dfm|DE OMB - Division of Facility Management|DE", "gss|DE OMB - Government Support Services|DE",
|
|
59
|
+
"daviefl|Town of Davie|FL", "ocoee|City of Ocoee|FL", "broward|Broward County|FL", "hillsboroughcounty|Hillsborough County|FL", "marionfl|Marion County|FL", "monroecounty-fl|Monroe County|FL", "pascocountyfl|Pasco County|FL", "famu|Florida A&M University|FL", "fau|Florida Atlantic University|FL", "fgcu|Florida Gulf Coast University|FL", "floridapoly|Florida Polytechnic University|FL", "ucf|University of Central Florida|FL", "tampabaywater|Tampa Bay Water|FL", "gohart|Hillsborough Transit Authority|FL", "psta|Pinellas Suncoast Transit Authority|FL",
|
|
60
|
+
"brookhavenga|City of Brookhaven|GA", "sandysprings|City of Sandy Springs|GA", "chathamcountyga|Chatham County|GA", "columbiacountyga|Columbia County|GA", "gwinnett|Gwinnett County Public Schools|GA",
|
|
61
|
+
"cityofnampa|City of Nampa|ID", "adacounty|Ada County|ID", "bannockcounty|Bannock County|ID",
|
|
62
|
+
"cookcountyil|Cook County|IL", "thecha|Chicago Housing Authority|IL", "cps|Chicago Public Schools|IL", "u-46|School District U-46|IL", "chicagoparkdistrict|Chicago Park District|IL", "mwrd|Metro Water Reclamation District of Greater Chicago|IL", "transitchicago|Chicago Transit Authority|IL", "metra|Metra|IL",
|
|
63
|
+
"indygo|Indianapolis Public Transportation Corp|IN",
|
|
64
|
+
"olatheks|City of Olathe|KS", "wichita|City of Wichita|KS",
|
|
65
|
+
"covingtonky|City of Covington|KY", "louisvilleky|City of Louisville|KY", "owensboro|City of Owensboro|KY", "lexingtonky|Lexington-Fayette|KY", "kyhousing|Kentucky Housing Corporation|KY", "tarc|Transit Authority of River City|KY",
|
|
66
|
+
"umass|University of Massachusetts|MA",
|
|
67
|
+
"harfordcountymd|Harford County|MD", "habc|Housing Authority of Baltimore City|MD", "hcpss|Howard County Public School System|MD", "menv|Maryland Environmental Service|MD", "mdcourts|Maryland Judiciary|MD",
|
|
68
|
+
"maine|State of Maine|ME",
|
|
69
|
+
"detroit|City of Detroit|MI",
|
|
70
|
+
"ramseycountymn|Ramsey County|MN", "sourcewell|Sourcewell|MN",
|
|
71
|
+
"jeffersoncitymo|Jefferson City|MO", "stlouiscountymo|St. Louis County|MO", "stlcc|St. Louis Community College|MO",
|
|
72
|
+
"apexnc|Town of Apex|NC", "charlottenc|City of Charlotte|NC", "wake|Wake County|NC", "ncat|NC A&T State University|NC", "ncsu|NC State University|NC",
|
|
73
|
+
"rutgers|Rutgers University|NJ",
|
|
74
|
+
"cabq|City of Albuquerque|NM", "mckinleycounty|McKinley County|NM",
|
|
75
|
+
"clarkcountynv|Clark County|NV", "ccsd|Clark County School District|NV",
|
|
76
|
+
"suffolkcountyny|Suffolk County|NY", "stonybrook|Stony Brook University|NY", "healthsolutions|Public Health Solutions|NY", "centro|Central NY Regional Transportation Authority|NY", "nfta|Niagara Frontier Transportation Authority|NY", "panynj|Port Authority of New York & New Jersey|NY",
|
|
77
|
+
"akronohio|City of Akron|OH", "cincinnati-oh|City of Cincinnati|OH", "columbus|City of Columbus|OH", "equalisgroup|Equalis Group|OH",
|
|
78
|
+
"pdx|Portland State University|OR",
|
|
79
|
+
"pennbid|PennBid|PA", "alleghenycounty|Allegheny County|PA",
|
|
80
|
+
"charlestoncounty|Charleston County|SC", "tridenttech|Trident Technical College|SC",
|
|
81
|
+
"apsu|Austin Peay State University|TN",
|
|
82
|
+
"dfwairport|DFW International Airport|TX", "amarillo|City of Amarillo|TX", "arlingtontx|City of Arlington|TX", "brownsvilletx|City of Brownsville|TX", "burlesontx|City of Burleson|TX", "cityoflewisville|City of Lewisville|TX", "dallascityhall|City of Dallas|TX", "fortworthtexas|City of Fort Worth|TX", "friscotexas|City of Frisco|TX", "leandertx|City of Leander|TX", "mckinneytexas|City of McKinney|TX", "midlandtexas|City of Midland|TX", "roundrocktexas|City of Round Rock|TX", "sanantonio|City of San Antonio|TX", "schertz|City of Schertz|TX", "southlake|City of Southlake|TX", "templetx|City of Temple|TX", "waco-texas|City of Waco|TX", "mansfield|Mansfield Council of Governments|TX", "brazoriacounty|Brazoria County|TX", "dentoncounty|Denton County|TX", "galvestoncountytx|Galveston County|TX", "harriscountytx|Harris County|TX", "johnsoncountytx|Johnson County|TX", "lubbock|Lubbock County|TX", "parkercountytx|Parker County|TX", "smithcounty|Smith County|TX", "wilco|Williamson County|TX", "hccs|Houston Community College|TX", "rice-edu|Rice University|TX", "tccd|Tarrant County College District|TX", "utdallas|UT Dallas|TX", "utexas|UT Austin|TX", "utrgv|UT Rio Grande Valley|TX", "uttyler|UT Tyler|TX", "uthscsa|UT Health San Antonio|TX", "saha|Opportunity Home San Antonio|TX", "universityhealth|University Health (hospital district)|TX", "allenisd|Allen ISD|TX", "austinisd|Austin ISD|TX", "comalisd|Comal ISD|TX", "dallasisd|Dallas ISD|TX", "fortbendisd|Fort Bend ISD|TX", "kleinisd|Klein ISD|TX", "laredoisd|Laredo ISD|TX", "magnoliaisd|Magnolia ISD|TX", "mesquiteisd|Mesquite ISD|TX", "tomballisd|Tomball ISD|TX", "twc-texas-gov|Texas Workforce Commission|TX", "txdot|Texas Department of Transportation|TX", "dart|Dallas Area Rapid Transit|TX", "ridemetro|Harris County METRO|TX",
|
|
83
|
+
"ccog|The Cooperative Council of Governments|US", "omniapartners|OMNIA Partners|US", "utah|U3P / Utah Public Procurement Place|UT",
|
|
84
|
+
"fairfaxcounty|Fairfax County|VA", "cnu|Christopher Newport University|VA", "gmu|George Mason University|VA", "nsu|Norfolk State University|VA", "fcps|Fairfax County Public Schools|VA",
|
|
85
|
+
"cityofvancouver|City of Vancouver WA|WA", "federalwaywa|City of Federal Way|WA", "clarkcountywa|Clark County WA|WA", "kingcounty|King County|WA", "portolympia|Port of Olympia|WA", "lwsd|Lake Washington School District|WA", "tacoma|Tacoma Public Schools|WA",
|
|
86
|
+
"cityofmilwaukee|City of Milwaukee|WI", "westalliswi|City of West Allis|WI", "racinecounty|Racine County|WI", "waukeshacounty|Waukesha County|WI", "cvtc|Chippewa Valley Technical College|WI", "mmsd|Milwaukee Metropolitan Sewerage District|WI",
|
|
87
|
+
"marshall|Marshall University|WV",
|
|
88
|
+
];
|
|
89
|
+
export const BONFIRE_ORGS = BONFIRE_SEED_RAW.map((r) => {
|
|
90
|
+
const [org, name, state] = r.split("|");
|
|
91
|
+
return { org: org ?? "", name: name ?? "", state: state ?? "" };
|
|
92
|
+
});
|
|
93
|
+
// ─── XML helpers (bespoke regex parser, mirrors fpds/gao) ─────────
|
|
94
|
+
function decodeEntities(s) {
|
|
95
|
+
return s
|
|
96
|
+
.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"')
|
|
97
|
+
.replace(/�?39;/g, "'").replace(/'/g, "'").replace(/&/g, "&");
|
|
98
|
+
}
|
|
99
|
+
function tag(block, name) {
|
|
100
|
+
const m = new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`).exec(block);
|
|
101
|
+
if (!m)
|
|
102
|
+
return null;
|
|
103
|
+
return decodeEntities((m[1] ?? "").replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1").trim());
|
|
104
|
+
}
|
|
105
|
+
/** Parse a Bonfire RSS item block → curated opportunity. Title is
|
|
106
|
+
* "Reference #: <ref>. Name: <name>"; description embeds "closes <date>". */
|
|
107
|
+
function mapItem(block) {
|
|
108
|
+
const title = tag(block, "title");
|
|
109
|
+
const description = tag(block, "description");
|
|
110
|
+
let referenceNumber = null;
|
|
111
|
+
let name = str(title);
|
|
112
|
+
if (title) {
|
|
113
|
+
const m = /Reference #:\s*(.*?)\.\s*Name:\s*([\s\S]*)$/.exec(title);
|
|
114
|
+
if (m) {
|
|
115
|
+
referenceNumber = str(m[1]);
|
|
116
|
+
name = str(m[2]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
let closeDate = null;
|
|
120
|
+
if (description) {
|
|
121
|
+
const c = /\bcloses\s+([A-Za-z0-9:,\s]+?(?:AM|PM)[A-Za-z0-9 ]*)$/i.exec(description) || /\bcloses\s+(.+)$/i.exec(description);
|
|
122
|
+
if (c && c[1])
|
|
123
|
+
closeDate = str(c[1].trim());
|
|
124
|
+
}
|
|
125
|
+
return { referenceNumber, name, description: str(description), closeDate, link: str(tag(block, "link")), pubDate: str(tag(block, "pubDate")) };
|
|
126
|
+
}
|
|
127
|
+
// ─── SSRF-guarded fetch ───────────────────────────────────────────
|
|
128
|
+
async function getBonfireRss(org) {
|
|
129
|
+
const host = `${org}${BONFIRE_SUFFIX}`;
|
|
130
|
+
const url = `https://${host}/opportunities/rss`;
|
|
131
|
+
const built = new URL(url);
|
|
132
|
+
if (built.hostname !== host || !built.hostname.endsWith(BONFIRE_SUFFIX) || built.protocol !== "https:") {
|
|
133
|
+
throw new ToolErrorCarrier({
|
|
134
|
+
kind: "invalid_input",
|
|
135
|
+
message: `Constructed Bonfire URL host ${JSON.stringify(built.hostname)} (${built.protocol}) is not ${JSON.stringify(host)} over https — refusing to fetch (SSRF safety).`,
|
|
136
|
+
retryable: false,
|
|
137
|
+
upstreamEndpoint: bonfireLabel(org),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return getText(url, { label: bonfireLabel(org), redirect: "error" });
|
|
141
|
+
}
|
|
142
|
+
export async function listOrganizations(args) {
|
|
143
|
+
const limit = args.limit ?? 50;
|
|
144
|
+
const offset = args.offset ?? 0;
|
|
145
|
+
const stateFilter = args.state ? args.state.trim().toUpperCase() : null;
|
|
146
|
+
const queryFilter = args.query ? args.query.trim().toLowerCase() : null;
|
|
147
|
+
const filtersApplied = [];
|
|
148
|
+
if (stateFilter)
|
|
149
|
+
filtersApplied.push("state");
|
|
150
|
+
if (queryFilter)
|
|
151
|
+
filtersApplied.push("query");
|
|
152
|
+
const filtered = BONFIRE_ORGS.filter((o) => (!stateFilter || o.state.toUpperCase() === stateFilter) &&
|
|
153
|
+
(!queryFilter || o.name.toLowerCase().includes(queryFilter)));
|
|
154
|
+
const totalAvailable = filtered.length;
|
|
155
|
+
const page = filtered.slice(offset, offset + limit);
|
|
156
|
+
const returned = page.length;
|
|
157
|
+
const hasMore = offset + returned < totalAvailable;
|
|
158
|
+
return withMeta({ organizations: page }, {
|
|
159
|
+
source: "Bonfire (Euna) curated seed directory (keyless)",
|
|
160
|
+
keylessMode: true,
|
|
161
|
+
returned,
|
|
162
|
+
totalAvailable,
|
|
163
|
+
filtersApplied,
|
|
164
|
+
filtersDropped: [],
|
|
165
|
+
fieldsUnavailable: [],
|
|
166
|
+
pagination: { offset, limit, hasMore, nextOffset: hasMore ? offset + returned : null },
|
|
167
|
+
notes: [BONFIRE_SEED_NOTE],
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
export async function searchOpportunities(args) {
|
|
171
|
+
const limit = args.limit ?? 50;
|
|
172
|
+
const offset = args.offset ?? 0;
|
|
173
|
+
const org = args.org ?? "";
|
|
174
|
+
if (!BONFIRE_ORG_RE.test(org)) {
|
|
175
|
+
throw new ToolErrorCarrier({
|
|
176
|
+
kind: "invalid_input",
|
|
177
|
+
message: `Invalid Bonfire org ${JSON.stringify(org)} — expected a lowercase-alnum/hyphen subdomain slug (from bonfire_list_organizations, e.g. 'harriscountytx', 'u-46').`,
|
|
178
|
+
retryable: false,
|
|
179
|
+
upstreamEndpoint: bonfireLabel(org),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const body = await getBonfireRss(org); // getText THROWS on 429/5xx/404/timeout (P2)
|
|
183
|
+
// P4: a 200 body must be RSS (no <rss/<channel ⇒ drift; e.g. an error/HTML page).
|
|
184
|
+
if (!/<rss[\s>]/i.test(body) || !/<channel[\s>]/i.test(body)) {
|
|
185
|
+
throw driftError(bonfireLabel(org), "Bonfire returned a non-RSS body at HTTP 200 — schema drift (expected an <rss><channel> feed).");
|
|
186
|
+
}
|
|
187
|
+
// Tolerate attributes on the opening <item …> tag (consistent with the <channel[\s>]
|
|
188
|
+
// drift guard and the inner tag() matcher). RSS 2.0 <item> has no standard attributes,
|
|
189
|
+
// but a namespaced/extended feed could add them — a bare-<item> regex would silently
|
|
190
|
+
// DROP such items and undercount totalAvailable (a latent P1 risk caught in dogfooding).
|
|
191
|
+
const items = body.match(/<item(?:\s[^>]*)?>[\s\S]*?<\/item>/gi) ?? [];
|
|
192
|
+
const all = items.map(mapItem);
|
|
193
|
+
// P1: the RSS is the COMPLETE open set ⇒ totalAvailable = item count (true total).
|
|
194
|
+
const totalAvailable = all.length;
|
|
195
|
+
const pageRows = all.slice(offset, offset + limit);
|
|
196
|
+
const returned = pageRows.length;
|
|
197
|
+
const hasMore = offset + returned < totalAvailable;
|
|
198
|
+
return withMeta({ org, opportunities: pageRows }, {
|
|
199
|
+
source: BONFIRE_SOURCE(org),
|
|
200
|
+
keylessMode: true,
|
|
201
|
+
returned,
|
|
202
|
+
totalAvailable,
|
|
203
|
+
filtersApplied: ["org"],
|
|
204
|
+
filtersDropped: [],
|
|
205
|
+
fieldsUnavailable: [],
|
|
206
|
+
pagination: { offset, limit, hasMore, nextOffset: hasMore ? offset + returned : null },
|
|
207
|
+
notes: [
|
|
208
|
+
"The RSS is the COMPLETE set of the org's currently-OPEN opportunities (no server pagination) — totalAvailable is the exact open-opportunity count, and this tool pages over it client-side. An empty feed (returned 0) means the org has no open opportunities right now. closeDate is parsed best-effort from the description text.",
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=bonfire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bonfire.js","sourceRoot":"","sources":["../src/bonfire.ts"],"names":[],"mappings":"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"}
|
package/dist/cbp-border.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cbp-border.ts — CBP Border Wait Times (bwt.cbp.gov, KEYLESS) — the FREIGHT /
|
|
3
|
-
* LOGISTICS lane. Live
|
|
3
|
+
* LOGISTICS lane. Live COMMERCIAL-VEHICLE (freight-truck) wait times at every US
|
|
4
4
|
* land border port (Canadian + Mexican): per-port lane delays, operational status,
|
|
5
|
-
* and open-lane counts.
|
|
5
|
+
* and open-lane counts. (The raw feed also carries passenger/pedestrian lanes, but
|
|
6
|
+
* this tool surfaces ONLY the commercial-vehicle lanes — the freight lane.)
|
|
7
|
+
* Answers "what's the current commercial-truck delay at port
|
|
6
8
|
* X" — real-time freight-crossing situational awareness for logistics/trade vendors.
|
|
7
9
|
*
|
|
8
10
|
* SOURCE: CBP's official Border Wait Times API (bwt.cbp.gov/api/bwtnew) — a .gov host,
|
|
@@ -38,9 +40,11 @@ export type CbpPort = {
|
|
|
38
40
|
};
|
|
39
41
|
};
|
|
40
42
|
/**
|
|
41
|
-
* List CBP land-border-port
|
|
42
|
-
* filtered by border (Canadian/Mexican) and/or port name (substring
|
|
43
|
-
*
|
|
43
|
+
* List CBP land-border-port COMMERCIAL-VEHICLE (freight-truck) wait times, optionally
|
|
44
|
+
* filtered by border (Canadian/Mexican) and/or port name (substring, applied
|
|
45
|
+
* CLIENT-SIDE over the full fetched set and disclosed as such). Passenger/pedestrian
|
|
46
|
+
* lanes are NOT surfaced (freight lane only). Honest `_meta` (exact match total +
|
|
47
|
+
* real-time freshness).
|
|
44
48
|
*/
|
|
45
49
|
export declare function borderWaitTimes(args: {
|
|
46
50
|
border?: string;
|
package/dist/cbp-border.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cbp-border.d.ts","sourceRoot":"","sources":["../src/cbp-border.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cbp-border.d.ts","sourceRoot":"","sources":["../src/cbp-border.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAEzE,eAAO,MAAM,QAAQ,gBAAgB,CAAC;AA+BtC,MAAM,MAAM,OAAO,GAAG;IACpB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AACF,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iBAAiB,EAAE;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;CAClF,CAAC;AA2DF;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,UAAU,CAAC,CAuDtB"}
|