@kolbo/mcp 1.87.11 → 1.87.12

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.
@@ -1,113 +1,164 @@
1
- 'use strict';
2
-
3
- const { widgetPage } = require('../html');
4
-
5
- /**
6
- * Plans / upgrade widget — shown by `show_plans`, and by any generation the
7
- * server refused for credits.
8
- *
9
- * The live pricing UI lives on app.kolbo.ai. This card iframes
10
- * `/pricing/embed` (same PlanList / PricingCard as the site) so a price or
11
- * perk change does not require an MCP republish. Buy/Subscribe on that page
12
- * open https://app.kolbo.ai/pricing in a new tab Stripe and the user's
13
- * session must not run inside a third-party host iframe.
14
- *
15
- * structuredContent contract:
16
- * {
17
- * widget: 'plans',
18
- * reason: 'insufficient_credits' | 'requested',
19
- * balance, required, shortfall, // when reason === 'insufficient_credits'
20
- * current_plan: { key, name },
21
- * plans: [{ key, name, interval, credits, price, original_price,
22
- * discount_percent, promo_text, currency, top_up_discount }],
23
- * credit_packs: [{ …, subscriber_price, is_subscriber }],
24
- * pricing_url
25
- * }
26
- */
27
-
28
- const BODY = `
29
- <div class="k-card" id="card">
30
- <div class="k-head">
31
- <span class="k-logo" id="logo"></span>
32
- <span class="k-title" id="tool-title">Plans</span>
33
- <span class="k-spacer"></span>
34
- <span class="k-chip" id="balance-chip" style="display:none"></span>
35
- </div>
36
- <div class="k-body">
37
- <div class="k-error" id="notice" style="display:none"></div>
38
- <div id="stage"></div>
39
- <div class="k-actions" id="actions"></div>
40
- </div>
41
- <div class="k-footer">
42
- <span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span>
43
- </div>
44
- </div>
45
- `;
46
-
47
- const SCRIPT = `
48
- var state = null;
49
- var PRICING_EMBED = 'https://app.kolbo.ai/pricing/embed';
50
-
51
- el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
52
- el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
53
-
54
- function pricingUrl() {
55
- return (state && state.pricing_url) || 'https://app.kolbo.ai/pricing';
56
- }
57
-
58
- function boot(sc) {
59
- if (!sc) return;
60
- state = sc;
61
- el('tool-title').textContent = sc.reason === 'insufficient_credits' ? 'Out of credits' : 'Plans';
62
-
63
- if (sc.balance != null) {
64
- var chip = el('balance-chip');
65
- chip.style.display = '';
66
- chip.innerHTML = ICONS.sparkle + ' ' + esc(String(sc.balance)) + ' credits left';
67
- }
68
-
69
- if (sc.reason === 'insufficient_credits') {
70
- var need = sc.shortfall != null ? sc.shortfall
71
- : (sc.required != null && sc.balance != null ? Math.max(0, sc.required - sc.balance) : null);
72
- el('notice').style.display = '';
73
- el('notice').innerHTML = ICONS.warn + ' ' +
74
- esc(need ? ('That generation needs ' + need + ' more credit' + (need === 1 ? '' : 's') + '.')
75
- : 'That generation needs more credits than you have left.');
76
- }
77
-
78
- el('stage').innerHTML = '<iframe class="k-pricing-frame" src="' + PRICING_EMBED +
79
- '" title="Kolbo plans" referrerpolicy="no-referrer-when-downgrade"></iframe>';
80
- var frame = el('stage').querySelector('iframe');
81
- if (frame) frame.onload = function () { window.kolbo.notifySize(); };
82
- renderActions();
83
- window.kolbo.notifySize();
84
- }
85
-
86
- function renderActions() {
87
- el('actions').innerHTML =
88
- '<button class="k-btn ghost" id="btn-pricing">See all plans ' + ICONS.open + '</button>';
89
- el('btn-pricing').onclick = function () { window.kolbo.openLink(pricingUrl()); };
90
- }
91
-
92
- window.kolbo.onToolResult(function (result) {
93
- var sc = result.structuredContent || structured(result);
94
- if (sc) boot(sc);
95
- });
96
- window.kolbo.onToolInput(function () {
97
- if (state) return;
98
- el('stage').innerHTML = '<div class="k-skel square" style="min-height:240px"></div>';
99
- window.kolbo.notifySize();
100
- });
101
- window.kolbo.ready(function (ctx) {
102
- var info = ctx && ctx.toolInfo;
103
- if (state || !info) return;
104
- var raw = info.result && (info.result.structuredContent || structured(info.result));
105
- if (raw) boot(raw);
106
- });
107
- `;
108
-
109
- function plansWidgetHtml() {
110
- return widgetPage({ title: 'Kolbo Plans', body: BODY, script: SCRIPT });
111
- }
112
-
113
- module.exports = { plansWidgetHtml };
1
+ 'use strict';
2
+
3
+ const { widgetPage } = require('../html');
4
+
5
+ /**
6
+ * Plans / upgrade widget — shown by `show_plans`, and by any generation the
7
+ * server refused for credits.
8
+ *
9
+ * Rendered NATIVELY from structuredContent. It used to iframe
10
+ * app.kolbo.ai/pricing/embed, which needs `frameDomains` in the widget CSP
11
+ * OpenAI rejected the ChatGPT app for exactly that on 2026-09-06 ("frameDomains
12
+ * is reserved for limited cases where embedding a third-party experience is
13
+ * essential"). Prices here are the live, promo-adjusted numbers the server
14
+ * fetched from /v1/account/plans at call time; Buy/Subscribe open
15
+ * https://app.kolbo.ai/pricing in a new tab — Stripe and the user's session
16
+ * never run inside a host iframe.
17
+ *
18
+ * structuredContent contract:
19
+ * {
20
+ * widget: 'plans',
21
+ * reason: 'insufficient_credits' | 'requested',
22
+ * balance, required, shortfall, // when reason === 'insufficient_credits'
23
+ * current_plan: { key, name },
24
+ * plans: [{ key, name, interval, credits, price, original_price,
25
+ * discount_percent, promo_text, currency, top_up_discount }],
26
+ * credit_packs: [{ …, subscriber_price, is_subscriber }],
27
+ * pricing_url
28
+ * }
29
+ */
30
+
31
+ const BODY = `
32
+ <div class="k-card" id="card">
33
+ <div class="k-head">
34
+ <span class="k-logo" id="logo"></span>
35
+ <span class="k-title" id="tool-title">Plans</span>
36
+ <span class="k-spacer"></span>
37
+ <span class="k-chip" id="balance-chip" style="display:none"></span>
38
+ </div>
39
+ <div class="k-body">
40
+ <div class="k-error" id="notice" style="display:none"></div>
41
+ <div id="stage"></div>
42
+ <div class="k-actions" id="actions"></div>
43
+ </div>
44
+ <div class="k-footer">
45
+ <span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span>
46
+ </div>
47
+ </div>
48
+ `;
49
+
50
+ const SCRIPT = `
51
+ var state = null;
52
+ var interval = 'month';
53
+
54
+ el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
55
+ el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
56
+
57
+ function pricingUrl() {
58
+ return (state && state.pricing_url) || 'https://app.kolbo.ai/pricing';
59
+ }
60
+ function money(v, currency) {
61
+ if (v == null || isNaN(v)) return '';
62
+ var sym = { usd: '$', eur: '\u20ac', gbp: '\u00a3', ils: '\u20aa' }[String(currency || 'usd').toLowerCase()] || '';
63
+ var n = Number(v);
64
+ return sym + (Math.round(n) === n ? String(n) : n.toFixed(2));
65
+ }
66
+ function fmtCredits(n) { return n == null ? '' : Number(n).toLocaleString('en-US'); }
67
+ function isAnnual(p) { return /^(year|annual|yearly)/i.test(String(p.interval || '')); }
68
+
69
+ function planCard(p, opts) {
70
+ var current = state.current_plan && ((state.current_plan.key && state.current_plan.key === p.key) || (state.current_plan.name && state.current_plan.name === p.name));
71
+ var pricing = '<div class="k-plan-pricing"><span class="k-plan-price">' + esc(money(p.price, p.currency)) + '</span>' +
72
+ (p.original_price != null && p.original_price !== p.price ? '<span class="k-plan-was">' + esc(money(p.original_price, p.currency)) + '</span>' : '') +
73
+ (opts.pack ? '' : '<span class="k-plan-note">/ ' + (isAnnual(p) ? 'year' : 'month') + '</span>') + '</div>';
74
+ var badge = current ? '<span class="k-plan-badge current">Current plan</span>'
75
+ : (p.discount_percent ? '<span class="k-plan-badge">' + esc(String(p.discount_percent)) + '% off</span>' : '');
76
+ var note = p.promo_text ? '<div class="k-plan-note">' + esc(p.promo_text) + '</div>'
77
+ : (opts.pack && p.is_subscriber && p.subscriber_price != null ? '<div class="k-plan-note">Subscriber price applied</div>' : '')
78
+ + (!opts.pack && p.top_up_discount ? '<div class="k-plan-note">Cheaper credit packs included</div>' : '');
79
+ var cta = current ? '' : '<button class="k-btn ' + (opts.pack ? 'ghost' : 'primary') + '" data-open="1">' + (opts.pack ? 'Buy pack' : 'Upgrade') + ' ' + ICONS.open + '</button>';
80
+ return '<div class="k-plan' + (current ? ' current' : '') + '">' +
81
+ '<div class="k-plan-top"><span class="k-plan-name">' + esc(p.name || p.key || 'Plan') + '</span>' + badge + '</div>' +
82
+ (p.credits != null ? '<div class="k-plan-credits">' + ICONS.sparkle + ' ' + esc(fmtCredits(p.credits)) + ' credits' + (opts.pack ? '' : ' / ' + (isAnnual(p) ? 'year' : 'month')) + '</div>' : '') +
83
+ pricing + note + cta + '</div>';
84
+ }
85
+
86
+ function renderStage() {
87
+ var plans = (state.plans || []).filter(function (p) { return p && (p.price != null || p.credits != null); });
88
+ var packs = (state.credit_packs || []).filter(function (p) { return p && p.price != null; });
89
+ var monthly = plans.filter(function (p) { return !isAnnual(p); });
90
+ var annual = plans.filter(isAnnual);
91
+ var hasToggle = monthly.length && annual.length;
92
+ var shown = hasToggle ? (interval === 'year' ? annual : monthly) : plans;
93
+ var html = '';
94
+ if (hasToggle) {
95
+ html += '<div class="k-plan-toggle">' +
96
+ '<button class="k-toggle-btn' + (interval === 'month' ? ' active' : '') + '" data-interval="month">Monthly</button>' +
97
+ '<button class="k-toggle-btn' + (interval === 'year' ? ' active' : '') + '" data-interval="year">Annual</button></div>';
98
+ }
99
+ if (shown.length) html += '<div class="k-plan-grid">' + shown.map(function (p) { return planCard(p, {}); }).join('') + '</div>';
100
+ if (packs.length) {
101
+ html += '<div class="k-pack-head">Credit packs</div><div class="k-plan-grid">' +
102
+ packs.map(function (p) { return planCard(p, { pack: true }); }).join('') + '</div>';
103
+ }
104
+ if (!html) html = '<div class="k-plan-note">Live plans and prices are on the Kolbo pricing page.</div>';
105
+ el('stage').innerHTML = html;
106
+ var btns = el('stage').querySelectorAll('[data-interval]');
107
+ for (var i = 0; i < btns.length; i++) btns[i].onclick = function () { interval = this.getAttribute('data-interval'); renderStage(); window.kolbo.notifySize(); };
108
+ var opens = el('stage').querySelectorAll('[data-open]');
109
+ for (var j = 0; j < opens.length; j++) opens[j].onclick = function () { window.kolbo.openLink(pricingUrl()); };
110
+ }
111
+
112
+ function boot(sc) {
113
+ if (!sc) return;
114
+ state = sc;
115
+ el('tool-title').textContent = sc.reason === 'insufficient_credits' ? 'Out of credits' : 'Plans';
116
+
117
+ if (sc.balance != null) {
118
+ var chip = el('balance-chip');
119
+ chip.style.display = '';
120
+ chip.innerHTML = ICONS.sparkle + ' ' + esc(String(sc.balance)) + ' credits left';
121
+ }
122
+
123
+ if (sc.reason === 'insufficient_credits') {
124
+ var need = sc.shortfall != null ? sc.shortfall
125
+ : (sc.required != null && sc.balance != null ? Math.max(0, sc.required - sc.balance) : null);
126
+ el('notice').style.display = '';
127
+ el('notice').innerHTML = ICONS.warn + ' ' +
128
+ esc(need ? ('That generation needs ' + need + ' more credit' + (need === 1 ? '' : 's') + '.')
129
+ : 'That generation needs more credits than you have left.');
130
+ }
131
+
132
+ renderStage();
133
+ renderActions();
134
+ window.kolbo.notifySize();
135
+ }
136
+
137
+ function renderActions() {
138
+ el('actions').innerHTML =
139
+ '<button class="k-btn ghost" id="btn-pricing">See all plans ' + ICONS.open + '</button>';
140
+ el('btn-pricing').onclick = function () { window.kolbo.openLink(pricingUrl()); };
141
+ }
142
+
143
+ window.kolbo.onToolResult(function (result) {
144
+ var sc = result.structuredContent || structured(result);
145
+ if (sc) boot(sc);
146
+ });
147
+ window.kolbo.onToolInput(function () {
148
+ if (state) return;
149
+ el('stage').innerHTML = '<div class="k-skel square" style="min-height:240px"></div>';
150
+ window.kolbo.notifySize();
151
+ });
152
+ window.kolbo.ready(function (ctx) {
153
+ var info = ctx && ctx.toolInfo;
154
+ if (state || !info) return;
155
+ var raw = info.result && (info.result.structuredContent || structured(info.result));
156
+ if (raw) boot(raw);
157
+ });
158
+ `;
159
+
160
+ function plansWidgetHtml() {
161
+ return widgetPage({ title: 'Kolbo Plans', body: BODY, script: SCRIPT });
162
+ }
163
+
164
+ module.exports = { plansWidgetHtml };