@kivimedia/kmhub 2.9.0 → 2.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/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 +36 -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 -109
- 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 +134 -134
- package/tools/hr.mjs +162 -162
- package/tools/knowledge.mjs +125 -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 +134 -134
- package/tools.mjs +407 -407
package/tools/schedules.mjs
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool family: schedules - what runs on its own, and whether it actually is.
|
|
3
|
-
*
|
|
4
|
-
* Workstream D, the half that is safe today. D1's writing scheduler was rejected by
|
|
5
|
-
* three independent reviews against the B8 write policy: a scheduled run composes
|
|
6
|
-
* its request body fresh at run time, so no confirm token can exist for it in
|
|
7
|
-
* advance, and "scheduling is the consent" can only be built as a branch that skips
|
|
8
|
-
* verification entirely.
|
|
9
|
-
*
|
|
10
|
-
* 🚨 What ships is the thing whose ABSENCE is dangerous. A recurring job that fails
|
|
11
|
-
* past its retry limit is set failed and nothing re-queues it and nothing reports
|
|
12
|
-
* it. The schedule is dead permanently and the client finds out weeks later. Worse,
|
|
13
|
-
* "ran and found nothing" and "has not run since April" look identical from the
|
|
14
|
-
* outside. This makes them distinguishable.
|
|
15
|
-
*
|
|
16
|
-
* The family contract this file follows is documented in ./README.md.
|
|
17
|
-
*/
|
|
18
|
-
export const FAMILY = 'schedules';
|
|
19
|
-
|
|
20
|
-
export const TOOLS = ['km_list_schedules'];
|
|
21
|
-
|
|
22
|
-
export const PROFILES = ['*'];
|
|
23
|
-
|
|
24
|
-
function routeMissing(r) {
|
|
25
|
-
return r.status === 404 || r.status === 405 || r.status === 501;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const NOT_SUPPORTED =
|
|
29
|
-
'Your KM Hub does not expose scheduling to the terminal yet. That is not a fault: the workspace is fine and every ' +
|
|
30
|
-
'other tool works as normal. Recurring work is configured in the web app at https://hub.kivimedia.co.';
|
|
31
|
-
|
|
32
|
-
export function register(server, call, { out, text }) {
|
|
33
|
-
server.tool(
|
|
34
|
-
'km_list_schedules',
|
|
35
|
-
'Everything in this workspace set to run on its own - the morning sweep, sequence sweeps, scheduled actions and ' +
|
|
36
|
-
'the rest - with when each is next due, when it last finished, and whether it is actually healthy. ' +
|
|
37
|
-
'Call it whenever the user asks what runs automatically, why something stopped arriving, or whether anything ' +
|
|
38
|
-
'is set up. Also call it before telling anybody that nothing happens on its own here. ' +
|
|
39
|
-
'🚨 LEAD WITH `problems`, and specifically with `dead_and_will_not_retry`. A recurring job that failed past its ' +
|
|
40
|
-
'retry limit is stopped PERMANENTLY: nothing re-queues it and no other screen in the product mentions it. The ' +
|
|
41
|
-
'client will not discover it on their own, because a schedule that silently stopped looks exactly like a quiet ' +
|
|
42
|
-
'week. Telling them is often the single most valuable thing in a session. ' +
|
|
43
|
-
'🚨 `overdue_hours` above about a day usually means the WORKER is not running, not that the schedule is wrong. ' +
|
|
44
|
-
'Say which you think it is rather than reporting a number and moving on. ' +
|
|
45
|
-
'Nothing here creates, pauses, restarts or runs a schedule. That happens in KM Hub, deliberately: a scheduled ' +
|
|
46
|
-
'run acts with nobody present, so it cannot give the confirmation that a money-spending or client-visible ' +
|
|
47
|
-
'action requires. Read only.',
|
|
48
|
-
{},
|
|
49
|
-
async () => {
|
|
50
|
-
const r = await call('GET', '/schedules');
|
|
51
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
52
|
-
return out(r);
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tool family: schedules - what runs on its own, and whether it actually is.
|
|
3
|
+
*
|
|
4
|
+
* Workstream D, the half that is safe today. D1's writing scheduler was rejected by
|
|
5
|
+
* three independent reviews against the B8 write policy: a scheduled run composes
|
|
6
|
+
* its request body fresh at run time, so no confirm token can exist for it in
|
|
7
|
+
* advance, and "scheduling is the consent" can only be built as a branch that skips
|
|
8
|
+
* verification entirely.
|
|
9
|
+
*
|
|
10
|
+
* 🚨 What ships is the thing whose ABSENCE is dangerous. A recurring job that fails
|
|
11
|
+
* past its retry limit is set failed and nothing re-queues it and nothing reports
|
|
12
|
+
* it. The schedule is dead permanently and the client finds out weeks later. Worse,
|
|
13
|
+
* "ran and found nothing" and "has not run since April" look identical from the
|
|
14
|
+
* outside. This makes them distinguishable.
|
|
15
|
+
*
|
|
16
|
+
* The family contract this file follows is documented in ./README.md.
|
|
17
|
+
*/
|
|
18
|
+
export const FAMILY = 'schedules';
|
|
19
|
+
|
|
20
|
+
export const TOOLS = ['km_list_schedules'];
|
|
21
|
+
|
|
22
|
+
export const PROFILES = ['*'];
|
|
23
|
+
|
|
24
|
+
function routeMissing(r) {
|
|
25
|
+
return r.status === 404 || r.status === 405 || r.status === 501;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const NOT_SUPPORTED =
|
|
29
|
+
'Your KM Hub does not expose scheduling to the terminal yet. That is not a fault: the workspace is fine and every ' +
|
|
30
|
+
'other tool works as normal. Recurring work is configured in the web app at https://hub.kivimedia.co.';
|
|
31
|
+
|
|
32
|
+
export function register(server, call, { out, text }) {
|
|
33
|
+
server.tool(
|
|
34
|
+
'km_list_schedules',
|
|
35
|
+
'Everything in this workspace set to run on its own - the morning sweep, sequence sweeps, scheduled actions and ' +
|
|
36
|
+
'the rest - with when each is next due, when it last finished, and whether it is actually healthy. ' +
|
|
37
|
+
'Call it whenever the user asks what runs automatically, why something stopped arriving, or whether anything ' +
|
|
38
|
+
'is set up. Also call it before telling anybody that nothing happens on its own here. ' +
|
|
39
|
+
'🚨 LEAD WITH `problems`, and specifically with `dead_and_will_not_retry`. A recurring job that failed past its ' +
|
|
40
|
+
'retry limit is stopped PERMANENTLY: nothing re-queues it and no other screen in the product mentions it. The ' +
|
|
41
|
+
'client will not discover it on their own, because a schedule that silently stopped looks exactly like a quiet ' +
|
|
42
|
+
'week. Telling them is often the single most valuable thing in a session. ' +
|
|
43
|
+
'🚨 `overdue_hours` above about a day usually means the WORKER is not running, not that the schedule is wrong. ' +
|
|
44
|
+
'Say which you think it is rather than reporting a number and moving on. ' +
|
|
45
|
+
'Nothing here creates, pauses, restarts or runs a schedule. That happens in KM Hub, deliberately: a scheduled ' +
|
|
46
|
+
'run acts with nobody present, so it cannot give the confirmation that a money-spending or client-visible ' +
|
|
47
|
+
'action requires. Read only.',
|
|
48
|
+
{},
|
|
49
|
+
async () => {
|
|
50
|
+
const r = await call('GET', '/schedules');
|
|
51
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
52
|
+
return out(r);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
}
|
package/tools/setup.mjs
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool family: setup - what this workspace is on, connected to, and paying for.
|
|
3
|
-
*
|
|
4
|
-
* Workstream B7. Setup was 3 of 21, the largest remaining gap, and the least
|
|
5
|
-
* glamorous pillar in the product. It is also where a model most often GUESSES:
|
|
6
|
-
* asked "am I paying for this", "is my email set up properly" or "is Stripe
|
|
7
|
-
* connected", a connector with no reach here invents a plausible answer.
|
|
8
|
-
*
|
|
9
|
-
* km_subscription -> GET /setup/subscription
|
|
10
|
-
* km_ai_usage -> GET /setup/usage
|
|
11
|
-
* km_list_integrations-> GET /setup/integrations
|
|
12
|
-
* km_deliverability -> GET /setup/deliverability
|
|
13
|
-
* km_workspace_config -> GET /setup/config
|
|
14
|
-
*
|
|
15
|
-
* 🚨🚨 km_list_integrations returns WHICH providers are connected and NEVER a
|
|
16
|
-
* credential. There is no parameter that changes that and no verbosity that reveals
|
|
17
|
-
* one. Do not ask for a key, do not offer to read one, and do not suggest another
|
|
18
|
-
* route to it. A truncated secret is still a secret.
|
|
19
|
-
*
|
|
20
|
-
* 🚨 READ ONLY. Changing a plan, a spend cap or a connection is money or a door.
|
|
21
|
-
*
|
|
22
|
-
* The family contract this file follows is documented in ./README.md.
|
|
23
|
-
*/
|
|
24
|
-
import { z } from 'zod';
|
|
25
|
-
|
|
26
|
-
export const FAMILY = 'setup';
|
|
27
|
-
|
|
28
|
-
export const TOOLS = [
|
|
29
|
-
'km_subscription',
|
|
30
|
-
'km_ai_usage',
|
|
31
|
-
'km_list_integrations',
|
|
32
|
-
'km_deliverability',
|
|
33
|
-
'km_workspace_config',
|
|
34
|
-
'km_calendar_feed',
|
|
35
|
-
'km_sms_config',
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
export const PROFILES = ['*'];
|
|
39
|
-
|
|
40
|
-
function routeMissing(r) {
|
|
41
|
-
return r.status === 404 || r.status === 405 || r.status === 501;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const NOT_SUPPORTED =
|
|
45
|
-
'Your KM Hub does not expose the settings surface to the terminal yet. That is not a fault: the workspace is fine ' +
|
|
46
|
-
'and every other tool works as normal. Billing, connections and deliverability are all in the web app at ' +
|
|
47
|
-
'https://hub.kivimedia.co, and this connector will read them once your KM Hub is on a build that publishes them.';
|
|
48
|
-
|
|
49
|
-
export function register(server, call, { out, text, qs }) {
|
|
50
|
-
server.tool(
|
|
51
|
-
'km_subscription',
|
|
52
|
-
'What this workspace is actually on: the plan, whether it is active, trialing or comped, when it renews, whether it ' +
|
|
53
|
-
'is set to cancel, and any monthly spend cap. ' +
|
|
54
|
-
'Call it when the user asks about their plan, their bill, what they are paying for, or whether something is ' +
|
|
55
|
-
'included. Never answer those from memory or from what a plan name sounds like. ' +
|
|
56
|
-
'🚨 `cancel_at_period_end` true is worth saying out loud unprompted: Terminal Mode itself stops working when the ' +
|
|
57
|
-
'subscription lapses, so a user planning work past that date should know. `trial_days_left` deserves the same ' +
|
|
58
|
-
'treatment. Amounts are in cents. Read only: changing a plan happens in KM Hub.',
|
|
59
|
-
{},
|
|
60
|
-
async () => {
|
|
61
|
-
const r = await call('GET', '/setup/subscription');
|
|
62
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
63
|
-
return out(r);
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
server.tool(
|
|
68
|
-
'km_ai_usage',
|
|
69
|
-
'What the AI side of the workspace has cost this month, against the spend cap if one is set. These are the same ' +
|
|
70
|
-
'figures the billing page reads, so the terminal and the web app cannot disagree. ' +
|
|
71
|
-
'Use it before starting anything expensive, when the user asks what their usage looks like, or if a paid operation ' +
|
|
72
|
-
'has stopped working for no obvious reason. ' +
|
|
73
|
-
'🚨 If a cap is set and usage is close to it, say so plainly. Work simply stops when a cap is reached, and finding ' +
|
|
74
|
-
'that out mid-task is far worse than being told in advance. Amounts are in cents. Read only.',
|
|
75
|
-
{},
|
|
76
|
-
async () => {
|
|
77
|
-
const r = await call('GET', '/setup/usage');
|
|
78
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
79
|
-
return out(r);
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
server.tool(
|
|
84
|
-
'km_list_integrations',
|
|
85
|
-
'Which third-party providers this workspace has connected: payments, email, calendar, lead sources and so on. ' +
|
|
86
|
-
'Use it before suggesting anything that depends on an integration, and when the user asks whether something is ' +
|
|
87
|
-
'hooked up. A feature that needs Stripe is not available on a workspace without Stripe, and finding that out by ' +
|
|
88
|
-
'trying is a bad experience you can avoid with one call. ' +
|
|
89
|
-
'🚨 This returns the NAMES of connected providers and never a credential value. There is no argument, no verbosity ' +
|
|
90
|
-
'and no other route that returns one. Do not ask the user to paste a key into the terminal either: connecting and ' +
|
|
91
|
-
'rotating providers happens in KM Hub at https://hub.kivimedia.co, where the secret stays. Read only.',
|
|
92
|
-
{},
|
|
93
|
-
async () => {
|
|
94
|
-
const r = await call('GET', '/setup/integrations');
|
|
95
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
96
|
-
return out(r);
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
server.tool(
|
|
101
|
-
'km_deliverability',
|
|
102
|
-
'Whether the mail this business sends is actually arriving: daily delivery, bounce and complaint figures over a ' +
|
|
103
|
-
'window. ' +
|
|
104
|
-
'Call it before recommending any outbound push, and whenever outreach or a newsletter is underperforming. ' +
|
|
105
|
-
'🚨 Deliverability is the quietest way a business loses money, because mail that never arrived looks exactly like ' +
|
|
106
|
-
'mail nobody answered, and every diagnosis downstream of that mistake is wrong. Rising bounces or complaints matter ' +
|
|
107
|
-
'more than a falling open rate: they put the sending domain itself at risk, which costs every future send and not ' +
|
|
108
|
-
'just this one. Raise it unprompted when you see it. ' +
|
|
109
|
-
'Domain setup and authentication live in KM Hub. Read only.',
|
|
110
|
-
{
|
|
111
|
-
days: z.number().int().min(1).max(90).optional().describe('How many days back. Default 30.'),
|
|
112
|
-
},
|
|
113
|
-
async ({ days }) => {
|
|
114
|
-
const r = await call('GET', `/setup/deliverability${qs({ days })}`);
|
|
115
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
116
|
-
return out(r);
|
|
117
|
-
},
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
server.tool(
|
|
121
|
-
'km_workspace_config',
|
|
122
|
-
'How this workspace is shaped: the custom fields it records beyond the standard model, its holidays and whether each ' +
|
|
123
|
-
'blocks availability, and its email templates with their merge variables. ' +
|
|
124
|
-
'🚨 Read the custom fields before concluding a business does not track something. A workspace that records "room ' +
|
|
125
|
-
'layout" or "dietary notes" as a custom field is telling you what matters to it, and answering "KM Hub does not ' +
|
|
126
|
-
'store that" when it does is a bad and avoidable mistake. ' +
|
|
127
|
-
'A holiday with blocks_availability true is a date that genuinely is not bookable, which matters before offering ' +
|
|
128
|
-
'anybody a date. Template bodies are not returned: the list is for choosing one, and editing happens in KM Hub. ' +
|
|
129
|
-
'Read only.',
|
|
130
|
-
{},
|
|
131
|
-
async () => {
|
|
132
|
-
const r = await call('GET', '/setup/config');
|
|
133
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
134
|
-
return out(r);
|
|
135
|
-
},
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
server.tool(
|
|
139
|
-
'km_calendar_feed',
|
|
140
|
-
'Whether this workspace publishes its diary as a subscribable calendar feed, and when that was set up. ' +
|
|
141
|
-
'Use it when the user asks about syncing their calendar elsewhere. ' +
|
|
142
|
-
'🚨 The feed URL contains a private token and is NEVER returned here. Anybody holding it has a permanent ' +
|
|
143
|
-
'subscription to this diary, and it cannot be rotated without breaking every calendar already subscribed, so it is ' +
|
|
144
|
-
'not something to put in a terminal transcript. Point them at KM Hub to copy it. Read only.',
|
|
145
|
-
{},
|
|
146
|
-
async () => {
|
|
147
|
-
const r = await call('GET', '/setup/calendar-feed');
|
|
148
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
149
|
-
return out(r);
|
|
150
|
-
},
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
server.tool(
|
|
154
|
-
'km_sms_config',
|
|
155
|
-
'Whether SMS is switched on for this workspace, which provider it uses, whether credentials are configured, and the ' +
|
|
156
|
-
'number clients see. ' +
|
|
157
|
-
'Call it before suggesting anything that texts a client: a workspace with SMS off cannot send one, and finding that ' +
|
|
158
|
-
'out by trying wastes the user time. ' +
|
|
159
|
-
'🚨 Credential VALUES are never returned, only whether they are present. The sending number IS returned, ' +
|
|
160
|
-
'because it is the public identity clients already see on their phones. Read only.',
|
|
161
|
-
{},
|
|
162
|
-
async () => {
|
|
163
|
-
const r = await call('GET', '/setup/sms-config');
|
|
164
|
-
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
165
|
-
return out(r);
|
|
166
|
-
},
|
|
167
|
-
);
|
|
168
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tool family: setup - what this workspace is on, connected to, and paying for.
|
|
3
|
+
*
|
|
4
|
+
* Workstream B7. Setup was 3 of 21, the largest remaining gap, and the least
|
|
5
|
+
* glamorous pillar in the product. It is also where a model most often GUESSES:
|
|
6
|
+
* asked "am I paying for this", "is my email set up properly" or "is Stripe
|
|
7
|
+
* connected", a connector with no reach here invents a plausible answer.
|
|
8
|
+
*
|
|
9
|
+
* km_subscription -> GET /setup/subscription
|
|
10
|
+
* km_ai_usage -> GET /setup/usage
|
|
11
|
+
* km_list_integrations-> GET /setup/integrations
|
|
12
|
+
* km_deliverability -> GET /setup/deliverability
|
|
13
|
+
* km_workspace_config -> GET /setup/config
|
|
14
|
+
*
|
|
15
|
+
* 🚨🚨 km_list_integrations returns WHICH providers are connected and NEVER a
|
|
16
|
+
* credential. There is no parameter that changes that and no verbosity that reveals
|
|
17
|
+
* one. Do not ask for a key, do not offer to read one, and do not suggest another
|
|
18
|
+
* route to it. A truncated secret is still a secret.
|
|
19
|
+
*
|
|
20
|
+
* 🚨 READ ONLY. Changing a plan, a spend cap or a connection is money or a door.
|
|
21
|
+
*
|
|
22
|
+
* The family contract this file follows is documented in ./README.md.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
|
|
26
|
+
export const FAMILY = 'setup';
|
|
27
|
+
|
|
28
|
+
export const TOOLS = [
|
|
29
|
+
'km_subscription',
|
|
30
|
+
'km_ai_usage',
|
|
31
|
+
'km_list_integrations',
|
|
32
|
+
'km_deliverability',
|
|
33
|
+
'km_workspace_config',
|
|
34
|
+
'km_calendar_feed',
|
|
35
|
+
'km_sms_config',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const PROFILES = ['*'];
|
|
39
|
+
|
|
40
|
+
function routeMissing(r) {
|
|
41
|
+
return r.status === 404 || r.status === 405 || r.status === 501;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const NOT_SUPPORTED =
|
|
45
|
+
'Your KM Hub does not expose the settings surface to the terminal yet. That is not a fault: the workspace is fine ' +
|
|
46
|
+
'and every other tool works as normal. Billing, connections and deliverability are all in the web app at ' +
|
|
47
|
+
'https://hub.kivimedia.co, and this connector will read them once your KM Hub is on a build that publishes them.';
|
|
48
|
+
|
|
49
|
+
export function register(server, call, { out, text, qs }) {
|
|
50
|
+
server.tool(
|
|
51
|
+
'km_subscription',
|
|
52
|
+
'What this workspace is actually on: the plan, whether it is active, trialing or comped, when it renews, whether it ' +
|
|
53
|
+
'is set to cancel, and any monthly spend cap. ' +
|
|
54
|
+
'Call it when the user asks about their plan, their bill, what they are paying for, or whether something is ' +
|
|
55
|
+
'included. Never answer those from memory or from what a plan name sounds like. ' +
|
|
56
|
+
'🚨 `cancel_at_period_end` true is worth saying out loud unprompted: Terminal Mode itself stops working when the ' +
|
|
57
|
+
'subscription lapses, so a user planning work past that date should know. `trial_days_left` deserves the same ' +
|
|
58
|
+
'treatment. Amounts are in cents. Read only: changing a plan happens in KM Hub.',
|
|
59
|
+
{},
|
|
60
|
+
async () => {
|
|
61
|
+
const r = await call('GET', '/setup/subscription');
|
|
62
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
63
|
+
return out(r);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
server.tool(
|
|
68
|
+
'km_ai_usage',
|
|
69
|
+
'What the AI side of the workspace has cost this month, against the spend cap if one is set. These are the same ' +
|
|
70
|
+
'figures the billing page reads, so the terminal and the web app cannot disagree. ' +
|
|
71
|
+
'Use it before starting anything expensive, when the user asks what their usage looks like, or if a paid operation ' +
|
|
72
|
+
'has stopped working for no obvious reason. ' +
|
|
73
|
+
'🚨 If a cap is set and usage is close to it, say so plainly. Work simply stops when a cap is reached, and finding ' +
|
|
74
|
+
'that out mid-task is far worse than being told in advance. Amounts are in cents. Read only.',
|
|
75
|
+
{},
|
|
76
|
+
async () => {
|
|
77
|
+
const r = await call('GET', '/setup/usage');
|
|
78
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
79
|
+
return out(r);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
server.tool(
|
|
84
|
+
'km_list_integrations',
|
|
85
|
+
'Which third-party providers this workspace has connected: payments, email, calendar, lead sources and so on. ' +
|
|
86
|
+
'Use it before suggesting anything that depends on an integration, and when the user asks whether something is ' +
|
|
87
|
+
'hooked up. A feature that needs Stripe is not available on a workspace without Stripe, and finding that out by ' +
|
|
88
|
+
'trying is a bad experience you can avoid with one call. ' +
|
|
89
|
+
'🚨 This returns the NAMES of connected providers and never a credential value. There is no argument, no verbosity ' +
|
|
90
|
+
'and no other route that returns one. Do not ask the user to paste a key into the terminal either: connecting and ' +
|
|
91
|
+
'rotating providers happens in KM Hub at https://hub.kivimedia.co, where the secret stays. Read only.',
|
|
92
|
+
{},
|
|
93
|
+
async () => {
|
|
94
|
+
const r = await call('GET', '/setup/integrations');
|
|
95
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
96
|
+
return out(r);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
server.tool(
|
|
101
|
+
'km_deliverability',
|
|
102
|
+
'Whether the mail this business sends is actually arriving: daily delivery, bounce and complaint figures over a ' +
|
|
103
|
+
'window. ' +
|
|
104
|
+
'Call it before recommending any outbound push, and whenever outreach or a newsletter is underperforming. ' +
|
|
105
|
+
'🚨 Deliverability is the quietest way a business loses money, because mail that never arrived looks exactly like ' +
|
|
106
|
+
'mail nobody answered, and every diagnosis downstream of that mistake is wrong. Rising bounces or complaints matter ' +
|
|
107
|
+
'more than a falling open rate: they put the sending domain itself at risk, which costs every future send and not ' +
|
|
108
|
+
'just this one. Raise it unprompted when you see it. ' +
|
|
109
|
+
'Domain setup and authentication live in KM Hub. Read only.',
|
|
110
|
+
{
|
|
111
|
+
days: z.number().int().min(1).max(90).optional().describe('How many days back. Default 30.'),
|
|
112
|
+
},
|
|
113
|
+
async ({ days }) => {
|
|
114
|
+
const r = await call('GET', `/setup/deliverability${qs({ days })}`);
|
|
115
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
116
|
+
return out(r);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
server.tool(
|
|
121
|
+
'km_workspace_config',
|
|
122
|
+
'How this workspace is shaped: the custom fields it records beyond the standard model, its holidays and whether each ' +
|
|
123
|
+
'blocks availability, and its email templates with their merge variables. ' +
|
|
124
|
+
'🚨 Read the custom fields before concluding a business does not track something. A workspace that records "room ' +
|
|
125
|
+
'layout" or "dietary notes" as a custom field is telling you what matters to it, and answering "KM Hub does not ' +
|
|
126
|
+
'store that" when it does is a bad and avoidable mistake. ' +
|
|
127
|
+
'A holiday with blocks_availability true is a date that genuinely is not bookable, which matters before offering ' +
|
|
128
|
+
'anybody a date. Template bodies are not returned: the list is for choosing one, and editing happens in KM Hub. ' +
|
|
129
|
+
'Read only.',
|
|
130
|
+
{},
|
|
131
|
+
async () => {
|
|
132
|
+
const r = await call('GET', '/setup/config');
|
|
133
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
134
|
+
return out(r);
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
server.tool(
|
|
139
|
+
'km_calendar_feed',
|
|
140
|
+
'Whether this workspace publishes its diary as a subscribable calendar feed, and when that was set up. ' +
|
|
141
|
+
'Use it when the user asks about syncing their calendar elsewhere. ' +
|
|
142
|
+
'🚨 The feed URL contains a private token and is NEVER returned here. Anybody holding it has a permanent ' +
|
|
143
|
+
'subscription to this diary, and it cannot be rotated without breaking every calendar already subscribed, so it is ' +
|
|
144
|
+
'not something to put in a terminal transcript. Point them at KM Hub to copy it. Read only.',
|
|
145
|
+
{},
|
|
146
|
+
async () => {
|
|
147
|
+
const r = await call('GET', '/setup/calendar-feed');
|
|
148
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
149
|
+
return out(r);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
server.tool(
|
|
154
|
+
'km_sms_config',
|
|
155
|
+
'Whether SMS is switched on for this workspace, which provider it uses, whether credentials are configured, and the ' +
|
|
156
|
+
'number clients see. ' +
|
|
157
|
+
'Call it before suggesting anything that texts a client: a workspace with SMS off cannot send one, and finding that ' +
|
|
158
|
+
'out by trying wastes the user time. ' +
|
|
159
|
+
'🚨 Credential VALUES are never returned, only whether they are present. The sending number IS returned, ' +
|
|
160
|
+
'because it is the public identity clients already see on their phones. Read only.',
|
|
161
|
+
{},
|
|
162
|
+
async () => {
|
|
163
|
+
const r = await call('GET', '/setup/sms-config');
|
|
164
|
+
if (routeMissing(r)) return text(NOT_SUPPORTED, true);
|
|
165
|
+
return out(r);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
}
|
package/tools/sops-bridges.mjs
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool family: sops-bridges - the SOP, delivered to the moment of the work.
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS IS A SECOND FAMILY AND NOT MORE TOOLS IN sops.mjs
|
|
5
|
-
* The sops family is about the LIBRARY: writing procedures, publishing them,
|
|
6
|
-
* attaching them to things. These two tools are about a JOB. Somebody is about
|
|
7
|
-
* to load a truck for Saturday, and the only question they have is "what do I
|
|
8
|
-
* need to know before this one". km_sops_for can only answer that thing by
|
|
9
|
-
* thing; km_sop_brief answers it for the whole booking at once - the booking's
|
|
10
|
-
* own procedures plus the venue's, the event type's, every piece of gear's and
|
|
11
|
-
* every crew role's, deduplicated, critical steps first.
|
|
12
|
-
*
|
|
13
|
-
* That is the difference between KM Hub SOPs and the tool J&M cancelled after
|
|
14
|
-
* two days. GembaDocs holds the same 21-step warehouse procedure and cannot say
|
|
15
|
-
* which of its documents matter tonight, because it has never heard of the
|
|
16
|
-
* venue, the gear or the person driving the truck.
|
|
17
|
-
*
|
|
18
|
-
* WHAT IS DELIBERATELY NOT HERE
|
|
19
|
-
* The bridge resolve route (POST sops/bridge/resolve) has no tool. It exists so
|
|
20
|
-
* DJEP, the Zoho bridge and the SMPL bridge can ask by name when they do not
|
|
21
|
-
* know a KM Hub uuid. A model in this conversation always has the booking, so
|
|
22
|
-
* giving it a hint-matching tool would only invite it to guess a venue name
|
|
23
|
-
* when it could have passed an id. Bridges call that route directly.
|
|
24
|
-
*
|
|
25
|
-
* SAFETY. Both tools read. Nothing here sends, charges or changes anything.
|
|
26
|
-
*
|
|
27
|
-
* The family contract this file follows is documented in ./README.md.
|
|
28
|
-
*/
|
|
29
|
-
import { z } from 'zod';
|
|
30
|
-
|
|
31
|
-
export const FAMILY = 'sops-bridges';
|
|
32
|
-
|
|
33
|
-
export const TOOLS = ['km_sop_brief', 'km_sop_pullsheet'];
|
|
34
|
-
|
|
35
|
-
// Empty, not ['*'] - see the note in sops.mjs. ['*'] pins a family into every
|
|
36
|
-
// profile, which is the opposite of what this comment used to claim.
|
|
37
|
-
export const PROFILES = [];
|
|
38
|
-
|
|
39
|
-
export function register(server, call, { out, text }) {
|
|
40
|
-
server.tool(
|
|
41
|
-
'km_sop_brief',
|
|
42
|
-
'Everything somebody should read before working a specific booking: the SOPs attached to the booking itself, plus the ' +
|
|
43
|
-
'ones attached to its venue, its event type, every piece of gear on it, every crew role staffed on it, and the client. ' +
|
|
44
|
-
'Deduplicated, ordered so the critical procedures come first, with a flat list of every critical step across all of them. ' +
|
|
45
|
-
'REACH FOR THIS WHENEVER A JOB IS BEING PREPARED, BRIEFED OR TALKED THROUGH, and reach for it before you answer from ' +
|
|
46
|
-
'memory: a written procedure beats a good guess, and this is the only tool that finds every procedure that touches one ' +
|
|
47
|
-
'job in a single call. Worth running unprompted the day before an event - an SOP that exists and is not read is the same ' +
|
|
48
|
-
'as no SOP at all. ' +
|
|
49
|
-
'It also answers usefully when the answer is nothing: it names the venue, the gear and the roles that have no procedure ' +
|
|
50
|
-
'written for them, which is the list of what only lives in somebody\'s head. ' +
|
|
51
|
-
'Only PUBLISHED SOPs appear. A draft is somebody still thinking and an archived one is no longer how the work is done, ' +
|
|
52
|
-
'and putting either in front of a crew member carries the authority of a document it has not earned. Read only.',
|
|
53
|
-
{
|
|
54
|
-
booking_id: z.string()
|
|
55
|
-
.describe('The booking id, or the booking number people say out loud (for example JM-1042). Either works.'),
|
|
56
|
-
},
|
|
57
|
-
async ({ booking_id }) => out(await call('GET', `/sops/brief/${encodeURIComponent(booking_id)}`)),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
server.tool(
|
|
61
|
-
'km_sop_pullsheet',
|
|
62
|
-
'The same job brief as km_sop_brief, rendered as a plain-text block to paste into a pull sheet, a production system or a ' +
|
|
63
|
-
'printed sheet taped to a road case. It carries the SOP reference numbers, titles, revisions, why each one applies, and ' +
|
|
64
|
-
'the CRITICAL STEPS ONLY - not the full procedure. ' +
|
|
65
|
-
'Reach for it when the user asks for something to paste, print, send to the crew, or add to a pull sheet or a DJEP ' +
|
|
66
|
-
'export, and when they want the short version rather than every step. ' +
|
|
67
|
-
'It is plain text on purpose: it lands in systems with no HTML, so hand it over exactly as it comes back rather than ' +
|
|
68
|
-
'reformatting it into a table. Use km_sop_brief instead when somebody needs to actually follow a procedure, because the ' +
|
|
69
|
-
'non-critical steps are the ones missing here. Read only.',
|
|
70
|
-
{
|
|
71
|
-
booking_id: z.string()
|
|
72
|
-
.describe('The booking id, or the booking number people say out loud. Either works.'),
|
|
73
|
-
},
|
|
74
|
-
async ({ booking_id }) => {
|
|
75
|
-
const r = await call('GET', `/sops/pullsheet/${encodeURIComponent(booking_id)}`);
|
|
76
|
-
// The block is the product, so it goes back as prose. Wrapped in JSON its
|
|
77
|
-
// line breaks arrive as the two characters backslash-n, and the thing a
|
|
78
|
-
// person is meant to paste stops being pasteable.
|
|
79
|
-
const block = r && r.ok && r.data && typeof r.data.text === 'string' ? r.data.text : '';
|
|
80
|
-
if (!block) return out(r);
|
|
81
|
-
|
|
82
|
-
const warnings = Array.isArray(r.data.warnings) ? r.data.warnings.filter(Boolean) : [];
|
|
83
|
-
return text(warnings.length ? `${block}\n\n${warnings.map((w) => `note: ${w}`).join('\n')}` : block);
|
|
84
|
-
},
|
|
85
|
-
);
|
|
86
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tool family: sops-bridges - the SOP, delivered to the moment of the work.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS A SECOND FAMILY AND NOT MORE TOOLS IN sops.mjs
|
|
5
|
+
* The sops family is about the LIBRARY: writing procedures, publishing them,
|
|
6
|
+
* attaching them to things. These two tools are about a JOB. Somebody is about
|
|
7
|
+
* to load a truck for Saturday, and the only question they have is "what do I
|
|
8
|
+
* need to know before this one". km_sops_for can only answer that thing by
|
|
9
|
+
* thing; km_sop_brief answers it for the whole booking at once - the booking's
|
|
10
|
+
* own procedures plus the venue's, the event type's, every piece of gear's and
|
|
11
|
+
* every crew role's, deduplicated, critical steps first.
|
|
12
|
+
*
|
|
13
|
+
* That is the difference between KM Hub SOPs and the tool J&M cancelled after
|
|
14
|
+
* two days. GembaDocs holds the same 21-step warehouse procedure and cannot say
|
|
15
|
+
* which of its documents matter tonight, because it has never heard of the
|
|
16
|
+
* venue, the gear or the person driving the truck.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IS DELIBERATELY NOT HERE
|
|
19
|
+
* The bridge resolve route (POST sops/bridge/resolve) has no tool. It exists so
|
|
20
|
+
* DJEP, the Zoho bridge and the SMPL bridge can ask by name when they do not
|
|
21
|
+
* know a KM Hub uuid. A model in this conversation always has the booking, so
|
|
22
|
+
* giving it a hint-matching tool would only invite it to guess a venue name
|
|
23
|
+
* when it could have passed an id. Bridges call that route directly.
|
|
24
|
+
*
|
|
25
|
+
* SAFETY. Both tools read. Nothing here sends, charges or changes anything.
|
|
26
|
+
*
|
|
27
|
+
* The family contract this file follows is documented in ./README.md.
|
|
28
|
+
*/
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
|
|
31
|
+
export const FAMILY = 'sops-bridges';
|
|
32
|
+
|
|
33
|
+
export const TOOLS = ['km_sop_brief', 'km_sop_pullsheet'];
|
|
34
|
+
|
|
35
|
+
// Empty, not ['*'] - see the note in sops.mjs. ['*'] pins a family into every
|
|
36
|
+
// profile, which is the opposite of what this comment used to claim.
|
|
37
|
+
export const PROFILES = [];
|
|
38
|
+
|
|
39
|
+
export function register(server, call, { out, text }) {
|
|
40
|
+
server.tool(
|
|
41
|
+
'km_sop_brief',
|
|
42
|
+
'Everything somebody should read before working a specific booking: the SOPs attached to the booking itself, plus the ' +
|
|
43
|
+
'ones attached to its venue, its event type, every piece of gear on it, every crew role staffed on it, and the client. ' +
|
|
44
|
+
'Deduplicated, ordered so the critical procedures come first, with a flat list of every critical step across all of them. ' +
|
|
45
|
+
'REACH FOR THIS WHENEVER A JOB IS BEING PREPARED, BRIEFED OR TALKED THROUGH, and reach for it before you answer from ' +
|
|
46
|
+
'memory: a written procedure beats a good guess, and this is the only tool that finds every procedure that touches one ' +
|
|
47
|
+
'job in a single call. Worth running unprompted the day before an event - an SOP that exists and is not read is the same ' +
|
|
48
|
+
'as no SOP at all. ' +
|
|
49
|
+
'It also answers usefully when the answer is nothing: it names the venue, the gear and the roles that have no procedure ' +
|
|
50
|
+
'written for them, which is the list of what only lives in somebody\'s head. ' +
|
|
51
|
+
'Only PUBLISHED SOPs appear. A draft is somebody still thinking and an archived one is no longer how the work is done, ' +
|
|
52
|
+
'and putting either in front of a crew member carries the authority of a document it has not earned. Read only.',
|
|
53
|
+
{
|
|
54
|
+
booking_id: z.string()
|
|
55
|
+
.describe('The booking id, or the booking number people say out loud (for example JM-1042). Either works.'),
|
|
56
|
+
},
|
|
57
|
+
async ({ booking_id }) => out(await call('GET', `/sops/brief/${encodeURIComponent(booking_id)}`)),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
server.tool(
|
|
61
|
+
'km_sop_pullsheet',
|
|
62
|
+
'The same job brief as km_sop_brief, rendered as a plain-text block to paste into a pull sheet, a production system or a ' +
|
|
63
|
+
'printed sheet taped to a road case. It carries the SOP reference numbers, titles, revisions, why each one applies, and ' +
|
|
64
|
+
'the CRITICAL STEPS ONLY - not the full procedure. ' +
|
|
65
|
+
'Reach for it when the user asks for something to paste, print, send to the crew, or add to a pull sheet or a DJEP ' +
|
|
66
|
+
'export, and when they want the short version rather than every step. ' +
|
|
67
|
+
'It is plain text on purpose: it lands in systems with no HTML, so hand it over exactly as it comes back rather than ' +
|
|
68
|
+
'reformatting it into a table. Use km_sop_brief instead when somebody needs to actually follow a procedure, because the ' +
|
|
69
|
+
'non-critical steps are the ones missing here. Read only.',
|
|
70
|
+
{
|
|
71
|
+
booking_id: z.string()
|
|
72
|
+
.describe('The booking id, or the booking number people say out loud. Either works.'),
|
|
73
|
+
},
|
|
74
|
+
async ({ booking_id }) => {
|
|
75
|
+
const r = await call('GET', `/sops/pullsheet/${encodeURIComponent(booking_id)}`);
|
|
76
|
+
// The block is the product, so it goes back as prose. Wrapped in JSON its
|
|
77
|
+
// line breaks arrive as the two characters backslash-n, and the thing a
|
|
78
|
+
// person is meant to paste stops being pasteable.
|
|
79
|
+
const block = r && r.ok && r.data && typeof r.data.text === 'string' ? r.data.text : '';
|
|
80
|
+
if (!block) return out(r);
|
|
81
|
+
|
|
82
|
+
const warnings = Array.isArray(r.data.warnings) ? r.data.warnings.filter(Boolean) : [];
|
|
83
|
+
return text(warnings.length ? `${block}\n\n${warnings.map((w) => `note: ${w}`).join('\n')}` : block);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
}
|