@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.
Files changed (57) hide show
  1. package/README.md +170 -170
  2. package/bin/kmhub.mjs +896 -896
  3. package/coach-book-output-guard.mjs +760 -760
  4. package/index.mjs +57 -57
  5. package/package.json +56 -56
  6. package/prompts/briefing.md +29 -29
  7. package/prompts/luxury.md +70 -70
  8. package/prompts/play.md +49 -49
  9. package/prompts/run.md +37 -36
  10. package/prompts/setup.md +33 -33
  11. package/prompts/vs-booked.md +46 -46
  12. package/prompts/what-can-you-do.md +40 -40
  13. package/prompts.mjs +110 -110
  14. package/read-only-tools.json +143 -142
  15. package/remote.mjs +929 -929
  16. package/tools/balloon-costing.mjs +80 -80
  17. package/tools/booking-equipment.mjs +110 -110
  18. package/tools/bridges.mjs +54 -54
  19. package/tools/briefing.mjs +91 -91
  20. package/tools/calendar.mjs +170 -170
  21. package/tools/capabilities.mjs +155 -155
  22. package/tools/catalog.mjs +288 -288
  23. package/tools/clubs.mjs +176 -176
  24. package/tools/coach.mjs +771 -771
  25. package/tools/compare.mjs +76 -76
  26. package/tools/core.mjs +244 -244
  27. package/tools/crm.mjs +209 -209
  28. package/tools/dubsado.mjs +137 -137
  29. package/tools/exports.mjs +128 -128
  30. package/tools/fact-review.mjs +125 -125
  31. package/tools/flows.mjs +261 -261
  32. package/tools/forms.mjs +158 -158
  33. package/tools/gols.mjs +144 -134
  34. package/tools/hr.mjs +162 -162
  35. package/tools/knowledge.mjs +129 -125
  36. package/tools/marketing.mjs +396 -396
  37. package/tools/meta.mjs +245 -245
  38. package/tools/military.mjs +244 -244
  39. package/tools/money.mjs +235 -197
  40. package/tools/outreach.mjs +238 -238
  41. package/tools/pending.mjs +122 -122
  42. package/tools/photos.mjs +140 -140
  43. package/tools/plays.mjs +244 -244
  44. package/tools/profile.mjs +118 -118
  45. package/tools/radar.mjs +173 -173
  46. package/tools/recurring-invoices.mjs +149 -149
  47. package/tools/reengage.mjs +434 -434
  48. package/tools/schedules.mjs +55 -55
  49. package/tools/setup.mjs +168 -168
  50. package/tools/sops-bridges.mjs +86 -86
  51. package/tools/sops.mjs +314 -314
  52. package/tools/sourcing.mjs +268 -268
  53. package/tools/strategy.mjs +146 -146
  54. package/tools/studio.mjs +132 -132
  55. package/tools/venueradar.mjs +151 -151
  56. package/tools/voice.mjs +137 -134
  57. package/tools.mjs +407 -407
