@cyanheads/congressgov-mcp-server 0.3.18 → 0.3.19
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/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +5 -3
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +4 -0
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +11 -3
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.js +24 -9
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts +4 -0
- package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.js +45 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.js +20 -5
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-helpers.d.ts +2 -2
- package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/format-helpers.js +437 -27
- package/dist/mcp-server/tools/format-helpers.js.map +1 -1
- package/dist/services/congress-api/congress-api-service.d.ts.map +1 -1
- package/dist/services/congress-api/congress-api-service.js +64 -13
- package/dist/services/congress-api/congress-api-service.js.map +1 -1
- package/dist/services/congress-api/types.d.ts +1 -0
- package/dist/services/congress-api/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -14,16 +14,31 @@
|
|
|
14
14
|
function tb(content) {
|
|
15
15
|
return [{ type: 'text', text: content }];
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Strip HTML to plain text while preserving paragraph and line breaks. Upstream
|
|
19
|
+
* summary fields and other narrative bodies ship as HTML; we want the visible
|
|
20
|
+
* structure (paragraph boundaries) to survive into the rendered Markdown.
|
|
21
|
+
*
|
|
22
|
+
* Inline contexts that need single-line output should pass `{ inline: true }`.
|
|
23
|
+
*/
|
|
24
|
+
function stripHtml(html, { inline = false } = {}) {
|
|
25
|
+
const text = html
|
|
26
|
+
.replace(/<\s*br\s*\/?\s*>/gi, '\n')
|
|
27
|
+
.replace(/<\s*\/p\s*>/gi, '\n\n')
|
|
19
28
|
.replace(/<[^>]*>/g, '')
|
|
20
29
|
.replace(/ /g, ' ')
|
|
21
30
|
.replace(/'/g, "'")
|
|
22
31
|
.replace(/&/g, '&')
|
|
23
32
|
.replace(/</g, '<')
|
|
24
33
|
.replace(/>/g, '>')
|
|
25
|
-
.replace(/"/g, '"')
|
|
26
|
-
|
|
34
|
+
.replace(/"/g, '"');
|
|
35
|
+
if (inline)
|
|
36
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
37
|
+
return text
|
|
38
|
+
.replace(/[ \t]+/g, ' ')
|
|
39
|
+
.replace(/\n[ \t]+/g, '\n')
|
|
40
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
41
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
27
42
|
.trim();
|
|
28
43
|
}
|
|
29
44
|
/**
|
|
@@ -54,7 +69,7 @@ function htmlToMarkdown(html) {
|
|
|
54
69
|
.replace(/\n{3,}/g, '\n\n')
|
|
55
70
|
.trim();
|
|
56
71
|
}
|
|
57
|
-
/** Safe deep access
|
|
72
|
+
/** Safe deep access for compact field display — collapses whitespace to a single line. */
|
|
58
73
|
function s(obj, ...path) {
|
|
59
74
|
let cur = obj;
|
|
60
75
|
for (const key of path) {
|
|
@@ -63,7 +78,7 @@ function s(obj, ...path) {
|
|
|
63
78
|
cur = cur[key];
|
|
64
79
|
}
|
|
65
80
|
if (typeof cur === 'string')
|
|
66
|
-
return stripHtml(cur);
|
|
81
|
+
return stripHtml(cur, { inline: true });
|
|
67
82
|
if (typeof cur === 'number')
|
|
68
83
|
return String(cur);
|
|
69
84
|
return;
|
|
@@ -85,11 +100,11 @@ function pagHeader(result) {
|
|
|
85
100
|
return `**${count} result${count !== 1 ? 's' : ''}**${next != null ? ` | next offset: ${next}` : ''}`;
|
|
86
101
|
}
|
|
87
102
|
/** Render a paginated list with header and per-item rendering. */
|
|
88
|
-
function renderList(result, renderItem) {
|
|
103
|
+
function renderList(result, renderItem, emptyHint) {
|
|
89
104
|
const items = (result.data ?? []);
|
|
90
105
|
const header = pagHeader(result);
|
|
91
106
|
if (items.length === 0) {
|
|
92
|
-
return `${header}\n\
|
|
107
|
+
return emptyHint ? `${header}\n\n${emptyHint}` : header;
|
|
93
108
|
}
|
|
94
109
|
const renderer = renderItem ?? renderGenericItem;
|
|
95
110
|
const rendered = items.map((item, i) => typeof item === 'object' && item !== null
|
|
@@ -118,13 +133,13 @@ function renderDetail(obj) {
|
|
|
118
133
|
if (val == null || val === '')
|
|
119
134
|
continue;
|
|
120
135
|
if (typeof val === 'string') {
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
136
|
+
const inline = stripHtml(val, { inline: true });
|
|
137
|
+
if (inline.length > 300) {
|
|
123
138
|
lines.push(`**${key}:**`);
|
|
124
|
-
lines.push(
|
|
139
|
+
lines.push(stripHtml(val));
|
|
125
140
|
}
|
|
126
141
|
else {
|
|
127
|
-
lines.push(`**${key}:** ${
|
|
142
|
+
lines.push(`**${key}:** ${inline}`);
|
|
128
143
|
}
|
|
129
144
|
}
|
|
130
145
|
else if (typeof val === 'number' || typeof val === 'boolean') {
|
|
@@ -172,7 +187,7 @@ function renderDetail(obj) {
|
|
|
172
187
|
if (v2 == null || v2 === '')
|
|
173
188
|
continue;
|
|
174
189
|
if (typeof v2 === 'string')
|
|
175
|
-
lines.push(` **${k2}:** ${stripHtml(v2)}`);
|
|
190
|
+
lines.push(` **${k2}:** ${stripHtml(v2, { inline: true })}`);
|
|
176
191
|
else if (typeof v2 === 'number' || typeof v2 === 'boolean')
|
|
177
192
|
lines.push(` **${k2}:** ${v2}`);
|
|
178
193
|
else if (typeof v2 === 'object' && v2)
|
|
@@ -203,13 +218,13 @@ function renderGenericItem(item, index) {
|
|
|
203
218
|
if (HEADING_FIELDS.has(key))
|
|
204
219
|
continue;
|
|
205
220
|
if (typeof val === 'string') {
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
221
|
+
const inline = stripHtml(val, { inline: true });
|
|
222
|
+
if (inline.length > 300) {
|
|
208
223
|
lines.push(`**${key}:**`);
|
|
209
|
-
lines.push(
|
|
224
|
+
lines.push(stripHtml(val));
|
|
210
225
|
}
|
|
211
226
|
else {
|
|
212
|
-
lines.push(`**${key}:** ${
|
|
227
|
+
lines.push(`**${key}:** ${inline}`);
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
230
|
else if (typeof val === 'number' || typeof val === 'boolean') {
|
|
@@ -262,7 +277,7 @@ function renderInline(obj) {
|
|
|
262
277
|
if (val == null || val === '')
|
|
263
278
|
continue;
|
|
264
279
|
if (typeof val === 'string') {
|
|
265
|
-
const cleaned = stripHtml(val);
|
|
280
|
+
const cleaned = stripHtml(val, { inline: true });
|
|
266
281
|
const preview = cleaned.length > 120 ? `${cleaned.slice(0, 117)}...` : cleaned;
|
|
267
282
|
parts.push(`${key}: ${preview}`);
|
|
268
283
|
}
|
|
@@ -390,7 +405,8 @@ function renderCrsReportItem(item, i) {
|
|
|
390
405
|
]);
|
|
391
406
|
if (meta)
|
|
392
407
|
lines.push(meta);
|
|
393
|
-
|
|
408
|
+
if (summary)
|
|
409
|
+
lines.push(summary);
|
|
394
410
|
if (url)
|
|
395
411
|
lines.push(`**URL:** ${url}`);
|
|
396
412
|
return lines.join('\n');
|
|
@@ -522,6 +538,348 @@ function renderCommitteeReportTextItem(item, i) {
|
|
|
522
538
|
}
|
|
523
539
|
return lines.join('\n');
|
|
524
540
|
}
|
|
541
|
+
/** Member-sponsored amendments — `type`/`title` are null upstream; identify by `amendmentNumber`. */
|
|
542
|
+
function renderAmendmentItem(item, i) {
|
|
543
|
+
const number = s(item, 'amendmentNumber');
|
|
544
|
+
const url = s(item, 'url') ?? '';
|
|
545
|
+
/** URL path carries the chamber prefix (samdt / hamdt) we need for a readable type label. */
|
|
546
|
+
const amdMatch = url.match(/\/amendment\/(\d+)\/(samdt|hamdt|suamdt|huamdt)\//i);
|
|
547
|
+
const typeCode = amdMatch?.[2]?.toLowerCase();
|
|
548
|
+
const chamber = typeCode === 'samdt' || typeCode === 'suamdt'
|
|
549
|
+
? 'Senate Amendment'
|
|
550
|
+
: typeCode === 'hamdt' || typeCode === 'huamdt'
|
|
551
|
+
? 'House Amendment'
|
|
552
|
+
: 'Amendment';
|
|
553
|
+
const heading = number ? `${chamber} ${number}` : 'Amendment';
|
|
554
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
555
|
+
const meta = join([
|
|
556
|
+
f('Congress', s(item, 'congress')),
|
|
557
|
+
f('Introduced', s(item, 'introducedDate')),
|
|
558
|
+
]);
|
|
559
|
+
if (meta)
|
|
560
|
+
lines.push(meta);
|
|
561
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
562
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
563
|
+
if (actionDate || actionText)
|
|
564
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
565
|
+
if (url)
|
|
566
|
+
lines.push(`**URL:** ${url}`);
|
|
567
|
+
return lines.join('\n');
|
|
568
|
+
}
|
|
569
|
+
/** Bill text versions — heading from `type` (e.g. "Enrolled Bill"), formats[] as labeled URLs. */
|
|
570
|
+
function renderBillTextItem(item, i) {
|
|
571
|
+
const type = s(item, 'type') ?? 'Bill Text';
|
|
572
|
+
const date = s(item, 'date');
|
|
573
|
+
const lines = [`### ${i + 1}. ${type}`];
|
|
574
|
+
if (date)
|
|
575
|
+
lines.push(`**Date:** ${date}`);
|
|
576
|
+
const formats = item.formats;
|
|
577
|
+
if (Array.isArray(formats)) {
|
|
578
|
+
for (const fmt of formats) {
|
|
579
|
+
const fType = s(fmt, 'type');
|
|
580
|
+
const fUrl = s(fmt, 'url');
|
|
581
|
+
if (fType && fUrl)
|
|
582
|
+
lines.push(`**${fType}:** ${fUrl}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return lines.join('\n');
|
|
586
|
+
}
|
|
587
|
+
/** Nomination type wrapper: `{isCivilian: true}` / `{isMilitary: true}` → readable label. */
|
|
588
|
+
function nominationTypeLabel(raw) {
|
|
589
|
+
if (!raw || typeof raw !== 'object')
|
|
590
|
+
return;
|
|
591
|
+
const t = raw;
|
|
592
|
+
if (t.isCivilian === true)
|
|
593
|
+
return 'Civilian';
|
|
594
|
+
if (t.isMilitary === true)
|
|
595
|
+
return 'Military';
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
function nominationHeading(item) {
|
|
599
|
+
const citation = s(item, 'citation');
|
|
600
|
+
if (citation)
|
|
601
|
+
return citation;
|
|
602
|
+
const number = s(item, 'number');
|
|
603
|
+
const partNumber = s(item, 'partNumber');
|
|
604
|
+
if (number && partNumber && partNumber !== '00')
|
|
605
|
+
return `PN${number}-${Number(partNumber)}`;
|
|
606
|
+
if (number)
|
|
607
|
+
return `PN${number}`;
|
|
608
|
+
return 'Nomination';
|
|
609
|
+
}
|
|
610
|
+
function renderNominationListItem(item, i) {
|
|
611
|
+
const heading = nominationHeading(item);
|
|
612
|
+
const type = nominationTypeLabel(item.nominationType);
|
|
613
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
614
|
+
const description = s(item, 'description');
|
|
615
|
+
if (description)
|
|
616
|
+
lines.push(description);
|
|
617
|
+
const meta = join([
|
|
618
|
+
f('Congress', s(item, 'congress')),
|
|
619
|
+
f('Type', type),
|
|
620
|
+
f('Received', s(item, 'receivedDate')),
|
|
621
|
+
f('Authority Date', s(item, 'authorityDate')),
|
|
622
|
+
f('Updated', s(item, 'updateDate')),
|
|
623
|
+
]);
|
|
624
|
+
if (meta)
|
|
625
|
+
lines.push(meta);
|
|
626
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
627
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
628
|
+
if (actionDate || actionText)
|
|
629
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
630
|
+
const url = s(item, 'url');
|
|
631
|
+
if (url)
|
|
632
|
+
lines.push(`**URL:** ${url}`);
|
|
633
|
+
return lines.join('\n');
|
|
634
|
+
}
|
|
635
|
+
function renderNominationDetail(item) {
|
|
636
|
+
const heading = nominationHeading(item);
|
|
637
|
+
const type = nominationTypeLabel(item.nominationType);
|
|
638
|
+
const lines = [`# ${heading}`];
|
|
639
|
+
const description = s(item, 'description');
|
|
640
|
+
if (description)
|
|
641
|
+
lines.push(description);
|
|
642
|
+
const meta = join([
|
|
643
|
+
f('Congress', s(item, 'congress')),
|
|
644
|
+
f('Type', type),
|
|
645
|
+
f('Part Number', s(item, 'partNumber')),
|
|
646
|
+
f('Received', s(item, 'receivedDate')),
|
|
647
|
+
f('Authority Date', s(item, 'authorityDate')),
|
|
648
|
+
f('Updated', s(item, 'updateDate')),
|
|
649
|
+
]);
|
|
650
|
+
if (meta)
|
|
651
|
+
lines.push(meta);
|
|
652
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
653
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
654
|
+
if (actionDate || actionText)
|
|
655
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
656
|
+
const subResources = [];
|
|
657
|
+
for (const key of ['actions', 'committees', 'hearings']) {
|
|
658
|
+
const sub = item[key];
|
|
659
|
+
if (sub && typeof sub.count === 'number' && sub.count > 0)
|
|
660
|
+
subResources.push(`${sub.count} ${key}`);
|
|
661
|
+
}
|
|
662
|
+
if (subResources.length)
|
|
663
|
+
lines.push(`**Available:** ${subResources.join(', ')}`);
|
|
664
|
+
const nominees = item.nominees;
|
|
665
|
+
if (Array.isArray(nominees) && nominees.length > 0) {
|
|
666
|
+
lines.push(`\n**Nominees (${nominees.length}):**`);
|
|
667
|
+
for (const n of nominees.slice(0, 20)) {
|
|
668
|
+
const ord = s(n, 'ordinal');
|
|
669
|
+
const count = s(n, 'nomineeCount');
|
|
670
|
+
const org = s(n, 'organization');
|
|
671
|
+
const title = s(n, 'positionTitle');
|
|
672
|
+
const parts = [
|
|
673
|
+
ord ? `Ord ${ord}` : undefined,
|
|
674
|
+
count ? `${count} nominee(s)` : undefined,
|
|
675
|
+
org,
|
|
676
|
+
title,
|
|
677
|
+
].filter(Boolean);
|
|
678
|
+
lines.push(`- ${parts.join(' — ')}`);
|
|
679
|
+
}
|
|
680
|
+
if (nominees.length > 20)
|
|
681
|
+
lines.push(`- _...${nominees.length - 20} more_`);
|
|
682
|
+
}
|
|
683
|
+
else if (s(item, 'partNumber') === '00') {
|
|
684
|
+
/** Parent nominations (partNumber=00) have no nominees array. Sub-resources
|
|
685
|
+
* also return 0 results — they live on the partitioned children (PN851-1, PN851-2, …). */
|
|
686
|
+
lines.push('\n_This is a parent nomination. Individual nominees and confirmation actions live on partitioned children (e.g. `PN851-1`, `PN851-2`). Use the partitioned form for `actions`, `committees`, `hearings`, or `nominees`._');
|
|
687
|
+
}
|
|
688
|
+
const url = s(item, 'url');
|
|
689
|
+
if (url)
|
|
690
|
+
lines.push(`\n**URL:** ${url}`);
|
|
691
|
+
return lines.join('\n');
|
|
692
|
+
}
|
|
693
|
+
/** Roll call vote detail — question, result, date, party totals. */
|
|
694
|
+
function renderRollVoteDetail(item) {
|
|
695
|
+
const roll = s(item, 'rollCallNumber');
|
|
696
|
+
const congress = s(item, 'congress');
|
|
697
|
+
const session = s(item, 'sessionNumber');
|
|
698
|
+
const result = s(item, 'result');
|
|
699
|
+
const question = s(item, 'voteQuestion');
|
|
700
|
+
const voteType = s(item, 'voteType');
|
|
701
|
+
const startDate = s(item, 'startDate');
|
|
702
|
+
const updated = s(item, 'updateDate');
|
|
703
|
+
const identifier = s(item, 'identifier');
|
|
704
|
+
const sourceUrl = s(item, 'sourceDataURL');
|
|
705
|
+
const headingLeft = roll ? `Roll ${roll}` : 'Roll call';
|
|
706
|
+
const heading = result ? `${headingLeft} — ${result}` : headingLeft;
|
|
707
|
+
const lines = [`# ${heading}`];
|
|
708
|
+
if (question)
|
|
709
|
+
lines.push(`**Question:** ${question}`);
|
|
710
|
+
const meta = join([
|
|
711
|
+
f('Congress', congress),
|
|
712
|
+
f('Session', session),
|
|
713
|
+
f('Type', voteType),
|
|
714
|
+
f('Date', startDate),
|
|
715
|
+
f('Updated', updated),
|
|
716
|
+
identifier && identifier !== roll ? f('ID', identifier) : undefined,
|
|
717
|
+
]);
|
|
718
|
+
if (meta)
|
|
719
|
+
lines.push(meta);
|
|
720
|
+
const totals = item.votePartyTotal;
|
|
721
|
+
if (Array.isArray(totals) && totals.length > 0) {
|
|
722
|
+
lines.push('\n**Party Totals:**');
|
|
723
|
+
for (const t of totals) {
|
|
724
|
+
const party = s(t, 'party', 'name') ?? s(t, 'voteParty') ?? '?';
|
|
725
|
+
const yea = s(t, 'yeaTotal') ?? '0';
|
|
726
|
+
const nay = s(t, 'nayTotal') ?? '0';
|
|
727
|
+
const present = s(t, 'presentTotal') ?? '0';
|
|
728
|
+
const notVoting = s(t, 'notVotingTotal') ?? '0';
|
|
729
|
+
lines.push(`- **${party}:** Yea ${yea}, Nay ${nay}, Present ${present}, Not Voting ${notVoting}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const results = item.results;
|
|
733
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
734
|
+
lines.push(`\n**Member Votes:** ${results.length} on this page`);
|
|
735
|
+
for (const r of results.slice(0, 20)) {
|
|
736
|
+
const name = s(r, 'firstName') && s(r, 'lastName')
|
|
737
|
+
? `${s(r, 'firstName')} ${s(r, 'lastName')}`
|
|
738
|
+
: (s(r, 'bioguideId') ?? '?');
|
|
739
|
+
const vote = s(r, 'voteCast');
|
|
740
|
+
const party = s(r, 'voteParty');
|
|
741
|
+
const state = s(r, 'voteState');
|
|
742
|
+
const parts = [
|
|
743
|
+
name,
|
|
744
|
+
party && state ? `(${party}-${state})` : undefined,
|
|
745
|
+
vote ? `→ ${vote}` : undefined,
|
|
746
|
+
].filter(Boolean);
|
|
747
|
+
lines.push(`- ${parts.join(' ')}`);
|
|
748
|
+
}
|
|
749
|
+
if (results.length > 20)
|
|
750
|
+
lines.push(`- _...${results.length - 20} more_`);
|
|
751
|
+
}
|
|
752
|
+
if (sourceUrl)
|
|
753
|
+
lines.push(`\n**Source Data URL:** ${sourceUrl}`);
|
|
754
|
+
return lines.join('\n');
|
|
755
|
+
}
|
|
756
|
+
/** Member detail. */
|
|
757
|
+
function renderMemberDetail(item) {
|
|
758
|
+
const name = s(item, 'directOrderName') ??
|
|
759
|
+
s(item, 'invertedOrderName') ??
|
|
760
|
+
s(item, 'bioguideId') ??
|
|
761
|
+
'Unknown';
|
|
762
|
+
const lines = [`# ${name}`];
|
|
763
|
+
const meta = join([
|
|
764
|
+
f('ID', s(item, 'bioguideId')),
|
|
765
|
+
f('Party', s(item, 'partyName') ?? s(item, 'currentParty')),
|
|
766
|
+
f('State', s(item, 'state')),
|
|
767
|
+
item.district != null ? f('District', s(item, 'district')) : undefined,
|
|
768
|
+
f('Currently Serving', typeof item.currentMember === 'boolean' ? String(item.currentMember) : undefined),
|
|
769
|
+
f('Birth Year', s(item, 'birthYear')),
|
|
770
|
+
f('Updated', s(item, 'updateDate')),
|
|
771
|
+
]);
|
|
772
|
+
if (meta)
|
|
773
|
+
lines.push(meta);
|
|
774
|
+
const honorific = s(item, 'honorificName');
|
|
775
|
+
if (honorific)
|
|
776
|
+
lines.push(`**Honorific:** ${honorific}`);
|
|
777
|
+
/** terms may be a direct array or nested as `{item: [...]}`. */
|
|
778
|
+
const rawTerms = item.terms;
|
|
779
|
+
const termsArr = Array.isArray(rawTerms)
|
|
780
|
+
? rawTerms
|
|
781
|
+
: rawTerms &&
|
|
782
|
+
typeof rawTerms === 'object' &&
|
|
783
|
+
Array.isArray(rawTerms.item)
|
|
784
|
+
? rawTerms.item
|
|
785
|
+
: undefined;
|
|
786
|
+
if (termsArr && termsArr.length > 0) {
|
|
787
|
+
lines.push(`\n**Terms (${termsArr.length}):**`);
|
|
788
|
+
const recent = termsArr.slice(-5);
|
|
789
|
+
for (const term of recent) {
|
|
790
|
+
const chamber = s(term, 'chamber');
|
|
791
|
+
const start = s(term, 'startYear');
|
|
792
|
+
const end = s(term, 'endYear');
|
|
793
|
+
const party = s(term, 'partyName');
|
|
794
|
+
const stateName = s(term, 'stateName');
|
|
795
|
+
const range = start && end ? `${start}–${end}` : start;
|
|
796
|
+
const parts = [chamber, range, party, stateName].filter(Boolean);
|
|
797
|
+
lines.push(`- ${parts.join(', ')}`);
|
|
798
|
+
}
|
|
799
|
+
if (termsArr.length > 5)
|
|
800
|
+
lines.push(`- _...${termsArr.length - 5} earlier_`);
|
|
801
|
+
}
|
|
802
|
+
const partyHistory = item.partyHistory;
|
|
803
|
+
if (Array.isArray(partyHistory) && partyHistory.length > 0) {
|
|
804
|
+
lines.push(`\n**Party History:**`);
|
|
805
|
+
for (const p of partyHistory) {
|
|
806
|
+
const partyName = s(p, 'partyName');
|
|
807
|
+
const start = s(p, 'startYear');
|
|
808
|
+
const end = s(p, 'endYear');
|
|
809
|
+
const range = start && end ? `${start}–${end}` : start;
|
|
810
|
+
const parts = [partyName, range && `(${range})`].filter(Boolean);
|
|
811
|
+
lines.push(`- ${parts.join(' ')}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
const leadership = item.leadership;
|
|
815
|
+
if (Array.isArray(leadership) && leadership.length > 0) {
|
|
816
|
+
lines.push(`\n**Leadership Roles (${leadership.length}):**`);
|
|
817
|
+
for (const l of leadership.slice(0, 10)) {
|
|
818
|
+
const type = s(l, 'type');
|
|
819
|
+
const congress = s(l, 'congress');
|
|
820
|
+
lines.push(`- ${[type, congress ? `Congress ${congress}` : undefined].filter(Boolean).join(' — ')}`);
|
|
821
|
+
}
|
|
822
|
+
if (leadership.length > 10)
|
|
823
|
+
lines.push(`- _...${leadership.length - 10} more_`);
|
|
824
|
+
}
|
|
825
|
+
const subResources = [];
|
|
826
|
+
for (const key of ['sponsoredLegislation', 'cosponsoredLegislation']) {
|
|
827
|
+
const sub = item[key];
|
|
828
|
+
if (sub && typeof sub.count === 'number' && sub.count > 0) {
|
|
829
|
+
const label = key === 'sponsoredLegislation' ? 'sponsored' : 'cosponsored';
|
|
830
|
+
subResources.push(`${sub.count} ${label}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (subResources.length)
|
|
834
|
+
lines.push(`\n**Legislation:** ${subResources.join(', ')}`);
|
|
835
|
+
const url = s(item, 'url');
|
|
836
|
+
if (url)
|
|
837
|
+
lines.push(`\n**URL:** ${url}`);
|
|
838
|
+
return lines.join('\n');
|
|
839
|
+
}
|
|
840
|
+
/** Committee list item — name + key fields. */
|
|
841
|
+
function renderCommitteeListItem(item, i) {
|
|
842
|
+
const name = s(item, 'name') ?? s(item, 'systemCode') ?? 'Committee';
|
|
843
|
+
const lines = [`### ${i + 1}. ${name}`];
|
|
844
|
+
const meta = join([
|
|
845
|
+
f('Code', s(item, 'systemCode')),
|
|
846
|
+
f('Chamber', s(item, 'chamber')),
|
|
847
|
+
f('Type', s(item, 'committeeTypeCode')),
|
|
848
|
+
f('Updated', s(item, 'updateDate')),
|
|
849
|
+
]);
|
|
850
|
+
if (meta)
|
|
851
|
+
lines.push(meta);
|
|
852
|
+
const url = s(item, 'url');
|
|
853
|
+
if (url)
|
|
854
|
+
lines.push(`**URL:** ${url}`);
|
|
855
|
+
return lines.join('\n');
|
|
856
|
+
}
|
|
857
|
+
/** Committee report list item — citation-first; upstream omits title and bill ref. */
|
|
858
|
+
function renderCommitteeReportListItem(item, i) {
|
|
859
|
+
const citation = s(item, 'citation');
|
|
860
|
+
const type = s(item, 'type');
|
|
861
|
+
const number = s(item, 'number');
|
|
862
|
+
const part = s(item, 'part');
|
|
863
|
+
const congress = s(item, 'congress');
|
|
864
|
+
const chamber = s(item, 'chamber');
|
|
865
|
+
const updated = s(item, 'updateDate');
|
|
866
|
+
const url = s(item, 'url');
|
|
867
|
+
const heading = citation ?? (type && number ? `${type} ${congress ?? ''}-${number}` : 'Committee Report');
|
|
868
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
869
|
+
const meta = join([
|
|
870
|
+
f('Congress', congress),
|
|
871
|
+
f('Chamber', chamber),
|
|
872
|
+
f('Type', type),
|
|
873
|
+
f('Number', number),
|
|
874
|
+
part && part !== '1' ? f('Part', part) : undefined,
|
|
875
|
+
f('Updated', updated),
|
|
876
|
+
]);
|
|
877
|
+
if (meta)
|
|
878
|
+
lines.push(meta);
|
|
879
|
+
if (url)
|
|
880
|
+
lines.push(`**URL:** ${url}`);
|
|
881
|
+
return lines.join('\n');
|
|
882
|
+
}
|
|
525
883
|
// ── Per-Tool Format Exports ─────────────────────────────────────────
|
|
526
884
|
function makeFormatter(detailKeys, itemRenderer) {
|
|
527
885
|
return (result) => {
|
|
@@ -546,10 +904,35 @@ export function formatBills(result) {
|
|
|
546
904
|
return tb(renderDetail(result.bill));
|
|
547
905
|
return tb(renderDetail(result));
|
|
548
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Bill sub-resource summary item — known shape (no nested `bill.*`, since the
|
|
909
|
+
* caller already has the bill). Reuses `htmlToMarkdown` so `<p>` / `<strong>`
|
|
910
|
+
* survive into the rendered Markdown.
|
|
911
|
+
*/
|
|
912
|
+
function renderBillSubresourceSummaryItem(item, i) {
|
|
913
|
+
const version = s(item, 'actionDesc') ?? s(item, 'versionCode') ?? 'Summary';
|
|
914
|
+
const actionDate = s(item, 'actionDate');
|
|
915
|
+
const updated = s(item, 'updateDate');
|
|
916
|
+
const lines = [`### ${i + 1}. ${version}`];
|
|
917
|
+
const meta = join([f('Action Date', actionDate), f('Summary Updated', updated)]);
|
|
918
|
+
if (meta)
|
|
919
|
+
lines.push(meta);
|
|
920
|
+
const text = typeof item.text === 'string' ? htmlToMarkdown(item.text) : '';
|
|
921
|
+
if (text)
|
|
922
|
+
lines.push('', text);
|
|
923
|
+
return lines.join('\n');
|
|
924
|
+
}
|
|
549
925
|
function pickBillListRenderer(first) {
|
|
550
926
|
if ('title' in first && 'number' in first)
|
|
551
927
|
return renderBillItem;
|
|
552
|
-
|
|
928
|
+
/** Bill text versions: `type` + `formats[]`, no `actionDate`. */
|
|
929
|
+
if ('type' in first && 'formats' in first)
|
|
930
|
+
return renderBillTextItem;
|
|
931
|
+
/** Bill sub-resource summaries: `actionDesc`/`versionCode` + `text`, no `actionCode`/`sourceSystem`. */
|
|
932
|
+
if ('text' in first && ('actionDesc' in first || 'versionCode' in first)) {
|
|
933
|
+
return renderBillSubresourceSummaryItem;
|
|
934
|
+
}
|
|
935
|
+
/** Actions always ship a `text` body; most also carry actionDate/actionCode/sourceSystem. */
|
|
553
936
|
if ('text' in first &&
|
|
554
937
|
('actionDate' in first || 'actionCode' in first || 'sourceSystem' in first))
|
|
555
938
|
return renderBillActionItem;
|
|
@@ -564,12 +947,18 @@ export function formatMembers(result) {
|
|
|
564
947
|
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
565
948
|
if (firstRecord && 'bioguideId' in firstRecord)
|
|
566
949
|
return tb(renderList(result, renderMemberItem));
|
|
567
|
-
|
|
568
|
-
|
|
950
|
+
/** Sponsored/cosponsored may mix bills (type+title) and amendments (amendmentNumber, null type/title).
|
|
951
|
+
* Dispatch per-row so amendments don't render as 'Untitled'. */
|
|
952
|
+
if (firstRecord && ('number' in firstRecord || 'amendmentNumber' in firstRecord)) {
|
|
953
|
+
const dispatch = (item, i) => 'amendmentNumber' in item && item.amendmentNumber != null
|
|
954
|
+
? renderAmendmentItem(item, i)
|
|
955
|
+
: renderBillItem(item, i);
|
|
956
|
+
return tb(renderList(result, dispatch));
|
|
957
|
+
}
|
|
569
958
|
return tb(renderList(result));
|
|
570
959
|
}
|
|
571
960
|
if (result.member != null)
|
|
572
|
-
return tb(
|
|
961
|
+
return tb(renderMemberDetail(result.member));
|
|
573
962
|
return tb(renderDetail(result));
|
|
574
963
|
}
|
|
575
964
|
/** Pull the committee's display name from nested history when the top-level `name` is missing. */
|
|
@@ -584,8 +973,15 @@ function extractCommitteeName(committee) {
|
|
|
584
973
|
}
|
|
585
974
|
/** Committee browse, detail, and sub-resources (bills, reports, nominations). */
|
|
586
975
|
export function formatCommittees(result) {
|
|
587
|
-
if (Array.isArray(result.data))
|
|
976
|
+
if (Array.isArray(result.data)) {
|
|
977
|
+
const first = result.data[0];
|
|
978
|
+
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
979
|
+
/** Committee list rows have `systemCode` + `name`. Sub-resource rows
|
|
980
|
+
* (bills/reports/nominations) keep their generic / specialized renderers. */
|
|
981
|
+
if (firstRecord && 'systemCode' in firstRecord && 'name' in firstRecord)
|
|
982
|
+
return tb(renderList(result, renderCommitteeListItem));
|
|
588
983
|
return tb(renderList(result));
|
|
984
|
+
}
|
|
589
985
|
if (result.committee != null) {
|
|
590
986
|
const committee = result.committee;
|
|
591
987
|
const name = extractCommitteeName(committee);
|
|
@@ -597,7 +993,7 @@ export function formatCommittees(result) {
|
|
|
597
993
|
/** Committee reports — list, detail, and text. */
|
|
598
994
|
export function formatCommitteeReports(result) {
|
|
599
995
|
if (Array.isArray(result.data))
|
|
600
|
-
return tb(renderList(result));
|
|
996
|
+
return tb(renderList(result, renderCommitteeReportListItem));
|
|
601
997
|
if (Array.isArray(result.text)) {
|
|
602
998
|
const textResult = { data: result.text, pagination: { count: result.text.length } };
|
|
603
999
|
return tb(renderList(textResult, renderCommitteeReportTextItem));
|
|
@@ -632,7 +1028,21 @@ export function formatDailyRecord(result) {
|
|
|
632
1028
|
/** Enacted public and private laws. */
|
|
633
1029
|
export const formatLaws = makeFormatter(['law'], renderBillItem);
|
|
634
1030
|
/** House roll call votes and member voting positions. */
|
|
635
|
-
export
|
|
1031
|
+
export function formatVotes(result) {
|
|
1032
|
+
if (Array.isArray(result.data))
|
|
1033
|
+
return tb(renderList(result, renderRollVoteItem));
|
|
1034
|
+
if (result.vote != null)
|
|
1035
|
+
return tb(renderRollVoteDetail(result.vote));
|
|
1036
|
+
return tb(renderDetail(result));
|
|
1037
|
+
}
|
|
636
1038
|
/** Presidential nominations and Senate confirmation pipeline. */
|
|
637
|
-
export
|
|
1039
|
+
export function formatNominations(result) {
|
|
1040
|
+
if (Array.isArray(result.data)) {
|
|
1041
|
+
const hint = typeof result.emptyHint === 'string' ? result.emptyHint : undefined;
|
|
1042
|
+
return tb(renderList(result, renderNominationListItem, hint));
|
|
1043
|
+
}
|
|
1044
|
+
if (result.nomination != null)
|
|
1045
|
+
return tb(renderNominationDetail(result.nomination));
|
|
1046
|
+
return tb(renderDetail(result));
|
|
1047
|
+
}
|
|
638
1048
|
//# sourceMappingURL=format-helpers.js.map
|