@kivimedia/kmhub 2.9.1 → 2.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.md +170 -170
- package/bin/kmhub.mjs +896 -896
- package/coach-book-output-guard.mjs +760 -760
- package/index.mjs +57 -57
- package/package.json +56 -56
- package/prompts/briefing.md +29 -29
- package/prompts/luxury.md +70 -70
- package/prompts/play.md +49 -49
- package/prompts/run.md +37 -36
- package/prompts/setup.md +33 -33
- package/prompts/vs-booked.md +46 -46
- package/prompts/what-can-you-do.md +40 -40
- package/prompts.mjs +110 -110
- package/read-only-tools.json +143 -142
- package/remote.mjs +929 -929
- package/tools/balloon-costing.mjs +80 -80
- package/tools/booking-equipment.mjs +110 -110
- package/tools/bridges.mjs +54 -54
- package/tools/briefing.mjs +91 -91
- package/tools/calendar.mjs +170 -170
- package/tools/capabilities.mjs +155 -155
- package/tools/catalog.mjs +288 -288
- package/tools/clubs.mjs +176 -176
- package/tools/coach.mjs +771 -771
- package/tools/compare.mjs +76 -76
- package/tools/core.mjs +244 -244
- package/tools/crm.mjs +209 -209
- package/tools/dubsado.mjs +137 -137
- package/tools/exports.mjs +128 -128
- package/tools/fact-review.mjs +125 -125
- package/tools/flows.mjs +261 -261
- package/tools/forms.mjs +158 -158
- package/tools/gols.mjs +144 -134
- package/tools/hr.mjs +162 -162
- package/tools/knowledge.mjs +129 -125
- package/tools/marketing.mjs +396 -396
- package/tools/meta.mjs +245 -245
- package/tools/military.mjs +244 -244
- package/tools/money.mjs +235 -197
- package/tools/outreach.mjs +238 -238
- package/tools/pending.mjs +122 -122
- package/tools/photos.mjs +140 -140
- package/tools/plays.mjs +244 -244
- package/tools/profile.mjs +118 -118
- package/tools/radar.mjs +173 -173
- package/tools/recurring-invoices.mjs +149 -149
- package/tools/reengage.mjs +434 -434
- package/tools/schedules.mjs +55 -55
- package/tools/setup.mjs +168 -168
- package/tools/sops-bridges.mjs +86 -86
- package/tools/sops.mjs +314 -314
- package/tools/sourcing.mjs +268 -268
- package/tools/strategy.mjs +146 -146
- package/tools/studio.mjs +132 -132
- package/tools/venueradar.mjs +151 -151
- package/tools/voice.mjs +137 -134
- package/tools.mjs +407 -407
package/tools/clubs.mjs
CHANGED
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool family: clubs - the Clubs Radar directory, read from the terminal.
|
|
3
|
-
*
|
|
4
|
-
* Like the military family (and unlike every scan-shaped radar), the clubs market is ENUMERABLE
|
|
5
|
-
* rather than searchable: every chartered American Legion post, VFW post, Elks lodge and Moose
|
|
6
|
-
* lodge is a published row somewhere, and the engine has already pulled them in. So the thing a
|
|
7
|
-
* person wants from the terminal is not "what did the last scan find", it is "show me what is
|
|
8
|
-
* already in there", and that needs its own family rather than another entry in a mission list.
|
|
9
|
-
*
|
|
10
|
-
* km_clubs_directory -> GET /clubs/directory
|
|
11
|
-
* km_clubs_posts -> GET /clubs/posts
|
|
12
|
-
* km_clubs_post -> GET /clubs/posts/:ref
|
|
13
|
-
*
|
|
14
|
-
* 🚨 READ ONLY, and there are three separate reasons, all of which the descriptions say out loud:
|
|
15
|
-
*
|
|
16
|
-
* 1. NOTHING HERE SUBMITS, APPROVES OR SENDS. Writing to a post's mailbox or dialing its phone
|
|
17
|
-
* is a human act in KM Hub, behind the Approval Queue - club_radar drafts are a never-auto
|
|
18
|
-
* source, because a cold first impression to a veterans hall is exactly the message a human
|
|
19
|
-
* must read first.
|
|
20
|
-
* 2. NOTHING HERE STARTS A SWEEP. Queueing one is admin-only under RLS in the web app, and the
|
|
21
|
-
* API runs on the service role, which bypasses RLS: a start tool would quietly hand any
|
|
22
|
-
* read/write key a power the product refuses to a non-admin member. Whether a sweep is
|
|
23
|
-
* running is readable through km_list_radar_scans with kind 'club_radar_ingest'.
|
|
24
|
-
* 3. Reading costs NOTHING and sends NOTHING. The directory is shared reference data with
|
|
25
|
-
* global read, already populated before anybody runs anything, so there is no first click
|
|
26
|
-
* to make and no bill for looking.
|
|
27
|
-
*
|
|
28
|
-
* The family contract this file follows is documented in ./README.md.
|
|
29
|
-
*/
|
|
30
|
-
import { z } from 'zod';
|
|
31
|
-
|
|
32
|
-
export const FAMILY = 'clubs';
|
|
33
|
-
|
|
34
|
-
export const TOOLS = [
|
|
35
|
-
'km_clubs_directory',
|
|
36
|
-
'km_clubs_posts',
|
|
37
|
-
'km_clubs_post',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
// Finding and reaching buyers is outreach work, so this family joins that profile. It is not in
|
|
41
|
-
// `core`: a session that only wants orientation should not carry three more schemas.
|
|
42
|
-
export const PROFILES = ['outreach'];
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Has this KM Hub simply never heard of the route, or did the route run and find nothing?
|
|
46
|
-
*
|
|
47
|
-
* 🚨 Same discrimination as the military family, for the same reason: asking for a post that is
|
|
48
|
-
* not in the directory is a real, ordinary 404. Collapsing the two would answer a plain typo with
|
|
49
|
-
* "your KM Hub does not have the clubs directory", which is a false statement about the product
|
|
50
|
-
* rather than a wrong answer about a post. So only the router's own self-describing 404, which
|
|
51
|
-
* carries the whole route list, counts as a missing route.
|
|
52
|
-
*/
|
|
53
|
-
function routeMissing(r) {
|
|
54
|
-
if (r.status === 405 || r.status === 501) return true;
|
|
55
|
-
if (r.status !== 404) return false;
|
|
56
|
-
const body = r && r.data && typeof r.data === 'object' ? r.data : null;
|
|
57
|
-
if (!body) return true;
|
|
58
|
-
if (Array.isArray(body.routes)) return true;
|
|
59
|
-
return typeof body.message === 'string' && body.message.startsWith('Unknown route');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const NOT_SUPPORTED =
|
|
63
|
-
'This KM Hub does not serve the clubs directory to the terminal yet. Nothing is broken: the workspace is fine ' +
|
|
64
|
-
'and every other tool works as normal. The directory is there in the web app at https://hub.kivimedia.co under ' +
|
|
65
|
-
'Outbound, named Clubs Radar.';
|
|
66
|
-
|
|
67
|
-
const NO_SEND =
|
|
68
|
-
'What it will NOT do: it cannot write to a post, dial a phone, mark an engagement or send anything, and no ' +
|
|
69
|
-
'other tool here can either. Working a post - the ladder from first contact to a monthly residency - happens in ' +
|
|
70
|
-
'KM Hub at https://hub.kivimedia.co, on purpose. Never tell somebody a message went out.';
|
|
71
|
-
|
|
72
|
-
const NO_INVENTION =
|
|
73
|
-
'Never construct an address. A post that publishes no email keeps no email, and a VFW row with a phone and no ' +
|
|
74
|
-
'mailbox is the NORMAL published state for that order, not missing data. A row whose email_confidence says ' +
|
|
75
|
-
'"pattern" is a constructed guess from the order\'s scheme (lodge<NUM>@mooseunits.org) that must verify before ' +
|
|
76
|
-
'it may reach a draft - say so whenever one is shown.';
|
|
77
|
-
|
|
78
|
-
export function register(server, call, { out, text, qs }) {
|
|
79
|
-
server.tool(
|
|
80
|
-
'km_clubs_directory',
|
|
81
|
-
'The fraternal and veterans halls KM Hub has already enumerated: every chartered American Legion post, VFW ' +
|
|
82
|
-
'post, Elks lodge and Moose lodge in the directory, counted by order and by state, with how many rent their ' +
|
|
83
|
-
'hall, run a club room, and publish a mailbox. ' +
|
|
84
|
-
'Reach for it whenever somebody asks about clubs, lodges, posts, veterans halls, the Legion, the VFW, the ' +
|
|
85
|
-
'Elks or the Moose, and reach for it FIRST rather than describing the feature: this directory is shared ' +
|
|
86
|
-
'reference data that is already full before anybody runs anything, so there is nothing to start and nothing ' +
|
|
87
|
-
'to wait for. Reading it costs nothing, spends nothing and sends nothing. ' +
|
|
88
|
-
'🚨 Lead with the `directory` block, which is counted from the rows and not asserted. Say those numbers ' +
|
|
89
|
-
'before any individual row, because the single thing people get wrong about this surface is assuming it is ' +
|
|
90
|
-
'empty. ' +
|
|
91
|
-
'Why these buyers matter: posts run weekly member nights on a PRINTED schedule and most own a rentable hall ' +
|
|
92
|
-
'with an entertainment budget - one signed residency is a recurring monthly show. ' +
|
|
93
|
-
NO_INVENTION +
|
|
94
|
-
' ' +
|
|
95
|
-
'Whether a sweep is running is a different question, answered by km_list_radar_scans with kind ' +
|
|
96
|
-
"'club_radar_ingest'. Starting one is done in KM Hub, never from here. " +
|
|
97
|
-
NO_SEND,
|
|
98
|
-
{},
|
|
99
|
-
async () => {
|
|
100
|
-
const r = await call('GET', '/clubs/directory');
|
|
101
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
102
|
-
return out(r);
|
|
103
|
-
},
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
server.tool(
|
|
107
|
-
'km_clubs_posts',
|
|
108
|
-
'The posts themselves: one row per hall, with its printed meeting schedule (the free-demo-night slot IS that ' +
|
|
109
|
-
'line), the hall-rental and club-room flags, the route counts, the best mailbox where one is published, and ' +
|
|
110
|
-
'this workspace\'s own engagement status per post. ' +
|
|
111
|
-
'Use it when somebody wants the posts near them, the halls that rent, or a call list. Elks rows are the ' +
|
|
112
|
-
'warmest written route (published lodge emails); VFW rows are phone-first because that order publishes no ' +
|
|
113
|
-
'email - a phone-only row there is the office working normally. ' +
|
|
114
|
-
'Every filter is optional and `filters_applied` says back which ones are doing the narrowing, so an empty ' +
|
|
115
|
-
'result is explained rather than shrugged at. The radius filter needs lat, lng and radius_miles together, ' +
|
|
116
|
-
'and `skipped_no_coordinates` counts the rows it could not judge. ' +
|
|
117
|
-
NO_INVENTION +
|
|
118
|
-
' ' +
|
|
119
|
-
'Read only. Costs nothing, spends nothing. ' +
|
|
120
|
-
NO_SEND,
|
|
121
|
-
{
|
|
122
|
-
org: z
|
|
123
|
-
.enum(['legion', 'vfw', 'elks', 'moose'])
|
|
124
|
-
.optional()
|
|
125
|
-
.describe('One fraternal order. Leave it out to see all four.'),
|
|
126
|
-
state: z.string().optional().describe('Two-letter US state, for example "NJ".'),
|
|
127
|
-
city: z.string().optional().describe('City name, matched loosely.'),
|
|
128
|
-
hall_rental: z.boolean().optional().describe('True for posts whose hall is rentable - the venue-partnership list.'),
|
|
129
|
-
club_room: z.boolean().optional().describe('True for posts with a bar, canteen or club room on premises.'),
|
|
130
|
-
q: z.string().optional().describe('Free text against name, place, order and meeting schedule, in any order.'),
|
|
131
|
-
lat: z.number().optional().describe('Radius center latitude. Use with lng and radius_miles.'),
|
|
132
|
-
lng: z.number().optional().describe('Radius center longitude. Use with lat and radius_miles.'),
|
|
133
|
-
radius_miles: z.number().optional().describe('Radius in miles around lat/lng. Nearest posts come back first.'),
|
|
134
|
-
limit: z.number().int().min(1).max(200).optional().describe('How many posts to return. Default 40. Check `truncated`.'),
|
|
135
|
-
},
|
|
136
|
-
async ({ org, state, city, hall_rental, club_room, q, lat, lng, radius_miles, limit }) => {
|
|
137
|
-
const r = await call('GET', `/clubs/posts${qs({ org, state, city, hall_rental, club_room, q, lat, lng, radius_miles, limit })}`);
|
|
138
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
139
|
-
return out(r);
|
|
140
|
-
},
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
server.tool(
|
|
144
|
-
'km_clubs_post',
|
|
145
|
-
'One post in full: every published contact with the page it was read off and the verbatim fragment it came ' +
|
|
146
|
-
'from, the meeting schedule, the hall flags, and whatever engagement this workspace has already recorded. ' +
|
|
147
|
-
'Use it after km_clubs_posts when somebody picks a post, or whenever they name one directly. It takes the ' +
|
|
148
|
-
'club_key from a previous answer (for example "elks:nj:21") or a 9-digit EIN. ' +
|
|
149
|
-
'🚨 `email_confidence` decides whether an address may be written to as it stands: "published" was read off ' +
|
|
150
|
-
'the page named in source_url; "pattern" is a constructed guess that must verify first, and any draft ' +
|
|
151
|
-
'against it is refused until it does. `serves_posts` above 1 means one mailbox answers for several posts - ' +
|
|
152
|
-
'send it once, never once per hall. ' +
|
|
153
|
-
'`your_engagement` is this workspace\'s own ladder (researching, contacted, replied, demo booked, monthly ' +
|
|
154
|
-
'resident) and its sent marks - the record that keeps the same mailbox from being contacted twice. ' +
|
|
155
|
-
NO_INVENTION +
|
|
156
|
-
' ' +
|
|
157
|
-
'Read only. Costs nothing, spends nothing. ' +
|
|
158
|
-
NO_SEND,
|
|
159
|
-
{
|
|
160
|
-
post: z
|
|
161
|
-
.string()
|
|
162
|
-
.describe('The club_key from a previous answer, for example "elks:nj:21", or the post\'s 9-digit EIN.'),
|
|
163
|
-
},
|
|
164
|
-
async ({ post }) => {
|
|
165
|
-
const ref = String(post || '').trim();
|
|
166
|
-
if (!ref) {
|
|
167
|
-
return text(
|
|
168
|
-
'I need to know which post. km_clubs_posts lists them, and each row carries the club_key this tool takes.',
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
const r = await call('GET', `/clubs/posts/${encodeURIComponent(ref)}`);
|
|
172
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
173
|
-
return out(r);
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tool family: clubs - the Clubs Radar directory, read from the terminal.
|
|
3
|
+
*
|
|
4
|
+
* Like the military family (and unlike every scan-shaped radar), the clubs market is ENUMERABLE
|
|
5
|
+
* rather than searchable: every chartered American Legion post, VFW post, Elks lodge and Moose
|
|
6
|
+
* lodge is a published row somewhere, and the engine has already pulled them in. So the thing a
|
|
7
|
+
* person wants from the terminal is not "what did the last scan find", it is "show me what is
|
|
8
|
+
* already in there", and that needs its own family rather than another entry in a mission list.
|
|
9
|
+
*
|
|
10
|
+
* km_clubs_directory -> GET /clubs/directory
|
|
11
|
+
* km_clubs_posts -> GET /clubs/posts
|
|
12
|
+
* km_clubs_post -> GET /clubs/posts/:ref
|
|
13
|
+
*
|
|
14
|
+
* 🚨 READ ONLY, and there are three separate reasons, all of which the descriptions say out loud:
|
|
15
|
+
*
|
|
16
|
+
* 1. NOTHING HERE SUBMITS, APPROVES OR SENDS. Writing to a post's mailbox or dialing its phone
|
|
17
|
+
* is a human act in KM Hub, behind the Approval Queue - club_radar drafts are a never-auto
|
|
18
|
+
* source, because a cold first impression to a veterans hall is exactly the message a human
|
|
19
|
+
* must read first.
|
|
20
|
+
* 2. NOTHING HERE STARTS A SWEEP. Queueing one is admin-only under RLS in the web app, and the
|
|
21
|
+
* API runs on the service role, which bypasses RLS: a start tool would quietly hand any
|
|
22
|
+
* read/write key a power the product refuses to a non-admin member. Whether a sweep is
|
|
23
|
+
* running is readable through km_list_radar_scans with kind 'club_radar_ingest'.
|
|
24
|
+
* 3. Reading costs NOTHING and sends NOTHING. The directory is shared reference data with
|
|
25
|
+
* global read, already populated before anybody runs anything, so there is no first click
|
|
26
|
+
* to make and no bill for looking.
|
|
27
|
+
*
|
|
28
|
+
* The family contract this file follows is documented in ./README.md.
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
|
|
32
|
+
export const FAMILY = 'clubs';
|
|
33
|
+
|
|
34
|
+
export const TOOLS = [
|
|
35
|
+
'km_clubs_directory',
|
|
36
|
+
'km_clubs_posts',
|
|
37
|
+
'km_clubs_post',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Finding and reaching buyers is outreach work, so this family joins that profile. It is not in
|
|
41
|
+
// `core`: a session that only wants orientation should not carry three more schemas.
|
|
42
|
+
export const PROFILES = ['outreach'];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Has this KM Hub simply never heard of the route, or did the route run and find nothing?
|
|
46
|
+
*
|
|
47
|
+
* 🚨 Same discrimination as the military family, for the same reason: asking for a post that is
|
|
48
|
+
* not in the directory is a real, ordinary 404. Collapsing the two would answer a plain typo with
|
|
49
|
+
* "your KM Hub does not have the clubs directory", which is a false statement about the product
|
|
50
|
+
* rather than a wrong answer about a post. So only the router's own self-describing 404, which
|
|
51
|
+
* carries the whole route list, counts as a missing route.
|
|
52
|
+
*/
|
|
53
|
+
function routeMissing(r) {
|
|
54
|
+
if (r.status === 405 || r.status === 501) return true;
|
|
55
|
+
if (r.status !== 404) return false;
|
|
56
|
+
const body = r && r.data && typeof r.data === 'object' ? r.data : null;
|
|
57
|
+
if (!body) return true;
|
|
58
|
+
if (Array.isArray(body.routes)) return true;
|
|
59
|
+
return typeof body.message === 'string' && body.message.startsWith('Unknown route');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const NOT_SUPPORTED =
|
|
63
|
+
'This KM Hub does not serve the clubs directory to the terminal yet. Nothing is broken: the workspace is fine ' +
|
|
64
|
+
'and every other tool works as normal. The directory is there in the web app at https://hub.kivimedia.co under ' +
|
|
65
|
+
'Outbound, named Clubs Radar.';
|
|
66
|
+
|
|
67
|
+
const NO_SEND =
|
|
68
|
+
'What it will NOT do: it cannot write to a post, dial a phone, mark an engagement or send anything, and no ' +
|
|
69
|
+
'other tool here can either. Working a post - the ladder from first contact to a monthly residency - happens in ' +
|
|
70
|
+
'KM Hub at https://hub.kivimedia.co, on purpose. Never tell somebody a message went out.';
|
|
71
|
+
|
|
72
|
+
const NO_INVENTION =
|
|
73
|
+
'Never construct an address. A post that publishes no email keeps no email, and a VFW row with a phone and no ' +
|
|
74
|
+
'mailbox is the NORMAL published state for that order, not missing data. A row whose email_confidence says ' +
|
|
75
|
+
'"pattern" is a constructed guess from the order\'s scheme (lodge<NUM>@mooseunits.org) that must verify before ' +
|
|
76
|
+
'it may reach a draft - say so whenever one is shown.';
|
|
77
|
+
|
|
78
|
+
export function register(server, call, { out, text, qs }) {
|
|
79
|
+
server.tool(
|
|
80
|
+
'km_clubs_directory',
|
|
81
|
+
'The fraternal and veterans halls KM Hub has already enumerated: every chartered American Legion post, VFW ' +
|
|
82
|
+
'post, Elks lodge and Moose lodge in the directory, counted by order and by state, with how many rent their ' +
|
|
83
|
+
'hall, run a club room, and publish a mailbox. ' +
|
|
84
|
+
'Reach for it whenever somebody asks about clubs, lodges, posts, veterans halls, the Legion, the VFW, the ' +
|
|
85
|
+
'Elks or the Moose, and reach for it FIRST rather than describing the feature: this directory is shared ' +
|
|
86
|
+
'reference data that is already full before anybody runs anything, so there is nothing to start and nothing ' +
|
|
87
|
+
'to wait for. Reading it costs nothing, spends nothing and sends nothing. ' +
|
|
88
|
+
'🚨 Lead with the `directory` block, which is counted from the rows and not asserted. Say those numbers ' +
|
|
89
|
+
'before any individual row, because the single thing people get wrong about this surface is assuming it is ' +
|
|
90
|
+
'empty. ' +
|
|
91
|
+
'Why these buyers matter: posts run weekly member nights on a PRINTED schedule and most own a rentable hall ' +
|
|
92
|
+
'with an entertainment budget - one signed residency is a recurring monthly show. ' +
|
|
93
|
+
NO_INVENTION +
|
|
94
|
+
' ' +
|
|
95
|
+
'Whether a sweep is running is a different question, answered by km_list_radar_scans with kind ' +
|
|
96
|
+
"'club_radar_ingest'. Starting one is done in KM Hub, never from here. " +
|
|
97
|
+
NO_SEND,
|
|
98
|
+
{},
|
|
99
|
+
async () => {
|
|
100
|
+
const r = await call('GET', '/clubs/directory');
|
|
101
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
102
|
+
return out(r);
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
server.tool(
|
|
107
|
+
'km_clubs_posts',
|
|
108
|
+
'The posts themselves: one row per hall, with its printed meeting schedule (the free-demo-night slot IS that ' +
|
|
109
|
+
'line), the hall-rental and club-room flags, the route counts, the best mailbox where one is published, and ' +
|
|
110
|
+
'this workspace\'s own engagement status per post. ' +
|
|
111
|
+
'Use it when somebody wants the posts near them, the halls that rent, or a call list. Elks rows are the ' +
|
|
112
|
+
'warmest written route (published lodge emails); VFW rows are phone-first because that order publishes no ' +
|
|
113
|
+
'email - a phone-only row there is the office working normally. ' +
|
|
114
|
+
'Every filter is optional and `filters_applied` says back which ones are doing the narrowing, so an empty ' +
|
|
115
|
+
'result is explained rather than shrugged at. The radius filter needs lat, lng and radius_miles together, ' +
|
|
116
|
+
'and `skipped_no_coordinates` counts the rows it could not judge. ' +
|
|
117
|
+
NO_INVENTION +
|
|
118
|
+
' ' +
|
|
119
|
+
'Read only. Costs nothing, spends nothing. ' +
|
|
120
|
+
NO_SEND,
|
|
121
|
+
{
|
|
122
|
+
org: z
|
|
123
|
+
.enum(['legion', 'vfw', 'elks', 'moose'])
|
|
124
|
+
.optional()
|
|
125
|
+
.describe('One fraternal order. Leave it out to see all four.'),
|
|
126
|
+
state: z.string().optional().describe('Two-letter US state, for example "NJ".'),
|
|
127
|
+
city: z.string().optional().describe('City name, matched loosely.'),
|
|
128
|
+
hall_rental: z.boolean().optional().describe('True for posts whose hall is rentable - the venue-partnership list.'),
|
|
129
|
+
club_room: z.boolean().optional().describe('True for posts with a bar, canteen or club room on premises.'),
|
|
130
|
+
q: z.string().optional().describe('Free text against name, place, order and meeting schedule, in any order.'),
|
|
131
|
+
lat: z.number().optional().describe('Radius center latitude. Use with lng and radius_miles.'),
|
|
132
|
+
lng: z.number().optional().describe('Radius center longitude. Use with lat and radius_miles.'),
|
|
133
|
+
radius_miles: z.number().optional().describe('Radius in miles around lat/lng. Nearest posts come back first.'),
|
|
134
|
+
limit: z.number().int().min(1).max(200).optional().describe('How many posts to return. Default 40. Check `truncated`.'),
|
|
135
|
+
},
|
|
136
|
+
async ({ org, state, city, hall_rental, club_room, q, lat, lng, radius_miles, limit }) => {
|
|
137
|
+
const r = await call('GET', `/clubs/posts${qs({ org, state, city, hall_rental, club_room, q, lat, lng, radius_miles, limit })}`);
|
|
138
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
139
|
+
return out(r);
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
server.tool(
|
|
144
|
+
'km_clubs_post',
|
|
145
|
+
'One post in full: every published contact with the page it was read off and the verbatim fragment it came ' +
|
|
146
|
+
'from, the meeting schedule, the hall flags, and whatever engagement this workspace has already recorded. ' +
|
|
147
|
+
'Use it after km_clubs_posts when somebody picks a post, or whenever they name one directly. It takes the ' +
|
|
148
|
+
'club_key from a previous answer (for example "elks:nj:21") or a 9-digit EIN. ' +
|
|
149
|
+
'🚨 `email_confidence` decides whether an address may be written to as it stands: "published" was read off ' +
|
|
150
|
+
'the page named in source_url; "pattern" is a constructed guess that must verify first, and any draft ' +
|
|
151
|
+
'against it is refused until it does. `serves_posts` above 1 means one mailbox answers for several posts - ' +
|
|
152
|
+
'send it once, never once per hall. ' +
|
|
153
|
+
'`your_engagement` is this workspace\'s own ladder (researching, contacted, replied, demo booked, monthly ' +
|
|
154
|
+
'resident) and its sent marks - the record that keeps the same mailbox from being contacted twice. ' +
|
|
155
|
+
NO_INVENTION +
|
|
156
|
+
' ' +
|
|
157
|
+
'Read only. Costs nothing, spends nothing. ' +
|
|
158
|
+
NO_SEND,
|
|
159
|
+
{
|
|
160
|
+
post: z
|
|
161
|
+
.string()
|
|
162
|
+
.describe('The club_key from a previous answer, for example "elks:nj:21", or the post\'s 9-digit EIN.'),
|
|
163
|
+
},
|
|
164
|
+
async ({ post }) => {
|
|
165
|
+
const ref = String(post || '').trim();
|
|
166
|
+
if (!ref) {
|
|
167
|
+
return text(
|
|
168
|
+
'I need to know which post. km_clubs_posts lists them, and each row carries the club_key this tool takes.',
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const r = await call('GET', `/clubs/posts/${encodeURIComponent(ref)}`);
|
|
172
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
173
|
+
return out(r);
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
}
|