@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/prompts/setup.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "Install or refresh the KM Hub rules pack, and check whether the KM Hub connection is current. Use for /kmhub-setup, when the user says set up my KM Hub rules, or asks whether their KM Hub connection, connector or rules are up to date, out of date, or need updating."
|
|
3
|
-
disable-model-invocation: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Set up KM Hub
|
|
7
|
-
|
|
8
|
-
The rules pack is how Claude learns how this particular business is set up: its offers, its pricing, its voice, its verticals. Until it is installed the tools work, but every answer is generic.
|
|
9
|
-
|
|
10
|
-
## Check first
|
|
11
|
-
|
|
12
|
-
Call `km_check_updates`. It compares the connector running right now, and the rules file already on this machine, against what KM Hub publishes today, then gives a plain-language verdict.
|
|
13
|
-
|
|
14
|
-
- Rules missing or stale: call `km_fetch_rules` and install what it returns.
|
|
15
|
-
- Everything current: say so in one line and stop. Do not refetch for the sake of it.
|
|
16
|
-
|
|
17
|
-
## Where the rules go
|
|
18
|
-
|
|
19
|
-
`km_fetch_rules` returns the pack. It is written into a `CLAUDE.md` as a single clearly marked KM Hub block.
|
|
20
|
-
|
|
21
|
-
**Ask which file before writing, and say why.** A user's own `CLAUDE.md` is often long and hand-tuned, and a rules pack landing in the middle of it is a surprise nobody asked for. Offer the choice plainly: their global `~/.claude/CLAUDE.md`, the project they are standing in, or a folder kept for KM Hub work. Replace any existing KM Hub block rather than appending a second one.
|
|
22
|
-
|
|
23
|
-
## What the pack is, and is not
|
|
24
|
-
|
|
25
|
-
- It is **rendered per workspace from live data**, so it goes stale on its own. Refetching is the update path; editing it by hand is not.
|
|
26
|
-
- The only KM Hub things on this machine are a URL, a key, and that file. No workspace data is stored locally.
|
|
27
|
-
- If the workspace settings and the real business disagree, the pack says so and names which one wins. Believe it over your own reading.
|
|
28
|
-
|
|
29
|
-
## If it will not install
|
|
30
|
-
|
|
31
|
-
- **402**: the subscription is not active, so Terminal Mode is switched off. The key and the data are both fine. Restart at https://hub.kivimedia.co and it works again within about a minute.
|
|
32
|
-
- **401 or 403**: the key is wrong, revoked, or missing a scope. Reinstall the connector from https://hub.kivimedia.co/terminal/ and it will mint a fresh one.
|
|
33
|
-
- **The route is not there yet**: this is reported as success on purpose. A KM Hub without update endpoints is not a broken KM Hub. Say the pack is unavailable on this workspace and carry on.
|
|
1
|
+
---
|
|
2
|
+
description: "Install or refresh the KM Hub rules pack, and check whether the KM Hub connection is current. Use for /kmhub-setup, when the user says set up my KM Hub rules, or asks whether their KM Hub connection, connector or rules are up to date, out of date, or need updating."
|
|
3
|
+
disable-model-invocation: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Set up KM Hub
|
|
7
|
+
|
|
8
|
+
The rules pack is how Claude learns how this particular business is set up: its offers, its pricing, its voice, its verticals. Until it is installed the tools work, but every answer is generic.
|
|
9
|
+
|
|
10
|
+
## Check first
|
|
11
|
+
|
|
12
|
+
Call `km_check_updates`. It compares the connector running right now, and the rules file already on this machine, against what KM Hub publishes today, then gives a plain-language verdict.
|
|
13
|
+
|
|
14
|
+
- Rules missing or stale: call `km_fetch_rules` and install what it returns.
|
|
15
|
+
- Everything current: say so in one line and stop. Do not refetch for the sake of it.
|
|
16
|
+
|
|
17
|
+
## Where the rules go
|
|
18
|
+
|
|
19
|
+
`km_fetch_rules` returns the pack. It is written into a `CLAUDE.md` as a single clearly marked KM Hub block.
|
|
20
|
+
|
|
21
|
+
**Ask which file before writing, and say why.** A user's own `CLAUDE.md` is often long and hand-tuned, and a rules pack landing in the middle of it is a surprise nobody asked for. Offer the choice plainly: their global `~/.claude/CLAUDE.md`, the project they are standing in, or a folder kept for KM Hub work. Replace any existing KM Hub block rather than appending a second one.
|
|
22
|
+
|
|
23
|
+
## What the pack is, and is not
|
|
24
|
+
|
|
25
|
+
- It is **rendered per workspace from live data**, so it goes stale on its own. Refetching is the update path; editing it by hand is not.
|
|
26
|
+
- The only KM Hub things on this machine are a URL, a key, and that file. No workspace data is stored locally.
|
|
27
|
+
- If the workspace settings and the real business disagree, the pack says so and names which one wins. Believe it over your own reading.
|
|
28
|
+
|
|
29
|
+
## If it will not install
|
|
30
|
+
|
|
31
|
+
- **402**: the subscription is not active, so Terminal Mode is switched off. The key and the data are both fine. Restart at https://hub.kivimedia.co and it works again within about a minute.
|
|
32
|
+
- **401 or 403**: the key is wrong, revoked, or missing a scope. Reinstall the connector from https://hub.kivimedia.co/terminal/ and it will mint a fresh one.
|
|
33
|
+
- **The route is not there yet**: this is reported as success on purpose. A KM Hub without update endpoints is not a broken KM Hub. Say the pack is unavailable on this workspace and carry on.
|
package/prompts/vs-booked.md
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "An honest, computed comparison of KM Hub Terminal Mode against Booked Solid's terminal, from live data rather than a slide. Use for /kmhub-vs-booked, or when the user asks how KM Hub compares to Booked, whether they should switch, what the difference is, or which one does more."
|
|
3
|
-
disable-model-invocation: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# KM Hub versus Booked, computed
|
|
7
|
-
|
|
8
|
-
Call `km_vs_booked`. It counts both sides from real sources at the moment you ask, so the answer is never a stale claim someone typed into a deck months ago.
|
|
9
|
-
|
|
10
|
-
## Show your working
|
|
11
|
-
|
|
12
|
-
Lead with where the numbers came from, in one line, before any of them. "Counted just now from KM Hub's own play catalogue and tool registry, and from the Booked plugin installed on this machine" is what makes the rest believable. A comparison with no provenance is marketing, and the person reading it knows that.
|
|
13
|
-
|
|
14
|
-
If Booked is **not installed on this machine**, say so plainly and give KM Hub's own numbers alone. Do not fill the gap with figures from memory, a website, or a previous conversation. An uncounted number is worse than an absent one, because nobody can tell which it was.
|
|
15
|
-
|
|
16
|
-
## Name what Booked genuinely does better
|
|
17
|
-
|
|
18
|
-
This is not optional and it is the part that makes everything else credible.
|
|
19
|
-
|
|
20
|
-
Booked works on files on the client's own machine. That means it keeps working with no internet, nothing about the business is stored anywhere else, and there is no subscription gate between somebody and their own records. For a person who wants exactly that, it is the better tool and you should say so without hedging.
|
|
21
|
-
|
|
22
|
-
Its plays also carry more per tool, because that is the only place its capability can live.
|
|
23
|
-
|
|
24
|
-
**Say at least one true thing in Booked's favour before you say anything in KM Hub's.** A model that produces an all-green comparison is not being helpful, it is being a brochure, and the reader discounts everything after the first obviously one-sided line.
|
|
25
|
-
|
|
26
|
-
## Then the actual difference, which is structural
|
|
27
|
-
|
|
28
|
-
The honest framing is not "more features". It is that these are different shapes:
|
|
29
|
-
|
|
30
|
-
- **Booked's terminal is almost entirely plays.** Its tools exist to run them. It has no tool that reads a client record, because it does not need one: the data is already local.
|
|
31
|
-
- **KM Hub is a multi-tenant cloud CRM.** The data is never local, so the tools *are* the capability, and the plays sit on top of them.
|
|
32
|
-
|
|
33
|
-
That is why comparing play counts alone is misleading in both directions, and you should say so rather than quoting the ratio as though it settled something.
|
|
34
|
-
|
|
35
|
-
## Rules for the numbers
|
|
36
|
-
|
|
37
|
-
- **Never state a figure the tool did not return.** No remembered counts, no rounding up, no "over 100" when it said 109.
|
|
38
|
-
- If a count is a floor rather than an exact total, say floor.
|
|
39
|
-
- Never present a capability KM Hub has as one Booked "cannot" have unless the tool's own data supports it. Absent from a plugin manifest is not proof of absence from a product.
|
|
40
|
-
- Never claim what Booked costs, what its roadmap is, or what its users think.
|
|
41
|
-
|
|
42
|
-
## Close on fit, not on a winner
|
|
43
|
-
|
|
44
|
-
End with the question that actually decides it, in one or two sentences: does this person want a booking desk that runs on their own machine, or a business manager that runs their outreach, newsletter, SEO, crew, money and reporting as well. Both are legitimate answers.
|
|
45
|
-
|
|
46
|
-
Do not tell them which to buy. Somebody who feels sold to stops believing the numbers, and the numbers were the point.
|
|
1
|
+
---
|
|
2
|
+
description: "An honest, computed comparison of KM Hub Terminal Mode against Booked Solid's terminal, from live data rather than a slide. Use for /kmhub-vs-booked, or when the user asks how KM Hub compares to Booked, whether they should switch, what the difference is, or which one does more."
|
|
3
|
+
disable-model-invocation: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# KM Hub versus Booked, computed
|
|
7
|
+
|
|
8
|
+
Call `km_vs_booked`. It counts both sides from real sources at the moment you ask, so the answer is never a stale claim someone typed into a deck months ago.
|
|
9
|
+
|
|
10
|
+
## Show your working
|
|
11
|
+
|
|
12
|
+
Lead with where the numbers came from, in one line, before any of them. "Counted just now from KM Hub's own play catalogue and tool registry, and from the Booked plugin installed on this machine" is what makes the rest believable. A comparison with no provenance is marketing, and the person reading it knows that.
|
|
13
|
+
|
|
14
|
+
If Booked is **not installed on this machine**, say so plainly and give KM Hub's own numbers alone. Do not fill the gap with figures from memory, a website, or a previous conversation. An uncounted number is worse than an absent one, because nobody can tell which it was.
|
|
15
|
+
|
|
16
|
+
## Name what Booked genuinely does better
|
|
17
|
+
|
|
18
|
+
This is not optional and it is the part that makes everything else credible.
|
|
19
|
+
|
|
20
|
+
Booked works on files on the client's own machine. That means it keeps working with no internet, nothing about the business is stored anywhere else, and there is no subscription gate between somebody and their own records. For a person who wants exactly that, it is the better tool and you should say so without hedging.
|
|
21
|
+
|
|
22
|
+
Its plays also carry more per tool, because that is the only place its capability can live.
|
|
23
|
+
|
|
24
|
+
**Say at least one true thing in Booked's favour before you say anything in KM Hub's.** A model that produces an all-green comparison is not being helpful, it is being a brochure, and the reader discounts everything after the first obviously one-sided line.
|
|
25
|
+
|
|
26
|
+
## Then the actual difference, which is structural
|
|
27
|
+
|
|
28
|
+
The honest framing is not "more features". It is that these are different shapes:
|
|
29
|
+
|
|
30
|
+
- **Booked's terminal is almost entirely plays.** Its tools exist to run them. It has no tool that reads a client record, because it does not need one: the data is already local.
|
|
31
|
+
- **KM Hub is a multi-tenant cloud CRM.** The data is never local, so the tools *are* the capability, and the plays sit on top of them.
|
|
32
|
+
|
|
33
|
+
That is why comparing play counts alone is misleading in both directions, and you should say so rather than quoting the ratio as though it settled something.
|
|
34
|
+
|
|
35
|
+
## Rules for the numbers
|
|
36
|
+
|
|
37
|
+
- **Never state a figure the tool did not return.** No remembered counts, no rounding up, no "over 100" when it said 109.
|
|
38
|
+
- If a count is a floor rather than an exact total, say floor.
|
|
39
|
+
- Never present a capability KM Hub has as one Booked "cannot" have unless the tool's own data supports it. Absent from a plugin manifest is not proof of absence from a product.
|
|
40
|
+
- Never claim what Booked costs, what its roadmap is, or what its users think.
|
|
41
|
+
|
|
42
|
+
## Close on fit, not on a winner
|
|
43
|
+
|
|
44
|
+
End with the question that actually decides it, in one or two sentences: does this person want a booking desk that runs on their own machine, or a business manager that runs their outreach, newsletter, SEO, crew, money and reporting as well. Both are legitimate answers.
|
|
45
|
+
|
|
46
|
+
Do not tell them which to buy. Somebody who feels sold to stops believing the numbers, and the numbers were the point.
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "A tour of everything KM Hub can do for this business, and honestly which parts work from the terminal. Use for /kmhub-what-can-you-do, or when the user asks what KM Hub can do, what else it does, whether it handles some area of their business, whether it replaces another tool they pay for, or says they did not know it could do something."
|
|
3
|
-
argument-hint: an area to focus on, or nothing for the whole product
|
|
4
|
-
disable-model-invocation: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# What KM Hub can do
|
|
8
|
-
|
|
9
|
-
Call `km_capabilities`. If the user named an area, pass the matching `pillar`. Otherwise take the whole product.
|
|
10
|
-
|
|
11
|
-
This is the answer to "what am I actually paying for", so it is worth doing properly rather than reciting a list.
|
|
12
|
-
|
|
13
|
-
## Give them the shape first
|
|
14
|
-
|
|
15
|
-
Open with the seven pillars and the one-line tagline for each, then the totals: how many feature pages there are, and how many you can drive from here. A person who has only seen the booking side genuinely does not know the rest exists.
|
|
16
|
-
|
|
17
|
-
Then go deeper only where they showed interest, or where the workspace suggests it. If you have already called `km_briefing` this session, use what it found: someone with 96 quiet deals should hear about Silence Patrol and Campaigns, not a flat alphabetical tour.
|
|
18
|
-
|
|
19
|
-
## Say the honest thing about reach
|
|
20
|
-
|
|
21
|
-
Every page carries `reach`.
|
|
22
|
-
|
|
23
|
-
- **`terminal`**: you can do it from here. Offer to.
|
|
24
|
-
- **`web_only`**: KM Hub does it, this connector cannot yet. Name the page, say it lives in the web app at https://hub.kivimedia.co, and move on.
|
|
25
|
-
|
|
26
|
-
🚨 **Never offer to do a `web_only` thing.** "KM Hub does your newsletters, that one is in the web app" builds trust. Promising it and then failing destroys more than the feature was worth. Do not blur this to sound more capable.
|
|
27
|
-
|
|
28
|
-
Do not apologise for the split either. Most of the product is reachable and the rest is a click away in a browser they already have open.
|
|
29
|
-
|
|
30
|
-
## What to lead with
|
|
31
|
-
|
|
32
|
-
Lead with what is unusual and what they are probably not using. The interesting facts are that this is not a booking tool: it runs outbound campaigns, newsletters, SEO and AI-search visibility, reviews, payroll, job costing, inventory and a set of AI officers that make recommendations. If they are comparing KM Hub to something that only handles bookings, that comparison is the thing to correct, without naming a competitor unless they do.
|
|
33
|
-
|
|
34
|
-
The other thing they are probably not using is the plays: the methods in `km_play_catalog`, including the fifteen luxury plays built from The Luxury Strategy, which cover pricing, the offer ladder, what the website says about price, discounting, scarcity and where the marketing effort goes. If the conversation turns to money, positioning or how the business is presented, name that family and offer the play that fits (`luxury-positioning-check` is an optional second opinion, never a prerequisite), or point at `/kmhub-luxury` for the tour.
|
|
35
|
-
|
|
36
|
-
## Finish with one offer, not ten
|
|
37
|
-
|
|
38
|
-
End on a single concrete next step drawn from what they reacted to. "Want me to run Silence Patrol on those 96 quiet deals?" beats "let me know what you would like to explore". One offer, theirs to take.
|
|
39
|
-
|
|
40
|
-
This reads a file. It sends nothing, changes nothing and costs nothing.
|
|
1
|
+
---
|
|
2
|
+
description: "A tour of everything KM Hub can do for this business, and honestly which parts work from the terminal. Use for /kmhub-what-can-you-do, or when the user asks what KM Hub can do, what else it does, whether it handles some area of their business, whether it replaces another tool they pay for, or says they did not know it could do something."
|
|
3
|
+
argument-hint: an area to focus on, or nothing for the whole product
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# What KM Hub can do
|
|
8
|
+
|
|
9
|
+
Call `km_capabilities`. If the user named an area, pass the matching `pillar`. Otherwise take the whole product.
|
|
10
|
+
|
|
11
|
+
This is the answer to "what am I actually paying for", so it is worth doing properly rather than reciting a list.
|
|
12
|
+
|
|
13
|
+
## Give them the shape first
|
|
14
|
+
|
|
15
|
+
Open with the seven pillars and the one-line tagline for each, then the totals: how many feature pages there are, and how many you can drive from here. A person who has only seen the booking side genuinely does not know the rest exists.
|
|
16
|
+
|
|
17
|
+
Then go deeper only where they showed interest, or where the workspace suggests it. If you have already called `km_briefing` this session, use what it found: someone with 96 quiet deals should hear about Silence Patrol and Campaigns, not a flat alphabetical tour.
|
|
18
|
+
|
|
19
|
+
## Say the honest thing about reach
|
|
20
|
+
|
|
21
|
+
Every page carries `reach`.
|
|
22
|
+
|
|
23
|
+
- **`terminal`**: you can do it from here. Offer to.
|
|
24
|
+
- **`web_only`**: KM Hub does it, this connector cannot yet. Name the page, say it lives in the web app at https://hub.kivimedia.co, and move on.
|
|
25
|
+
|
|
26
|
+
🚨 **Never offer to do a `web_only` thing.** "KM Hub does your newsletters, that one is in the web app" builds trust. Promising it and then failing destroys more than the feature was worth. Do not blur this to sound more capable.
|
|
27
|
+
|
|
28
|
+
Do not apologise for the split either. Most of the product is reachable and the rest is a click away in a browser they already have open.
|
|
29
|
+
|
|
30
|
+
## What to lead with
|
|
31
|
+
|
|
32
|
+
Lead with what is unusual and what they are probably not using. The interesting facts are that this is not a booking tool: it runs outbound campaigns, newsletters, SEO and AI-search visibility, reviews, payroll, job costing, inventory and a set of AI officers that make recommendations. If they are comparing KM Hub to something that only handles bookings, that comparison is the thing to correct, without naming a competitor unless they do.
|
|
33
|
+
|
|
34
|
+
The other thing they are probably not using is the plays: the methods in `km_play_catalog`, including the fifteen luxury plays built from The Luxury Strategy, which cover pricing, the offer ladder, what the website says about price, discounting, scarcity and where the marketing effort goes. If the conversation turns to money, positioning or how the business is presented, name that family and offer the play that fits (`luxury-positioning-check` is an optional second opinion, never a prerequisite), or point at `/kmhub-luxury` for the tour.
|
|
35
|
+
|
|
36
|
+
## Finish with one offer, not ten
|
|
37
|
+
|
|
38
|
+
End on a single concrete next step drawn from what they reacted to. "Want me to run Silence Patrol on those 96 quiet deals?" beats "let me know what you would like to explore". One offer, theirs to take.
|
|
39
|
+
|
|
40
|
+
This reads a file. It sends nothing, changes nothing and costs nothing.
|
package/prompts.mjs
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The KM Hub commands, served BY the connector rather than copied onto the disk
|
|
3
|
-
* beside it.
|
|
4
|
-
*
|
|
5
|
-
* 🚨 WHY THIS EXISTS. The six commands used to reach a machine only as markdown files
|
|
6
|
-
* that the installer copied into ~/.claude/commands. That is a second delivery channel,
|
|
7
|
-
* independent of the connector, and on 26-Aug-26 it did what independent channels do:
|
|
8
|
-
* it arrived on its own. A client finished an install with all six commands in her slash
|
|
9
|
-
* menu, all five working methods, and no tools behind any of them. The menu looked like
|
|
10
|
-
* proof and was not, because a file on disk knows nothing about whether the connector
|
|
11
|
-
* landed.
|
|
12
|
-
*
|
|
13
|
-
* Served as MCP prompts they cannot arrive alone. Claude Code discovers prompts from
|
|
14
|
-
* connected servers and lists them as `/mcp__kmhub__<name>`, so their presence in the
|
|
15
|
-
* menu is the connection, not a copy of something that once described it.
|
|
16
|
-
*
|
|
17
|
-
* The file copies stay: `/kmhub-briefing` is a friendlier thing to say on a call than
|
|
18
|
-
* `/mcp__kmhub__briefing`, and since installer 1.15.0 it is the one spelling Claude Code,
|
|
19
|
-
* Cursor and Grok Build share, because all three read ~/.claude/commands.
|
|
20
|
-
* src/lib/terminal-installer-copy.test.ts holds the two copies byte-identical so the
|
|
21
|
-
* friendlier name can never drift from the real one.
|
|
22
|
-
*/
|
|
23
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
24
|
-
import { dirname, join } from 'node:path';
|
|
25
|
-
import { fileURLToPath } from 'node:url';
|
|
26
|
-
import { z } from 'zod';
|
|
27
|
-
|
|
28
|
-
const PROMPT_DIR = join(dirname(fileURLToPath(import.meta.url)), 'prompts');
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Split a command file into its frontmatter and its body.
|
|
32
|
-
*
|
|
33
|
-
* Deliberately small: these files are ours, the frontmatter is three flat keys, and a
|
|
34
|
-
* YAML dependency on the server for that would be a supply-chain cost with no return.
|
|
35
|
-
* Anything it cannot parse is treated as body, which fails visibly rather than silently.
|
|
36
|
-
*/
|
|
37
|
-
function parseCommandFile(raw) {
|
|
38
|
-
const text = raw.replace(/^/, '');
|
|
39
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
40
|
-
if (!match) return { meta: {}, body: text.trim() };
|
|
41
|
-
|
|
42
|
-
const meta = {};
|
|
43
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
44
|
-
const at = line.indexOf(':');
|
|
45
|
-
if (at < 1) continue;
|
|
46
|
-
const key = line.slice(0, at).trim();
|
|
47
|
-
let value = line.slice(at + 1).trim();
|
|
48
|
-
if (
|
|
49
|
-
(value.startsWith('"') && value.endsWith('"') && value.length > 1)
|
|
50
|
-
|| (value.startsWith("'") && value.endsWith("'") && value.length > 1)
|
|
51
|
-
) {
|
|
52
|
-
value = value.slice(1, -1);
|
|
53
|
-
}
|
|
54
|
-
meta[key] = value;
|
|
55
|
-
}
|
|
56
|
-
return { meta, body: text.slice(match[0].length).trim() };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Every command file on disk, in a stable order so two servers list them the same way. */
|
|
60
|
-
export function loadPromptDefinitions() {
|
|
61
|
-
const files = readdirSync(PROMPT_DIR).filter((name) => name.endsWith('.md')).sort();
|
|
62
|
-
return files.map((file) => {
|
|
63
|
-
const name = file.replace(/\.md$/, '');
|
|
64
|
-
const { meta, body } = parseCommandFile(readFileSync(join(PROMPT_DIR, file), 'utf8'));
|
|
65
|
-
return {
|
|
66
|
-
name,
|
|
67
|
-
description: meta.description || `The KM Hub ${name} command.`,
|
|
68
|
-
argumentHint: meta['argument-hint'] || '',
|
|
69
|
-
body,
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Register the commands as MCP prompts on an McpServer.
|
|
76
|
-
*
|
|
77
|
-
* @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
|
|
78
|
-
* @returns {string[]} the prompt names registered, so a caller can log or test them
|
|
79
|
-
*/
|
|
80
|
-
export function registerPrompts(server) {
|
|
81
|
-
const registered = [];
|
|
82
|
-
|
|
83
|
-
for (const prompt of loadPromptDefinitions()) {
|
|
84
|
-
// Only ask for an argument where the command actually takes one. A required-looking
|
|
85
|
-
// empty box on /mcp__kmhub__briefing would be a small lie about how the command works.
|
|
86
|
-
const argsSchema = prompt.argumentHint
|
|
87
|
-
? { input: z.string().optional().describe(prompt.argumentHint) }
|
|
88
|
-
: undefined;
|
|
89
|
-
|
|
90
|
-
const handler = (args) => {
|
|
91
|
-
const input = args && typeof args.input === 'string' ? args.input.trim() : '';
|
|
92
|
-
const text = input ? `${prompt.body}\n\n## What was asked for\n\n${input}` : prompt.body;
|
|
93
|
-
return { messages: [{ role: 'user', content: { type: 'text', text } }] };
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
if (argsSchema) {
|
|
98
|
-
server.registerPrompt(prompt.name, { description: prompt.description, argsSchema }, handler);
|
|
99
|
-
} else {
|
|
100
|
-
server.registerPrompt(prompt.name, { description: prompt.description }, handler);
|
|
101
|
-
}
|
|
102
|
-
registered.push(prompt.name);
|
|
103
|
-
} catch {
|
|
104
|
-
// One unregistrable prompt must never cost the tools. The connector is the
|
|
105
|
-
// product; these are a way of reaching it.
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return registered;
|
|
110
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The KM Hub commands, served BY the connector rather than copied onto the disk
|
|
3
|
+
* beside it.
|
|
4
|
+
*
|
|
5
|
+
* 🚨 WHY THIS EXISTS. The six commands used to reach a machine only as markdown files
|
|
6
|
+
* that the installer copied into ~/.claude/commands. That is a second delivery channel,
|
|
7
|
+
* independent of the connector, and on 26-Aug-26 it did what independent channels do:
|
|
8
|
+
* it arrived on its own. A client finished an install with all six commands in her slash
|
|
9
|
+
* menu, all five working methods, and no tools behind any of them. The menu looked like
|
|
10
|
+
* proof and was not, because a file on disk knows nothing about whether the connector
|
|
11
|
+
* landed.
|
|
12
|
+
*
|
|
13
|
+
* Served as MCP prompts they cannot arrive alone. Claude Code discovers prompts from
|
|
14
|
+
* connected servers and lists them as `/mcp__kmhub__<name>`, so their presence in the
|
|
15
|
+
* menu is the connection, not a copy of something that once described it.
|
|
16
|
+
*
|
|
17
|
+
* The file copies stay: `/kmhub-briefing` is a friendlier thing to say on a call than
|
|
18
|
+
* `/mcp__kmhub__briefing`, and since installer 1.15.0 it is the one spelling Claude Code,
|
|
19
|
+
* Cursor and Grok Build share, because all three read ~/.claude/commands.
|
|
20
|
+
* src/lib/terminal-installer-copy.test.ts holds the two copies byte-identical so the
|
|
21
|
+
* friendlier name can never drift from the real one.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
|
|
28
|
+
const PROMPT_DIR = join(dirname(fileURLToPath(import.meta.url)), 'prompts');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Split a command file into its frontmatter and its body.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately small: these files are ours, the frontmatter is three flat keys, and a
|
|
34
|
+
* YAML dependency on the server for that would be a supply-chain cost with no return.
|
|
35
|
+
* Anything it cannot parse is treated as body, which fails visibly rather than silently.
|
|
36
|
+
*/
|
|
37
|
+
function parseCommandFile(raw) {
|
|
38
|
+
const text = raw.replace(/^/, '');
|
|
39
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
40
|
+
if (!match) return { meta: {}, body: text.trim() };
|
|
41
|
+
|
|
42
|
+
const meta = {};
|
|
43
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
44
|
+
const at = line.indexOf(':');
|
|
45
|
+
if (at < 1) continue;
|
|
46
|
+
const key = line.slice(0, at).trim();
|
|
47
|
+
let value = line.slice(at + 1).trim();
|
|
48
|
+
if (
|
|
49
|
+
(value.startsWith('"') && value.endsWith('"') && value.length > 1)
|
|
50
|
+
|| (value.startsWith("'") && value.endsWith("'") && value.length > 1)
|
|
51
|
+
) {
|
|
52
|
+
value = value.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
meta[key] = value;
|
|
55
|
+
}
|
|
56
|
+
return { meta, body: text.slice(match[0].length).trim() };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Every command file on disk, in a stable order so two servers list them the same way. */
|
|
60
|
+
export function loadPromptDefinitions() {
|
|
61
|
+
const files = readdirSync(PROMPT_DIR).filter((name) => name.endsWith('.md')).sort();
|
|
62
|
+
return files.map((file) => {
|
|
63
|
+
const name = file.replace(/\.md$/, '');
|
|
64
|
+
const { meta, body } = parseCommandFile(readFileSync(join(PROMPT_DIR, file), 'utf8'));
|
|
65
|
+
return {
|
|
66
|
+
name,
|
|
67
|
+
description: meta.description || `The KM Hub ${name} command.`,
|
|
68
|
+
argumentHint: meta['argument-hint'] || '',
|
|
69
|
+
body,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Register the commands as MCP prompts on an McpServer.
|
|
76
|
+
*
|
|
77
|
+
* @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
|
|
78
|
+
* @returns {string[]} the prompt names registered, so a caller can log or test them
|
|
79
|
+
*/
|
|
80
|
+
export function registerPrompts(server) {
|
|
81
|
+
const registered = [];
|
|
82
|
+
|
|
83
|
+
for (const prompt of loadPromptDefinitions()) {
|
|
84
|
+
// Only ask for an argument where the command actually takes one. A required-looking
|
|
85
|
+
// empty box on /mcp__kmhub__briefing would be a small lie about how the command works.
|
|
86
|
+
const argsSchema = prompt.argumentHint
|
|
87
|
+
? { input: z.string().optional().describe(prompt.argumentHint) }
|
|
88
|
+
: undefined;
|
|
89
|
+
|
|
90
|
+
const handler = (args) => {
|
|
91
|
+
const input = args && typeof args.input === 'string' ? args.input.trim() : '';
|
|
92
|
+
const text = input ? `${prompt.body}\n\n## What was asked for\n\n${input}` : prompt.body;
|
|
93
|
+
return { messages: [{ role: 'user', content: { type: 'text', text } }] };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (argsSchema) {
|
|
98
|
+
server.registerPrompt(prompt.name, { description: prompt.description, argsSchema }, handler);
|
|
99
|
+
} else {
|
|
100
|
+
server.registerPrompt(prompt.name, { description: prompt.description }, handler);
|
|
101
|
+
}
|
|
102
|
+
registered.push(prompt.name);
|
|
103
|
+
} catch {
|
|
104
|
+
// One unregistrable prompt must never cost the tools. The connector is the
|
|
105
|
+
// product; these are a way of reaching it.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return registered;
|
|
110
|
+
}
|