@kivimedia/kmhub 2.0.0 → 2.9.1
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 +6 -5
- package/bin/kmhub.mjs +20 -7
- package/coach-book-output-guard.mjs +760 -0
- package/index.mjs +2 -0
- package/package.json +8 -3
- package/prompts/briefing.md +29 -0
- package/prompts/luxury.md +70 -0
- package/prompts/play.md +49 -0
- package/prompts/run.md +36 -0
- package/prompts/setup.md +33 -0
- package/prompts/vs-booked.md +46 -0
- package/prompts/what-can-you-do.md +40 -0
- package/prompts.mjs +110 -0
- package/read-only-tools.json +142 -0
- package/remote.mjs +815 -99
- package/tools/balloon-costing.mjs +80 -0
- package/tools/booking-equipment.mjs +110 -0
- package/tools/bridges.mjs +54 -0
- package/tools/calendar.mjs +9 -0
- package/tools/capabilities.mjs +155 -0
- package/tools/catalog.mjs +288 -0
- package/tools/clubs.mjs +176 -0
- package/tools/coach.mjs +771 -0
- package/tools/compare.mjs +76 -0
- package/tools/core.mjs +21 -0
- package/tools/crm.mjs +12 -3
- package/tools/dubsado.mjs +137 -0
- package/tools/exports.mjs +128 -0
- package/tools/fact-review.mjs +125 -0
- package/tools/flows.mjs +261 -0
- package/tools/forms.mjs +158 -0
- package/tools/gols.mjs +134 -0
- package/tools/hr.mjs +162 -0
- package/tools/knowledge.mjs +4 -3
- package/tools/marketing.mjs +396 -0
- package/tools/meta.mjs +2 -2
- package/tools/military.mjs +244 -0
- package/tools/outreach.mjs +27 -4
- package/tools/pending.mjs +122 -0
- package/tools/photos.mjs +140 -0
- package/tools/plays.mjs +1 -1
- package/tools/profile.mjs +118 -0
- package/tools/radar.mjs +173 -0
- package/tools/recurring-invoices.mjs +149 -0
- package/tools/reengage.mjs +434 -0
- package/tools/schedules.mjs +55 -0
- package/tools/setup.mjs +168 -0
- package/tools/sops-bridges.mjs +86 -0
- package/tools/sops.mjs +314 -0
- package/tools/sourcing.mjs +50 -2
- package/tools/strategy.mjs +146 -0
- package/tools/studio.mjs +132 -0
- package/tools/venueradar.mjs +151 -0
- package/tools/voice.mjs +134 -0
- package/tools.mjs +70 -12
package/tools.mjs
CHANGED
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
* wants one side of the workspace. `full` is the default and holds everything, so
|
|
27
27
|
* an existing client sees no change.
|
|
28
28
|
*/
|
|
29
|
-
import { readdirSync } from 'node:fs';
|
|
29
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
30
30
|
import { z } from 'zod';
|
|
31
31
|
|
|
32
32
|
export const SERVER_NAME = 'kmhub';
|
|
33
|
-
export const SERVER_VERSION = '2.
|
|
33
|
+
export const SERVER_VERSION = '2.9.1';
|
|
34
34
|
|
|
35
35
|
export const DEFAULT_BASE =
|
|
36
36
|
process.env.KMHUB_API_BASE ||
|
|
@@ -89,7 +89,15 @@ export function out(r) {
|
|
|
89
89
|
lines.push(url ? `To pick things back up, open ${url} and restart the subscription.` : BILLING_FALLBACK);
|
|
90
90
|
return { content: [{ type: 'text', text: lines.join('\n\n') }], isError: true };
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
// The way back to the window (Freshdesk #271, Jackie): "I always have this problem
|
|
93
|
+
// of not getting back to the correct window using Claude and KM Hub once it is
|
|
94
|
+
// closed." A route that answers out of one page of the app says so in
|
|
95
|
+
// `open_in_km_hub`, and that line is appended in plain text rather than left buried
|
|
96
|
+
// in the JSON, so the model reliably shows her a link she can click twice.
|
|
97
|
+
const link = typeof body.open_in_km_hub === 'string' ? body.open_in_km_hub.trim() : '';
|
|
98
|
+
const json = JSON.stringify(r.data, null, 2);
|
|
99
|
+
const payload = link ? `${json}\n\nOpen in KM Hub: ${link}` : json;
|
|
100
|
+
return { content: [{ type: 'text', text: payload }], isError: !r.ok };
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
/** A plain prose answer. Defaults to a success result: not every "no" is an error. */
|
|
@@ -138,7 +146,8 @@ export function qs(params) {
|
|
|
138
146
|
// A play's task calls tools from every family at once, so a run
|
|
139
147
|
// started on a narrow profile fails halfway through, which is a
|
|
140
148
|
// worse outcome than not offering the play.
|
|
141
|
-
// money
|
|
149
|
+
// money The money side, whole. The read-only money tools, plus hr because
|
|
150
|
+
// payroll is money owed to the people who did the work, plus crm
|
|
142
151
|
// so you can open the client you are about to chase, plus knowledge
|
|
143
152
|
// because the published ladder is the only legal source of a number.
|
|
144
153
|
// outreach 51 The cold outreach machine: outreach and sourcing, plus crm to turn
|
|
@@ -147,19 +156,28 @@ export function qs(params) {
|
|
|
147
156
|
// saves the least of the three, and that is honest rather than
|
|
148
157
|
// disappointing: the outreach job genuinely reaches most of the
|
|
149
158
|
// workspace. `core` is the profile that buys real context back.
|
|
150
|
-
//
|
|
159
|
+
// content The being-found side: SEO and the newsletter, plus crm and
|
|
160
|
+
// knowledge, because content that does not know who buys or what
|
|
161
|
+
// the business sounds like is generic content. This profile only
|
|
162
|
+
// became real when the marketing family shipped (workstream B1);
|
|
163
|
+
// before that the name existed in the installer and silently fell
|
|
164
|
+
// back to `full`, which was a small lie told to anyone who used it.
|
|
165
|
+
// full Everything, including the plays. The default.
|
|
166
|
+
// coach Fully Booked Coach operations, client delivery, acquisition,
|
|
167
|
+
// content requests, and approval previews. It exposes no send
|
|
168
|
+
// or publish tool.
|
|
151
169
|
//
|
|
152
|
-
//
|
|
153
|
-
// client that still asks for one gets `full` rather than an error, exactly as any
|
|
154
|
-
// other unknown name does.
|
|
170
|
+
// An unknown profile name still gets `full` rather than an error, exactly as before.
|
|
155
171
|
|
|
156
172
|
export const DEFAULT_PROFILE = 'full';
|
|
157
173
|
|
|
158
174
|
export const PROFILES = {
|
|
159
175
|
core: ['core', 'meta', 'briefing'],
|
|
160
|
-
money: ['core', 'meta', 'briefing', 'crm', 'knowledge', 'money'],
|
|
161
|
-
outreach: ['core', 'meta', 'briefing', 'crm', 'calendar', 'knowledge', 'outreach', 'sourcing'],
|
|
176
|
+
money: ['core', 'meta', 'briefing', 'crm', 'knowledge', 'money', 'hr'],
|
|
177
|
+
outreach: ['core', 'meta', 'briefing', 'crm', 'calendar', 'knowledge', 'outreach', 'sourcing', 'marketing'],
|
|
178
|
+
content: ['core', 'meta', 'briefing', 'crm', 'knowledge', 'marketing'],
|
|
162
179
|
full: '*',
|
|
180
|
+
coach: ['coach'],
|
|
163
181
|
};
|
|
164
182
|
|
|
165
183
|
/**
|
|
@@ -168,6 +186,10 @@ export const PROFILES = {
|
|
|
168
186
|
* has to be named here on its own, because it is placed too and placed nowhere but
|
|
169
187
|
* `full`: without this line its own ['*'] would put it back into all three.
|
|
170
188
|
*/
|
|
189
|
+
// Coach is a public pilot surface with an explicit safety review. New families
|
|
190
|
+
// must not silently join it through a wildcard declaration.
|
|
191
|
+
const CLOSED_PROFILES = new Set(['coach']);
|
|
192
|
+
|
|
171
193
|
const PLACED = new Set([...Object.values(PROFILES).filter(Array.isArray).flat(), 'plays']);
|
|
172
194
|
|
|
173
195
|
export const PROFILE_NAMES = Object.keys(PROFILES);
|
|
@@ -256,6 +278,7 @@ function inProfile(family, profile) {
|
|
|
256
278
|
const listed = PROFILES[profile];
|
|
257
279
|
if (listed === '*') return true;
|
|
258
280
|
if (Array.isArray(listed) && listed.includes(family.FAMILY)) return true;
|
|
281
|
+
if (CLOSED_PROFILES.has(profile)) return false;
|
|
259
282
|
if (PLACED.has(family.FAMILY)) return false;
|
|
260
283
|
return family.PROFILES.includes('*') || family.PROFILES.includes(profile);
|
|
261
284
|
}
|
|
@@ -286,6 +309,41 @@ export const TOOL_NAMES = toolNamesFor(DEFAULT_PROFILE);
|
|
|
286
309
|
* make the SDK throw mid-request and cost the caller every tool, not just the
|
|
287
310
|
* clashing one. `raw` is there for the rare family that needs the real server.
|
|
288
311
|
*/
|
|
312
|
+
/**
|
|
313
|
+
* The tools announced as read-only, from read-only-tools.json (generated from the tool
|
|
314
|
+
* source by scripts/generate-read-only-tools.mjs - see there for the rule).
|
|
315
|
+
*
|
|
316
|
+
* 🚨 CODEX STOPS EVERY UNANNOTATED TOOL FOR AN APPROVAL. With no hint on any KM Hub tool,
|
|
317
|
+
* a Codex session asked permission before `km_me`, and `codex exec` failed every call with
|
|
318
|
+
* "MCP tool call requires approval". readOnlyHint: true is what lets a read run. Writes stay
|
|
319
|
+
* unannotated on purpose, so the person still approves each one.
|
|
320
|
+
*
|
|
321
|
+
* A missing or broken file costs the hints, never the tools.
|
|
322
|
+
*/
|
|
323
|
+
const READ_ONLY_TOOLS = (() => {
|
|
324
|
+
try {
|
|
325
|
+
const doc = JSON.parse(readFileSync(new URL('./read-only-tools.json', import.meta.url), 'utf8'));
|
|
326
|
+
return new Set(Array.isArray(doc.tools) ? doc.tools : []);
|
|
327
|
+
} catch (e) {
|
|
328
|
+
warn(`read-only-tools.json could not be read (${String(e?.message || e)}). Every tool will be announced without a read-only hint.`);
|
|
329
|
+
return new Set();
|
|
330
|
+
}
|
|
331
|
+
})();
|
|
332
|
+
|
|
333
|
+
export function isReadOnlyTool(name) {
|
|
334
|
+
return READ_ONLY_TOOLS.has(name);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function markReadOnly(name, registeredTool) {
|
|
338
|
+
if (!registeredTool || !READ_ONLY_TOOLS.has(name) || typeof registeredTool.update !== 'function') return registeredTool;
|
|
339
|
+
try {
|
|
340
|
+
registeredTool.update({ annotations: { ...(registeredTool.annotations || {}), readOnlyHint: true } });
|
|
341
|
+
} catch (e) {
|
|
342
|
+
warn(`could not mark '${name}' read-only (${String(e?.message || e)}). It still works; hosts will ask before running it.`);
|
|
343
|
+
}
|
|
344
|
+
return registeredTool;
|
|
345
|
+
}
|
|
346
|
+
|
|
289
347
|
function guardedRegistrar(server, taken, family, registered) {
|
|
290
348
|
const claim = (name) => {
|
|
291
349
|
if (typeof name !== 'string' || !name.trim()) {
|
|
@@ -302,8 +360,8 @@ function guardedRegistrar(server, taken, family, registered) {
|
|
|
302
360
|
return true;
|
|
303
361
|
};
|
|
304
362
|
return {
|
|
305
|
-
tool: (name, ...rest) => (claim(name) ? server.tool(name, ...rest) : undefined),
|
|
306
|
-
registerTool: (name, ...rest) => (claim(name) ? server.registerTool(name, ...rest) : undefined),
|
|
363
|
+
tool: (name, ...rest) => (claim(name) ? markReadOnly(name, server.tool(name, ...rest)) : undefined),
|
|
364
|
+
registerTool: (name, ...rest) => (claim(name) ? markReadOnly(name, server.registerTool(name, ...rest)) : undefined),
|
|
307
365
|
prompt: (...args) => server.prompt(...args),
|
|
308
366
|
resource: (...args) => server.resource(...args),
|
|
309
367
|
raw: server,
|