@cliwant/mcp-sam-gov 1.9.0 → 1.10.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/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 +208 -0
- package/dist/bonfire.js.map +1 -0
- 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/server.d.ts.map +1 -1
- package/dist/server.js +166 -1
- 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 +119 -18
- package/dist/socrata.js.map +1 -1
- package/package.json +1 -1
- package/src/arcgis-feature.ts +232 -0
- package/src/arcgis-hub.ts +270 -0
- package/src/bonfire.ts +245 -0
- package/src/opengov.ts +309 -0
- package/src/server.ts +178 -1
- package/src/socrata.ts +123 -18
|
@@ -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,CA6CtF"}
|
package/dist/bonfire.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
const items = body.match(/<item>[\s\S]*?<\/item>/gi) ?? [];
|
|
188
|
+
const all = items.map(mapItem);
|
|
189
|
+
// P1: the RSS is the COMPLETE open set ⇒ totalAvailable = item count (true total).
|
|
190
|
+
const totalAvailable = all.length;
|
|
191
|
+
const pageRows = all.slice(offset, offset + limit);
|
|
192
|
+
const returned = pageRows.length;
|
|
193
|
+
const hasMore = offset + returned < totalAvailable;
|
|
194
|
+
return withMeta({ org, opportunities: pageRows }, {
|
|
195
|
+
source: BONFIRE_SOURCE(org),
|
|
196
|
+
keylessMode: true,
|
|
197
|
+
returned,
|
|
198
|
+
totalAvailable,
|
|
199
|
+
filtersApplied: ["org"],
|
|
200
|
+
filtersDropped: [],
|
|
201
|
+
fieldsUnavailable: [],
|
|
202
|
+
pagination: { offset, limit, hasMore, nextOffset: hasMore ? offset + returned : null },
|
|
203
|
+
notes: [
|
|
204
|
+
"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.",
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=bonfire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bonfire.js","sourceRoot":"","sources":["../src/bonfire.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opengov.ts — OpenGov Procurement (formerly ProcureNow) public solicitations,
|
|
3
|
+
* a keyless-first SLED bid source (loop cycle — SLED bid campaign, 2026-07-19).
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: OpenGov Procurement hosts the live open-solicitation portals of
|
|
6
|
+
* **525+ US state/local governments** (cities, counties, school & special
|
|
7
|
+
* districts across 42 states + DC). The public portal SPA
|
|
8
|
+
* (`procurement.opengov.com/portal/{code}`) is served by an UNAUTHENTICATED
|
|
9
|
+
* backend REST API — the official key-gated `api-key` API is NOT used; this
|
|
10
|
+
* consumes ONLY the anonymous endpoints the public portal itself calls, so it is
|
|
11
|
+
* genuinely keyless (live-verified 2026-07-19: bare anonymous request, no api-key /
|
|
12
|
+
* auth header / cookie). It is the OpenGov sibling of the Socrata/CKAN/ArcGIS
|
|
13
|
+
* discovery tools and one of the two highest-reach keyless SLED bid feeds (with
|
|
14
|
+
* Bonfire RSS).
|
|
15
|
+
*
|
|
16
|
+
* The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
|
|
17
|
+
* (GET + POST/JSON via ADR-0014, redirect:"error") / `driftError` / `num`·`str`
|
|
18
|
+
* (coerce.ts, null-never-0 / null-never-empty) / `withMeta`·`buildMeta`,
|
|
19
|
+
* mirroring datagov-catalog.ts. KEYLESS: no key seam.
|
|
20
|
+
*
|
|
21
|
+
* ★ SSRF: the host is a compile-time literal (`OPENGOV_HOST`). All inputs ride in
|
|
22
|
+
* a MODULE-BUILT JSON body / URLSearchParams from validated typed args — NO
|
|
23
|
+
* raw-host/raw-path passthrough (this is a FIXED-host API). A post-construction
|
|
24
|
+
* hostname/protocol assertion + `redirect:"error"` lock every call.
|
|
25
|
+
*
|
|
26
|
+
* ★ TWO endpoints (both anonymous, live-verified):
|
|
27
|
+
* - `GET /api/v1/government` → a flat JSON array of ~560 org records (the whole
|
|
28
|
+
* directory in one call; `government.code` = the portal slug). We keep the
|
|
29
|
+
* ACTIVE, non-internal ones (~525 real public portals).
|
|
30
|
+
* - `POST /api/v1/project/list` body `{governmentCode, publicView:true, page,
|
|
31
|
+
* limit}` → `{projects:[…], count}`. `publicView:true` is REQUIRED (the public
|
|
32
|
+
* gate). `count` is the TRUE total of public projects for that org.
|
|
33
|
+
*
|
|
34
|
+
* ★ HONESTY PILLARS (captured live 2026-07-19):
|
|
35
|
+
* P1: totalAvailable = the real total — the filtered directory length (govt
|
|
36
|
+
* list) or the API's `count` (solicitations) — NEVER the page length.
|
|
37
|
+
* P2: getJson→fetchWithRetry THROWS on 429 / 5xx / timeout — NEVER a fake empty.
|
|
38
|
+
* A genuine no-match (projects:[], count:0) ⇒ honest empty.
|
|
39
|
+
* P3: every scalar via `str` (null-never-empty) / `num` (null-never-0). The
|
|
40
|
+
* `status` enum is surfaced VERBATIM (open = currently accepting; the response
|
|
41
|
+
* also carries pending/evaluation/closed — the CONSUMER filters; we do not
|
|
42
|
+
* hide non-open rows or fabricate an "open count").
|
|
43
|
+
* P4: `/government` non-array or `/project/list` `.projects` non-array ⇒
|
|
44
|
+
* driftError; a 200 non-JSON body ⇒ schema_drift via the catch-ladder
|
|
45
|
+
* (ToolErrorCarrier rethrow FIRST so a 429/5xx keeps its taxonomy).
|
|
46
|
+
*/
|
|
47
|
+
import { num } from "./coerce.js";
|
|
48
|
+
import { type MetaBundle } from "./meta.js";
|
|
49
|
+
export { num };
|
|
50
|
+
export declare const OPENGOV_HOST = "api.procurement.opengov.com";
|
|
51
|
+
export declare const OPENGOV_CODE_RE: RegExp;
|
|
52
|
+
export type OpengovGovernment = {
|
|
53
|
+
code: string | null;
|
|
54
|
+
name: string | null;
|
|
55
|
+
city: string | null;
|
|
56
|
+
state: string | null;
|
|
57
|
+
website: string | null;
|
|
58
|
+
};
|
|
59
|
+
export type OpengovSolicitation = {
|
|
60
|
+
id: number | null;
|
|
61
|
+
title: string | null;
|
|
62
|
+
solicitationNumber: string | null;
|
|
63
|
+
status: string | null;
|
|
64
|
+
type: string | null;
|
|
65
|
+
department: string | null;
|
|
66
|
+
releaseDate: string | null;
|
|
67
|
+
proposalDeadline: string | null;
|
|
68
|
+
contactName: string | null;
|
|
69
|
+
link: string | null;
|
|
70
|
+
};
|
|
71
|
+
export type OpengovListGovernmentsArgs = {
|
|
72
|
+
state?: string;
|
|
73
|
+
query?: string;
|
|
74
|
+
limit?: number;
|
|
75
|
+
offset?: number;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* List the OpenGov Procurement government portals (the whole directory arrives in
|
|
79
|
+
* ONE keyless GET). Client-side filters: `state` (2-letter), `query` (name
|
|
80
|
+
* substring). Only ACTIVE, non-internal portals are kept. Feeds `code` to
|
|
81
|
+
* opengov_search_solicitations. totalAvailable = the filtered directory size (P1).
|
|
82
|
+
*/
|
|
83
|
+
export declare function listGovernments(args: OpengovListGovernmentsArgs): Promise<MetaBundle>;
|
|
84
|
+
export type OpengovSearchSolicitationsArgs = {
|
|
85
|
+
governmentCode: string;
|
|
86
|
+
limit?: number;
|
|
87
|
+
offset?: number;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* List an OpenGov government's public solicitations (POST /project/list with the
|
|
91
|
+
* REQUIRED publicView gate). Returns all public projects with `status` surfaced
|
|
92
|
+
* (filter status==='open' for live bids). totalAvailable = the API's `count` (the
|
|
93
|
+
* org's total public-project count — P1, never the page length).
|
|
94
|
+
*/
|
|
95
|
+
export declare function searchSolicitations(args: OpengovSearchSolicitationsArgs): Promise<MetaBundle>;
|
|
96
|
+
//# sourceMappingURL=opengov.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opengov.d.ts","sourceRoot":"","sources":["../src/opengov.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAIH,OAAO,EAAE,GAAG,EAAO,MAAM,aAAa,CAAC;AACvC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAGzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAGf,eAAO,MAAM,YAAY,gCAAgC,CAAC;AAW1D,eAAO,MAAM,eAAe,QAAsB,CAAC;AAMnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AA6BF,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,UAAU,CAAC,CAuErB;AAGD,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,UAAU,CAAC,CAoFrB"}
|