@@ -1,146 +1,146 @@
1
- /**
2
- * Tool family: strategy - the officers, what they proposed, and what was done.
3
- *
4
- * Workstream B6. Strategy was 2 of 8: the pillar that makes "a COO and beyond"
5
- * true rather than aspirational had almost no terminal reach. A connector that
6
- * reads the pipeline but cannot see the decisions waiting on the owner, or what was
7
- * already done in their name, is a reporting tool rather than an operator.
8
- *
9
- * km_list_decisions -> GET /strategy/decisions
10
- * km_list_ai_officers -> GET /strategy/officers
11
- * km_list_recommendations -> GET /strategy/advice
12
- * km_list_ai_activity -> GET /strategy/activity
13
- * km_list_weekly_interviews-> GET /strategy/interviews
14
- *
15
- * 🚨 READ ONLY, and approval especially so. Approving a COO proposal executes real
16
- * work against real records: it is the most consequential button in the product and
17
- * it deserves its own decision when B8 settles write scope, not to inherit a general
18
- * one. Never imply you can approve something.
19
- *
20
- * The family contract this file follows is documented in ./README.md.
21
- */
22
- import { z } from 'zod';
23
-
24
- export const FAMILY = 'strategy';
25
-
26
- export const TOOLS = [
27
- 'km_list_decisions',
28
- 'km_list_ai_officers',
29
- 'km_list_recommendations',
30
- 'km_list_ai_activity',
31
- 'km_list_weekly_interviews',
32
- ];
33
-
34
- export const PROFILES = ['*'];
35
-
36
- function routeMissing(r) {
37
- return r.status === 404 || r.status === 405 || r.status === 501;
38
- }
39
-
40
- const NOT_SUPPORTED =
41
- 'Your KM Hub does not expose the AI Team surface to the terminal yet. That is not a fault: the workspace is fine ' +
42
- 'and every other tool works as normal. The officers, decisions and activity log are all there in the web app at ' +
43
- 'https://hub.kivimedia.co, and this connector will start reading them once your KM Hub is on a build that publishes them.';
44
-
45
- export function register(server, call, { out, text, qs }) {
46
- server.tool(
47
- 'km_list_decisions',
48
- 'The proposals the COO has put up for a human decision, what each one would actually DO, and how long it has been ' +
49
- 'waiting. ' +
50
- 'Call this whenever the user asks what needs them, what is outstanding, or what the AI has suggested, and any time ' +
51
- 'you are summarising the state of the business. A decision sitting unmade is different from a task not done: ' +
52
- 'nothing moves until somebody says yes, and the workspace will not chase them for it. ' +
53
- '🚨 Lead with `waiting_on_you` and `oldest_waiting_days`. A proposal that has been pending for weeks is usually one ' +
54
- 'the owner never saw rather than one they decided against, and saying the number out loud is the whole value here. ' +
55
- 'Read `risk` and `what_it_would_do` before describing anything: they are not all equivalent, and a high-risk ' +
56
- 'proposal deserves to be presented as one. ' +
57
- 'Nothing here can approve or reject. That happens in KM Hub, deliberately, because approval executes real work. ' +
58
- 'Read only.',
59
- {
60
- status: z
61
- .enum(['pending', 'approved', 'rejected', 'executed', 'failed', 'expired'])
62
- .optional()
63
- .describe('Narrow to one state. "pending" is what is waiting on a person, which is usually the real question.'),
64
- limit: z.number().int().min(1).max(100).optional().describe('How many, newest first. Default 30.'),
65
- },
66
- async ({ status, limit }) => {
67
- const r = await call('GET', `/strategy/decisions${qs({ status, limit })}`);
68
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
69
- return out(r);
70
- },
71
- );
72
-
73
- server.tool(
74
- 'km_list_ai_officers',
75
- 'Which AI officers this workspace has customised: their key, the name the owner gave them and the personality they ' +
76
- 'were given. Use it when the user refers to an officer by name and you do not recognise it, or asks who is on ' +
77
- 'their AI team. An empty list is normal and means they are running the defaults, not that something is missing. ' +
78
- 'Useful mainly so you address an officer the way the workspace does rather than inventing your own label. Read only.',
79
- {},
80
- async () => {
81
- const r = await call('GET', '/strategy/officers');
82
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
83
- return out(r);
84
- },
85
- );
86
-
87
- server.tool(
88
- 'km_list_recommendations',
89
- 'Standing recommendations and insights the workspace has generated for itself, each with its category, impact and ' +
90
- 'effort where they were recorded. ' +
91
- 'Use it when the user asks what they should be doing, or before you offer advice of your own: the workspace may ' +
92
- 'already have said the same thing, and repeating it as though it were new is worse than agreeing with it. ' +
93
- '🚨 These are suggestions, not instructions, and some will be stale. Weigh them against what the owner has actually ' +
94
- 'said they want, and check the date before presenting one as current. Read only.',
95
- {
96
- limit: z.number().int().min(1).max(100).optional().describe('How many of each, newest or highest priority first. Default 25.'),
97
- },
98
- async ({ limit }) => {
99
- const r = await call('GET', `/strategy/advice${qs({ limit })}`);
100
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
101
- return out(r);
102
- },
103
- );
104
-
105
- server.tool(
106
- 'km_list_ai_activity',
107
- 'What has actually been done in this workspace and by which surface: the app, a worker, a cron, the API or this ' +
108
- 'terminal. Each entry names the action, the record it touched, and whether it was later UNDONE. ' +
109
- 'Use it when the user asks what changed, who did something, or whether the AI has been acting on its own. It is ' +
110
- 'also the right call before you claim nothing has happened: a quiet inbox does not mean a quiet workspace. ' +
111
- '🚨 `undone_count` is the honest number and it must not be skipped to look competent. A pattern of undos says ' +
112
- 'something real about how far a particular automation should be trusted, and the owner is better served knowing it. ' +
113
- 'Undo itself lives in KM Hub. Read only.',
114
- {
115
- source: z
116
- .string()
117
- .optional()
118
- .describe('Narrow to one surface, for example app, worker, cron, api or desk. Leave it out for everything.'),
119
- limit: z.number().int().min(1).max(200).optional().describe('How many entries, newest first. Default 40.'),
120
- },
121
- async ({ source, limit }) => {
122
- const r = await call('GET', `/strategy/activity${qs({ source, limit })}`);
123
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
124
- return out(r);
125
- },
126
- );
127
-
128
- server.tool(
129
- 'km_list_weekly_interviews',
130
- 'The weekly interview sessions: which week, who, what topics were covered, the digest of what the session concluded ' +
131
- 'and what it wants to ask about next. ' +
132
- 'Use it when the user mentions their weekly check-in or sit-down, or when you want to know what has recently been ' +
133
- 'learned about how this business is running. The digests are one of the richest sources of context in the ' +
134
- 'workspace and almost nothing else reads them. ' +
135
- 'Full transcripts are deliberately not returned: they are long, and the digest is the part that carries the ' +
136
- 'conclusion. Read only.',
137
- {
138
- limit: z.number().int().min(1).max(60).optional().describe('How many sessions, most recent week first. Default 15.'),
139
- },
140
- async ({ limit }) => {
141
- const r = await call('GET', `/strategy/interviews${qs({ limit })}`);
142
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
143
- return out(r);
144
- },
145
- );
146
- }
1
+ /**
2
+ * Tool family: strategy - the officers, what they proposed, and what was done.
3
+ *
4
+ * Workstream B6. Strategy was 2 of 8: the pillar that makes "a COO and beyond"
5
+ * true rather than aspirational had almost no terminal reach. A connector that
6
+ * reads the pipeline but cannot see the decisions waiting on the owner, or what was
7
+ * already done in their name, is a reporting tool rather than an operator.
8
+ *
9
+ * km_list_decisions -> GET /strategy/decisions
10
+ * km_list_ai_officers -> GET /strategy/officers
11
+ * km_list_recommendations -> GET /strategy/advice
12
+ * km_list_ai_activity -> GET /strategy/activity
13
+ * km_list_weekly_interviews-> GET /strategy/interviews
14
+ *
15
+ * 🚨 READ ONLY, and approval especially so. Approving a COO proposal executes real
16
+ * work against real records: it is the most consequential button in the product and
17
+ * it deserves its own decision when B8 settles write scope, not to inherit a general
18
+ * one. Never imply you can approve something.
19
+ *
20
+ * The family contract this file follows is documented in ./README.md.
21
+ */
22
+ import { z } from 'zod';
23
+
24
+ export const FAMILY = 'strategy';
25
+
26
+ export const TOOLS = [
27
+ 'km_list_decisions',
28
+ 'km_list_ai_officers',
29
+ 'km_list_recommendations',
30
+ 'km_list_ai_activity',
31
+ 'km_list_weekly_interviews',
32
+ ];
33
+
34
+ export const PROFILES = ['*'];
35
+
36
+ function routeMissing(r) {
37
+ return r.status === 404 || r.status === 405 || r.status === 501;
38
+ }
39
+
40
+ const NOT_SUPPORTED =
41
+ 'Your KM Hub does not expose the AI Team surface to the terminal yet. That is not a fault: the workspace is fine ' +
42
+ 'and every other tool works as normal. The officers, decisions and activity log are all there in the web app at ' +
43
+ 'https://hub.kivimedia.co, and this connector will start reading them once your KM Hub is on a build that publishes them.';
44
+
45
+ export function register(server, call, { out, text, qs }) {
46
+ server.tool(
47
+ 'km_list_decisions',
48
+ 'The proposals the COO has put up for a human decision, what each one would actually DO, and how long it has been ' +
49
+ 'waiting. ' +
50
+ 'Call this whenever the user asks what needs them, what is outstanding, or what the AI has suggested, and any time ' +
51
+ 'you are summarising the state of the business. A decision sitting unmade is different from a task not done: ' +
52
+ 'nothing moves until somebody says yes, and the workspace will not chase them for it. ' +
53
+ '🚨 Lead with `waiting_on_you` and `oldest_waiting_days`. A proposal that has been pending for weeks is usually one ' +
54
+ 'the owner never saw rather than one they decided against, and saying the number out loud is the whole value here. ' +
55
+ 'Read `risk` and `what_it_would_do` before describing anything: they are not all equivalent, and a high-risk ' +
56
+ 'proposal deserves to be presented as one. ' +
57
+ 'Nothing here can approve or reject. That happens in KM Hub, deliberately, because approval executes real work. ' +
58
+ 'Read only.',
59
+ {
60
+ status: z
61
+ .enum(['pending', 'approved', 'rejected', 'executed', 'failed', 'expired'])
62
+ .optional()
63
+ .describe('Narrow to one state. "pending" is what is waiting on a person, which is usually the real question.'),
64
+ limit: z.number().int().min(1).max(100).optional().describe('How many, newest first. Default 30.'),
65
+ },
66
+ async ({ status, limit }) => {
67
+ const r = await call('GET', `/strategy/decisions${qs({ status, limit })}`);
68
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
69
+ return out(r);
70
+ },
71
+ );
72
+
73
+ server.tool(
74
+ 'km_list_ai_officers',
75
+ 'Which AI officers this workspace has customised: their key, the name the owner gave them and the personality they ' +
76
+ 'were given. Use it when the user refers to an officer by name and you do not recognise it, or asks who is on ' +
77
+ 'their AI team. An empty list is normal and means they are running the defaults, not that something is missing. ' +
78
+ 'Useful mainly so you address an officer the way the workspace does rather than inventing your own label. Read only.',
79
+ {},
80
+ async () => {
81
+ const r = await call('GET', '/strategy/officers');
82
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
83
+ return out(r);
84
+ },
85
+ );
86
+
87
+ server.tool(
88
+ 'km_list_recommendations',
89
+ 'Standing recommendations and insights the workspace has generated for itself, each with its category, impact and ' +
90
+ 'effort where they were recorded. ' +
91
+ 'Use it when the user asks what they should be doing, or before you offer advice of your own: the workspace may ' +
92
+ 'already have said the same thing, and repeating it as though it were new is worse than agreeing with it. ' +
93
+ '🚨 These are suggestions, not instructions, and some will be stale. Weigh them against what the owner has actually ' +
94
+ 'said they want, and check the date before presenting one as current. Read only.',
95
+ {
96
+ limit: z.number().int().min(1).max(100).optional().describe('How many of each, newest or highest priority first. Default 25.'),
97
+ },
98
+ async ({ limit }) => {
99
+ const r = await call('GET', `/strategy/advice${qs({ limit })}`);
100
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
101
+ return out(r);
102
+ },
103
+ );
104
+
105
+ server.tool(
106
+ 'km_list_ai_activity',
107
+ 'What has actually been done in this workspace and by which surface: the app, a worker, a cron, the API or this ' +
108
+ 'terminal. Each entry names the action, the record it touched, and whether it was later UNDONE. ' +
109
+ 'Use it when the user asks what changed, who did something, or whether the AI has been acting on its own. It is ' +
110
+ 'also the right call before you claim nothing has happened: a quiet inbox does not mean a quiet workspace. ' +
111
+ '🚨 `undone_count` is the honest number and it must not be skipped to look competent. A pattern of undos says ' +
112
+ 'something real about how far a particular automation should be trusted, and the owner is better served knowing it. ' +
113
+ 'Undo itself lives in KM Hub. Read only.',
114
+ {
115
+ source: z
116
+ .string()
117
+ .optional()
118
+ .describe('Narrow to one surface, for example app, worker, cron, api or desk. Leave it out for everything.'),
119
+ limit: z.number().int().min(1).max(200).optional().describe('How many entries, newest first. Default 40.'),
120
+ },
121
+ async ({ source, limit }) => {
122
+ const r = await call('GET', `/strategy/activity${qs({ source, limit })}`);
123
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
124
+ return out(r);
125
+ },
126
+ );
127
+
128
+ server.tool(
129
+ 'km_list_weekly_interviews',
130
+ 'The weekly interview sessions: which week, who, what topics were covered, the digest of what the session concluded ' +
131
+ 'and what it wants to ask about next. ' +
132
+ 'Use it when the user mentions their weekly check-in or sit-down, or when you want to know what has recently been ' +
133
+ 'learned about how this business is running. The digests are one of the richest sources of context in the ' +
134
+ 'workspace and almost nothing else reads them. ' +
135
+ 'Full transcripts are deliberately not returned: they are long, and the digest is the part that carries the ' +
136
+ 'conclusion. Read only.',
137
+ {
138
+ limit: z.number().int().min(1).max(60).optional().describe('How many sessions, most recent week first. Default 15.'),
139
+ },
140
+ async ({ limit }) => {
141
+ const r = await call('GET', `/strategy/interviews${qs({ limit })}`);
142
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
143
+ return out(r);
144
+ },
145
+ );
146
+ }
package/tools/studio.mjs CHANGED
@@ -1,132 +1,132 @@
1
- /**
2
- * Tool family: studio - what the creation surfaces have actually produced.
3
- *
4
- * The last seven pages. I argued these could not be closed by reading, on the
5
- * grounds that a studio is a place you make things rather than a place with state.
6
- * That was wrong. A studio has OUTPUT, and the output is exactly what an owner
7
- * wants to know about: what has been made, what is waiting on them, what is
8
- * scheduled to go out.
9
- *
10
- * km_list_design_assets -> GET /studio/assets
11
- * km_list_galleries -> GET /studio/galleries
12
- * km_list_linkedin_content -> GET /studio/linkedin
13
- * km_list_balloon_work -> GET /studio/balloon
14
- * km_review_queue -> GET /studio/review
15
- *
16
- * 🚨 READ ONLY. Generating an image or a post spends money with a provider, and
17
- * approving one publishes in the client's name.
18
- *
19
- * The family contract this file follows is documented in ./README.md.
20
- */
21
- import { z } from 'zod';
22
-
23
- export const FAMILY = 'studio';
24
-
25
- export const TOOLS = [
26
- 'km_list_design_assets',
27
- 'km_list_galleries',
28
- 'km_list_linkedin_content',
29
- 'km_list_balloon_work',
30
- 'km_review_queue',
31
- ];
32
-
33
- export const PROFILES = ['content', 'outreach'];
34
-
35
- function routeMissing(r) {
36
- return r.status === 404 || r.status === 405 || r.status === 501;
37
- }
38
-
39
- const NOT_SUPPORTED =
40
- 'Your KM Hub does not expose the studios to the terminal yet. That is not a fault: the workspace is fine and every ' +
41
- 'other tool works as normal. They are there in the web app at https://hub.kivimedia.co.';
42
-
43
- export function register(server, call, { out, text, qs }) {
44
- server.tool(
45
- 'km_review_queue',
46
- 'EVERYTHING across the whole product that is waiting on a human: outreach drafts, SEO posts, LinkedIn posts, COO ' +
47
- 'proposals and proposed business facts, each with a count and how old the oldest one is. ' +
48
- 'Call this in any catch-up, any status question, and before telling somebody they are on top of things. ' +
49
- '🚨 No single page in KM Hub shows all of this, because every surface owns its own queue. Approval queues are ' +
50
- 'where work goes to die quietly: nothing sends until a person releases it, nothing chases them, and the owner ' +
51
- 'usually finds out when a client asks why they never heard back. ' +
52
- '🚨 LEAD WITH THE OLDEST, NOT THE LARGEST. Forty drafts written this morning are fine. One draft that has been ' +
53
- 'waiting six weeks is a client who was ignored. Read only.',
54
- {},
55
- async () => {
56
- const r = await call('GET', '/studio/review');
57
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
58
- return out(r);
59
- },
60
- );
61
-
62
- server.tool(
63
- 'km_list_design_assets',
64
- 'Images and design assets the studios have generated, with the template and provider behind each and a link to the ' +
65
- 'file. ' +
66
- 'Use it when the user asks what has been made, wants something to reuse, or before generating anything new: ' +
67
- 'regenerating something that already exists spends money twice for the same picture. ' +
68
- 'The prompt and inputs are not returned, because they are long and what a person wants is what exists and where. ' +
69
- 'Read only.',
70
- {
71
- limit: z.number().int().min(1).max(200).optional().describe('How many assets, newest first. Default 40.'),
72
- },
73
- async ({ limit }) => {
74
- const r = await call('GET', `/studio/assets${qs({ limit })}`);
75
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
76
- return out(r);
77
- },
78
- );
79
-
80
- server.tool(
81
- 'km_list_galleries',
82
- 'The galleries this business publishes, whether each is active, and how many items are in them. ' +
83
- '🚨 An ACTIVE gallery with no active items is a public page showing nothing, which is worse than not having one ' +
84
- 'at all, and nobody notices because the page still loads. That combination is worth raising unprompted. ' +
85
- 'Use it before advising anything about what buyers see before they enquire. Building a gallery happens in KM Hub. ' +
86
- '🚨 These are the PUBLIC gallery pages on the website, and they have nothing to do with the photos that ride inside ' +
87
- 'an outreach email. If the question is about pictures in an email, this is the wrong tool and an empty answer here ' +
88
- 'means nothing: use km_list_work_photos and km_attach_draft_photos. ' +
89
- 'Read only.',
90
- {},
91
- async () => {
92
- const r = await call('GET', '/studio/galleries');
93
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
94
- return out(r);
95
- },
96
- );
97
-
98
- server.tool(
99
- 'km_list_linkedin_content',
100
- 'LinkedIn posts the workspace has produced: the hook archetype each uses, its status, when it is scheduled, and a ' +
101
- 'preview of the body. ' +
102
- 'Use it when the user asks about their LinkedIn presence, what is going out, or what is waiting on them. ' +
103
- '🚨 Read `by_hook`. A workspace using one hook archetype for everything becomes predictable, and predictable is ' +
104
- 'invisible on LinkedIn. That is a more useful observation than the post count. `awaiting_approval` is the other ' +
105
- 'number worth naming: those are written and going nowhere. Publishing happens in KM Hub. Read only.',
106
- {
107
- limit: z.number().int().min(1).max(200).optional().describe('How many posts, newest first. Default 40.'),
108
- },
109
- async ({ limit }) => {
110
- const r = await call('GET', `/studio/linkedin${qs({ limit })}`);
111
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
112
- return out(r);
113
- },
114
- );
115
-
116
- server.tool(
117
- 'km_list_balloon_work',
118
- 'Balloon designs and the render runs behind them: the event, the guest count, the status, and whether each design ' +
119
- 'ever reached a proposal. ' +
120
- '🚨 `designs_on_a_proposal` against the design count is the number that matters. A design that never reached a ' +
121
- 'proposal is work that was done and never sold, and that gap is invisible on any single screen. ' +
122
- 'Rendering costs money with an image provider and happens in KM Hub. Read only.',
123
- {
124
- limit: z.number().int().min(1).max(100).optional().describe('How many designs and runs, newest first. Default 30.'),
125
- },
126
- async ({ limit }) => {
127
- const r = await call('GET', `/studio/balloon${qs({ limit })}`);
128
- if (routeMissing(r)) return text(NOT_SUPPORTED, true);
129
- return out(r);
130
- },
131
- );
132
- }
1
+ /**
2
+ * Tool family: studio - what the creation surfaces have actually produced.
3
+ *
4
+ * The last seven pages. I argued these could not be closed by reading, on the
5
+ * grounds that a studio is a place you make things rather than a place with state.
6
+ * That was wrong. A studio has OUTPUT, and the output is exactly what an owner
7
+ * wants to know about: what has been made, what is waiting on them, what is
8
+ * scheduled to go out.
9
+ *
10
+ * km_list_design_assets -> GET /studio/assets
11
+ * km_list_galleries -> GET /studio/galleries
12
+ * km_list_linkedin_content -> GET /studio/linkedin
13
+ * km_list_balloon_work -> GET /studio/balloon
14
+ * km_review_queue -> GET /studio/review
15
+ *
16
+ * 🚨 READ ONLY. Generating an image or a post spends money with a provider, and
17
+ * approving one publishes in the client's name.
18
+ *
19
+ * The family contract this file follows is documented in ./README.md.
20
+ */
21
+ import { z } from 'zod';
22
+
23
+ export const FAMILY = 'studio';
24
+
25
+ export const TOOLS = [
26
+ 'km_list_design_assets',
27
+ 'km_list_galleries',
28
+ 'km_list_linkedin_content',
29
+ 'km_list_balloon_work',
30
+ 'km_review_queue',
31
+ ];
32
+
33
+ export const PROFILES = ['content', 'outreach'];
34
+
35
+ function routeMissing(r) {
36
+ return r.status === 404 || r.status === 405 || r.status === 501;
37
+ }
38
+
39
+ const NOT_SUPPORTED =
40
+ 'Your KM Hub does not expose the studios to the terminal yet. That is not a fault: the workspace is fine and every ' +
41
+ 'other tool works as normal. They are there in the web app at https://hub.kivimedia.co.';
42
+
43
+ export function register(server, call, { out, text, qs }) {
44
+ server.tool(
45
+ 'km_review_queue',
46
+ 'EVERYTHING across the whole product that is waiting on a human: outreach drafts, SEO posts, LinkedIn posts, COO ' +
47
+ 'proposals and proposed business facts, each with a count and how old the oldest one is. ' +
48
+ 'Call this in any catch-up, any status question, and before telling somebody they are on top of things. ' +
49
+ '🚨 No single page in KM Hub shows all of this, because every surface owns its own queue. Approval queues are ' +
50
+ 'where work goes to die quietly: nothing sends until a person releases it, nothing chases them, and the owner ' +
51
+ 'usually finds out when a client asks why they never heard back. ' +
52
+ '🚨 LEAD WITH THE OLDEST, NOT THE LARGEST. Forty drafts written this morning are fine. One draft that has been ' +
53
+ 'waiting six weeks is a client who was ignored. Read only.',
54
+ {},
55
+ async () => {
56
+ const r = await call('GET', '/studio/review');
57
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
58
+ return out(r);
59
+ },
60
+ );
61
+
62
+ server.tool(
63
+ 'km_list_design_assets',
64
+ 'Images and design assets the studios have generated, with the template and provider behind each and a link to the ' +
65
+ 'file. ' +
66
+ 'Use it when the user asks what has been made, wants something to reuse, or before generating anything new: ' +
67
+ 'regenerating something that already exists spends money twice for the same picture. ' +
68
+ 'The prompt and inputs are not returned, because they are long and what a person wants is what exists and where. ' +
69
+ 'Read only.',
70
+ {
71
+ limit: z.number().int().min(1).max(200).optional().describe('How many assets, newest first. Default 40.'),
72
+ },
73
+ async ({ limit }) => {
74
+ const r = await call('GET', `/studio/assets${qs({ limit })}`);
75
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
76
+ return out(r);
77
+ },
78
+ );
79
+
80
+ server.tool(
81
+ 'km_list_galleries',
82
+ 'The galleries this business publishes, whether each is active, and how many items are in them. ' +
83
+ '🚨 An ACTIVE gallery with no active items is a public page showing nothing, which is worse than not having one ' +
84
+ 'at all, and nobody notices because the page still loads. That combination is worth raising unprompted. ' +
85
+ 'Use it before advising anything about what buyers see before they enquire. Building a gallery happens in KM Hub. ' +
86
+ '🚨 These are the PUBLIC gallery pages on the website, and they have nothing to do with the photos that ride inside ' +
87
+ 'an outreach email. If the question is about pictures in an email, this is the wrong tool and an empty answer here ' +
88
+ 'means nothing: use km_list_work_photos and km_attach_draft_photos. ' +
89
+ 'Read only.',
90
+ {},
91
+ async () => {
92
+ const r = await call('GET', '/studio/galleries');
93
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
94
+ return out(r);
95
+ },
96
+ );
97
+
98
+ server.tool(
99
+ 'km_list_linkedin_content',
100
+ 'LinkedIn posts the workspace has produced: the hook archetype each uses, its status, when it is scheduled, and a ' +
101
+ 'preview of the body. ' +
102
+ 'Use it when the user asks about their LinkedIn presence, what is going out, or what is waiting on them. ' +
103
+ '🚨 Read `by_hook`. A workspace using one hook archetype for everything becomes predictable, and predictable is ' +
104
+ 'invisible on LinkedIn. That is a more useful observation than the post count. `awaiting_approval` is the other ' +
105
+ 'number worth naming: those are written and going nowhere. Publishing happens in KM Hub. Read only.',
106
+ {
107
+ limit: z.number().int().min(1).max(200).optional().describe('How many posts, newest first. Default 40.'),
108
+ },
109
+ async ({ limit }) => {
110
+ const r = await call('GET', `/studio/linkedin${qs({ limit })}`);
111
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
112
+ return out(r);
113
+ },
114
+ );
115
+
116
+ server.tool(
117
+ 'km_list_balloon_work',
118
+ 'Balloon designs and the render runs behind them: the event, the guest count, the status, and whether each design ' +
119
+ 'ever reached a proposal. ' +
120
+ '🚨 `designs_on_a_proposal` against the design count is the number that matters. A design that never reached a ' +
121
+ 'proposal is work that was done and never sold, and that gap is invisible on any single screen. ' +
122
+ 'Rendering costs money with an image provider and happens in KM Hub. Read only.',
123
+ {
124
+ limit: z.number().int().min(1).max(100).optional().describe('How many designs and runs, newest first. Default 30.'),
125
+ },
126
+ async ({ limit }) => {
127
+ const r = await call('GET', `/studio/balloon${qs({ limit })}`);
128
+ if (routeMissing(r)) return text(NOT_SUPPORTED, true);
129
+ return out(r);
130
+ },
131
+ );
132
+ }