@kolbo/mcp 1.87.10 → 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.
- package/package.json +1 -1
- package/src/apps/index.js +6 -5
- package/src/apps/theme.js +444 -446
- package/src/apps/widgets/plans.js +164 -113
- package/src/tools/blender.js +62 -57
|
@@ -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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* }
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<span class="k-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
var
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 };
|
package/src/tools/blender.js
CHANGED
|
@@ -56,20 +56,25 @@ const idempotencyKey = z.string()
|
|
|
56
56
|
.regex(/^[A-Za-z0-9._:-]+$/)
|
|
57
57
|
.optional()
|
|
58
58
|
.describe('Optional replay-safe key. Reuse it only when retrying the same command.');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
// Every shared piece below is a FACTORY, not a shared instance. zod-to-json-schema
|
|
60
|
+
// de-duplicates repeated zod instances into JSON-pointer `$ref`s, and this tool
|
|
61
|
+
// reuses name/vector/color dozens of times inside one discriminated union, so the
|
|
62
|
+
// emitted inputSchema carried 54 `$ref`s, draft-07 tuple `items: [...]` arrays and
|
|
63
|
+
// `const` discriminators. ChatGPT's app "Scan Tools" rejects all three
|
|
64
|
+
// ("Invalid MCP tool schema for tool 'blender_apply_operations'", 2026-09-06),
|
|
65
|
+
// which blocks re-scanning the whole Kolbo app. Fresh instances + fixed-length
|
|
66
|
+
// arrays + single-value enums keep the schema inside the plain subset every host
|
|
67
|
+
// accepts. Validation semantics are unchanged.
|
|
68
|
+
const name = () => noControls(128);
|
|
69
|
+
const num = () => z.number().finite().min(-MAX_ABSOLUTE_NUMBER).max(MAX_ABSOLUTE_NUMBER);
|
|
70
|
+
const vector = () => z.array(num()).length(3);
|
|
71
|
+
const color = () => z.array(z.number().finite().min(0).max(1)).min(3).max(4);
|
|
72
|
+
const propertyScalar = () => z.union([
|
|
68
73
|
noControls(256),
|
|
69
|
-
|
|
74
|
+
num(),
|
|
70
75
|
z.boolean(),
|
|
71
76
|
]);
|
|
72
|
-
const properties = z.record(z.union([propertyScalar, z.array(propertyScalar).max(16)]))
|
|
77
|
+
const properties = () => z.record(z.union([propertyScalar(), z.array(propertyScalar()).max(16)]))
|
|
73
78
|
.refine((value) => Object.keys(value).length <= 30, 'A modifier may contain at most 30 properties.')
|
|
74
79
|
.refine(
|
|
75
80
|
(value) => Object.keys(value).every((key) => SAFE_PROPERTY_KEY.test(key) && !BLOCKED_PROPERTY_KEYS.has(key)),
|
|
@@ -79,81 +84,81 @@ const properties = z.record(z.union([propertyScalar, z.array(propertyScalar).max
|
|
|
79
84
|
(value) => Buffer.byteLength(JSON.stringify(value), 'utf8') <= 16 * 1024,
|
|
80
85
|
'Modifier properties must be at most 16 KiB as JSON.',
|
|
81
86
|
);
|
|
82
|
-
const transform = {
|
|
83
|
-
location: vector.optional(),
|
|
84
|
-
rotation_euler: vector.optional(),
|
|
85
|
-
scale: vector.optional(),
|
|
86
|
-
};
|
|
87
|
+
const transform = () => ({
|
|
88
|
+
location: vector().optional(),
|
|
89
|
+
rotation_euler: vector().optional(),
|
|
90
|
+
scale: vector().optional(),
|
|
91
|
+
});
|
|
92
|
+
const op = (value) => z.enum([value]);
|
|
87
93
|
const operation = z.discriminatedUnion('op', [
|
|
88
94
|
z.object({
|
|
89
|
-
op:
|
|
90
|
-
name: name.optional(),
|
|
95
|
+
op: op('object.create'),
|
|
96
|
+
name: name().optional(),
|
|
91
97
|
type: z.enum(['CUBE', 'SPHERE', 'CYLINDER', 'CONE', 'PLANE', 'EMPTY', 'CAMERA', 'LIGHT']),
|
|
92
98
|
light_type: z.enum(['POINT', 'SUN', 'SPOT', 'AREA']).optional(),
|
|
93
99
|
energy: z.number().finite().min(0).max(MAX_ABSOLUTE_NUMBER).optional(),
|
|
94
|
-
color: color.optional(),
|
|
100
|
+
color: color().optional(),
|
|
95
101
|
shadow_soft_size: z.number().finite().min(0).max(MAX_ABSOLUTE_NUMBER).optional(),
|
|
96
102
|
lens: z.number().finite().min(1).max(10_000).optional(),
|
|
97
|
-
collection: name.optional(),
|
|
98
|
-
...transform,
|
|
103
|
+
collection: name().optional(),
|
|
104
|
+
...transform(),
|
|
99
105
|
}).strict(),
|
|
100
|
-
z.object({ op:
|
|
101
|
-
z.object({ op:
|
|
102
|
-
z.object({ op:
|
|
106
|
+
z.object({ op: op('object.transform'), object: name(), ...transform() }).strict(),
|
|
107
|
+
z.object({ op: op('object.rename'), object: name(), new_name: name() }).strict(),
|
|
108
|
+
z.object({ op: op('object.delete'), object: name() }).strict(),
|
|
103
109
|
z.object({
|
|
104
|
-
op:
|
|
105
|
-
object: name,
|
|
106
|
-
new_name: name.optional(),
|
|
107
|
-
collection: name.optional(),
|
|
108
|
-
...transform,
|
|
110
|
+
op: op('object.duplicate'),
|
|
111
|
+
object: name(),
|
|
112
|
+
new_name: name().optional(),
|
|
113
|
+
collection: name().optional(),
|
|
114
|
+
...transform(),
|
|
109
115
|
}).strict(),
|
|
110
|
-
z.object({ op:
|
|
111
|
-
z.object({ op:
|
|
112
|
-
z.object({ op:
|
|
113
|
-
z.object({ op:
|
|
116
|
+
z.object({ op: op('object.set_parent'), object: name(), parent: name().nullable().optional() }).strict(),
|
|
117
|
+
z.object({ op: op('collection.create'), name: name(), parent: name().nullable().optional() }).strict(),
|
|
118
|
+
z.object({ op: op('collection.delete'), collection: name() }).strict(),
|
|
119
|
+
z.object({ op: op('collection.link_object'), collection: name(), object: name() }).strict(),
|
|
114
120
|
z.object({
|
|
115
|
-
op:
|
|
116
|
-
|
|
117
|
-
base_color: color.optional(),
|
|
121
|
+
op: op('material.create'), name: name(),
|
|
122
|
+
base_color: color().optional(),
|
|
118
123
|
roughness: z.number().min(0).max(1).optional(),
|
|
119
124
|
metallic: z.number().min(0).max(1).optional(),
|
|
120
125
|
alpha: z.number().min(0).max(1).optional(),
|
|
121
|
-
emission_color: color.optional(),
|
|
126
|
+
emission_color: color().optional(),
|
|
122
127
|
emission_strength: z.number().finite().min(0).max(MAX_ABSOLUTE_NUMBER).optional(),
|
|
123
128
|
}).strict(),
|
|
124
|
-
z.object({ op:
|
|
129
|
+
z.object({ op: op('material.assign'), object: name(), material: name(), replace: z.boolean().optional() }).strict(),
|
|
125
130
|
z.object({
|
|
126
|
-
op:
|
|
127
|
-
material: name,
|
|
128
|
-
base_color: color.optional(),
|
|
131
|
+
op: op('material.set_principled'),
|
|
132
|
+
material: name(),
|
|
133
|
+
base_color: color().optional(),
|
|
129
134
|
roughness: z.number().min(0).max(1).optional(),
|
|
130
135
|
metallic: z.number().min(0).max(1).optional(),
|
|
131
136
|
alpha: z.number().min(0).max(1).optional(),
|
|
132
|
-
emission_color: color.optional(),
|
|
137
|
+
emission_color: color().optional(),
|
|
133
138
|
emission_strength: z.number().finite().min(0).max(MAX_ABSOLUTE_NUMBER).optional(),
|
|
134
139
|
}).strict(),
|
|
135
|
-
z.object({ op:
|
|
140
|
+
z.object({ op: op('world.set_color'), color: color() }).strict(),
|
|
136
141
|
z.object({
|
|
137
|
-
op:
|
|
138
|
-
object: name,
|
|
142
|
+
op: op('modifier.add'),
|
|
143
|
+
object: name(),
|
|
139
144
|
type: z.string().regex(SAFE_MODIFIER_TYPE),
|
|
140
|
-
name: name.optional(),
|
|
141
|
-
properties: properties.optional(),
|
|
145
|
+
name: name().optional(),
|
|
146
|
+
properties: properties().optional(),
|
|
142
147
|
}).strict(),
|
|
143
|
-
z.object({ op:
|
|
144
|
-
z.object({ op:
|
|
145
|
-
z.object({ op:
|
|
148
|
+
z.object({ op: op('modifier.configure'), object: name(), modifier: name(), properties: properties() }).strict(),
|
|
149
|
+
z.object({ op: op('modifier.remove'), object: name(), modifier: name() }).strict(),
|
|
150
|
+
z.object({ op: op('camera.set_active'), object: name() }).strict(),
|
|
146
151
|
z.object({
|
|
147
|
-
op:
|
|
148
|
-
object: name,
|
|
149
|
-
data_path: name,
|
|
152
|
+
op: op('animation.keyframe_insert'),
|
|
153
|
+
object: name(),
|
|
154
|
+
data_path: name(),
|
|
150
155
|
frame: z.number().int().min(-1_048_574).max(1_048_574),
|
|
151
156
|
index: z.number().int().min(-1).max(1024).optional(),
|
|
152
157
|
}).strict(),
|
|
153
158
|
z.object({
|
|
154
|
-
op:
|
|
155
|
-
object: name,
|
|
156
|
-
data_path: name,
|
|
159
|
+
op: op('animation.delete_keyframe'),
|
|
160
|
+
object: name(),
|
|
161
|
+
data_path: name(),
|
|
157
162
|
frame: z.number().int().min(-1_048_574).max(1_048_574),
|
|
158
163
|
index: z.number().int().min(-1).max(1024).optional(),
|
|
159
164
|
}).strict(),
|
|
@@ -316,7 +321,7 @@ function registerBlenderTools(server, client) {
|
|
|
316
321
|
.optional(),
|
|
317
322
|
kind: z.enum(['model', '3d', 'glb', 'image', 'video']).optional(),
|
|
318
323
|
import_mode: z.enum(['plane', 'active_material', 'world', 'sequencer', 'collection']).optional(),
|
|
319
|
-
name: name.optional(),
|
|
324
|
+
name: name().optional(),
|
|
320
325
|
idempotency_key: idempotencyKey,
|
|
321
326
|
},
|
|
322
327
|
async (args) => {
|