@hraness/slopcamera 3.8.1 → 3.9.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/NOTICE.md +14 -0
- package/README.md +14 -6
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/capability-manifest.ts +15 -2
- package/apps/desktop/cli/commands.ts +1 -1
- package/apps/desktop/cli/help.ts +23 -4
- package/apps/desktop/cli/portable-surface.ts +1 -0
- package/apps/desktop/cli/verbs.ts +4 -0
- package/apps/desktop/dist/cli/main.js +306 -284
- package/dist/cli.js +24 -4
- package/dist/{index-842t02pp.js → index-m6g0ca9b.js} +1 -1
- package/dist/{index-y8spxs77.js → index-mgychzh3.js} +6 -3
- package/dist/{index-6jrxaser.js → index-rxx27q8b.js} +4 -4
- package/dist/index.js +1 -1
- package/dist/operations.js +1 -1
- package/dist/workflow.js +1 -1
- package/package.json +4 -2
- package/skills/slopcamera/SKILL.md +2 -0
- package/skills/slopcamera/references/customization.md +8 -1
- package/skills/slopcamera/references/icon-place.md +76 -0
- package/skills/slopcamera/references/install.md +2 -2
- package/skills/slopcamera/references/music-video.md +10 -2
- package/skills/slopcamera/references/soundtracks.md +58 -0
- package/src/bounded-file.ts +135 -0
- package/src/cli.ts +191 -0
- package/src/icon-scene.ts +695 -0
- package/src/index.ts +2 -0
- package/src/mcp/tools.ts +311 -0
- package/src/mcp/types.ts +4 -0
- package/src/operations.ts +306 -0
- package/src/soundtrack.ts +489 -0
- package/src/version.ts +1 -1
package/src/mcp/tools.ts
CHANGED
|
@@ -22,6 +22,26 @@ import {
|
|
|
22
22
|
type RenderSlopcameraOperationInput,
|
|
23
23
|
type VectorizeSlopcameraOperationInput,
|
|
24
24
|
} from "../operations.js"
|
|
25
|
+
import {
|
|
26
|
+
composeSlopcameraIcon,
|
|
27
|
+
renderSlopcameraIcon,
|
|
28
|
+
slopcameraIconLanguages,
|
|
29
|
+
slopcameraIconPalettes,
|
|
30
|
+
slopcameraIconSceneLimits,
|
|
31
|
+
SlopcameraIconError,
|
|
32
|
+
type SlopcameraIconComposeInput,
|
|
33
|
+
type SlopcameraIconRenderInput,
|
|
34
|
+
} from "../icon-scene.js"
|
|
35
|
+
import {
|
|
36
|
+
composeSlopcameraSoundtrack,
|
|
37
|
+
deriveSlopcameraSoundtrackGrid,
|
|
38
|
+
slopcameraSoundtrackFormats,
|
|
39
|
+
slopcameraSoundtrackLimits,
|
|
40
|
+
SlopcameraSoundtrackError,
|
|
41
|
+
type SlopcameraSoundtrackComposeInput,
|
|
42
|
+
type SlopcameraSoundtrackGridInput,
|
|
43
|
+
} from "../soundtrack.js"
|
|
44
|
+
import { BoundedFileError } from "../bounded-file.js"
|
|
25
45
|
import {
|
|
26
46
|
createDefaultHostResourceCoordinator,
|
|
27
47
|
HostResourceError,
|
|
@@ -158,6 +178,28 @@ function deepFreeze<T>(value: T): T {
|
|
|
158
178
|
return Object.freeze(value)
|
|
159
179
|
}
|
|
160
180
|
|
|
181
|
+
const iconReceiptOutputSchema = {
|
|
182
|
+
type: "object",
|
|
183
|
+
additionalProperties: false,
|
|
184
|
+
required: ["ok", "operation", "receipt"],
|
|
185
|
+
properties: {
|
|
186
|
+
ok: { const: true },
|
|
187
|
+
operation: { type: "string", enum: ["slopcamera.icon.compose", "slopcamera.icon.render"] },
|
|
188
|
+
receipt: { type: "object" },
|
|
189
|
+
},
|
|
190
|
+
} as const
|
|
191
|
+
|
|
192
|
+
const soundtrackReceiptOutputSchema = {
|
|
193
|
+
type: "object",
|
|
194
|
+
additionalProperties: false,
|
|
195
|
+
required: ["ok", "operation", "receipt"],
|
|
196
|
+
properties: {
|
|
197
|
+
ok: { const: true },
|
|
198
|
+
operation: { type: "string", enum: ["slopcamera.soundtrack.compose", "slopcamera.soundtrack.grid"] },
|
|
199
|
+
receipt: { type: "object" },
|
|
200
|
+
},
|
|
201
|
+
} as const
|
|
202
|
+
|
|
161
203
|
/**
|
|
162
204
|
* The complete MCP tool registry. Public copy cites its size, its scene-tool
|
|
163
205
|
* count, and its names; `scripts/check-copy.ts` fails when they disagree.
|
|
@@ -999,6 +1041,131 @@ export const slopcameraMcpTools: readonly McpToolDefinition[] = deepFreeze([
|
|
|
999
1041
|
openWorldHint: false,
|
|
1000
1042
|
},
|
|
1001
1043
|
},
|
|
1044
|
+
{
|
|
1045
|
+
name: "compose_icon",
|
|
1046
|
+
title: "Compose icon scene",
|
|
1047
|
+
description:
|
|
1048
|
+
"Parse and solve one root-relative vector icon scene, collection, construction program, or recipe, and report its digests, geometry, and diagnostics. Optionally writes the canonical solved document. Local and deterministic; no model or network.",
|
|
1049
|
+
inputSchema: {
|
|
1050
|
+
type: "object",
|
|
1051
|
+
additionalProperties: false,
|
|
1052
|
+
required: ["path"],
|
|
1053
|
+
properties: {
|
|
1054
|
+
path: {
|
|
1055
|
+
type: "string",
|
|
1056
|
+
description: `Root-relative path to an icon source JSON (${String(slopcameraIconSceneLimits.sourceBytes / 1024 / 1024)} MiB maximum).`,
|
|
1057
|
+
},
|
|
1058
|
+
output: {
|
|
1059
|
+
type: "string",
|
|
1060
|
+
description: "Optional root-relative .json path for the canonical solved document.",
|
|
1061
|
+
},
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
outputSchema: iconReceiptOutputSchema,
|
|
1065
|
+
annotations: {
|
|
1066
|
+
title: "Compose icon scene",
|
|
1067
|
+
readOnlyHint: false,
|
|
1068
|
+
destructiveHint: true,
|
|
1069
|
+
idempotentHint: true,
|
|
1070
|
+
openWorldHint: false,
|
|
1071
|
+
},
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
name: "render_icon",
|
|
1075
|
+
title: "Render icon scene",
|
|
1076
|
+
description:
|
|
1077
|
+
"Draw one root-relative vector icon scene or construction program to an inert SVG, or replay a recipe and require its recorded digests. Optionally writes a replayable recipe. Local and deterministic; no model or network.",
|
|
1078
|
+
inputSchema: {
|
|
1079
|
+
type: "object",
|
|
1080
|
+
additionalProperties: false,
|
|
1081
|
+
required: ["path", "output"],
|
|
1082
|
+
properties: {
|
|
1083
|
+
path: {
|
|
1084
|
+
type: "string",
|
|
1085
|
+
description: `Root-relative path to an icon source JSON (${String(slopcameraIconSceneLimits.sourceBytes / 1024 / 1024)} MiB maximum).`,
|
|
1086
|
+
},
|
|
1087
|
+
output: { type: "string", description: "Root-relative .svg output path." },
|
|
1088
|
+
recipe: { type: "string", description: "Optional root-relative .json path for a replayable recipe." },
|
|
1089
|
+
size: {
|
|
1090
|
+
type: "integer",
|
|
1091
|
+
minimum: slopcameraIconSceneLimits.sizeMin,
|
|
1092
|
+
maximum: slopcameraIconSceneLimits.sizeMax,
|
|
1093
|
+
description: "Output edge in pixels. Recipes fix their own size.",
|
|
1094
|
+
},
|
|
1095
|
+
background: { type: "boolean", description: "Paint the paper background." },
|
|
1096
|
+
palette: { type: "string", enum: slopcameraIconPalettes, description: "Scene palette." },
|
|
1097
|
+
language: { type: "string", enum: slopcameraIconLanguages, description: "Construction-program drawing language." },
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
outputSchema: iconReceiptOutputSchema,
|
|
1101
|
+
annotations: {
|
|
1102
|
+
title: "Render icon scene",
|
|
1103
|
+
readOnlyHint: false,
|
|
1104
|
+
destructiveHint: true,
|
|
1105
|
+
idempotentHint: true,
|
|
1106
|
+
openWorldHint: false,
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
name: "compose_soundtrack",
|
|
1111
|
+
title: "Compose soundtrack score",
|
|
1112
|
+
description:
|
|
1113
|
+
"Parse and verify one root-relative loop or song from compose text, song text, score JSON, or a Standard MIDI file, and report its canonical digest, tempo, meter, and length. Optionally writes the score document. Renders no audio; no network.",
|
|
1114
|
+
inputSchema: {
|
|
1115
|
+
type: "object",
|
|
1116
|
+
additionalProperties: false,
|
|
1117
|
+
required: ["path"],
|
|
1118
|
+
properties: {
|
|
1119
|
+
path: {
|
|
1120
|
+
type: "string",
|
|
1121
|
+
description: `Root-relative path to a score or MIDI file (${String(slopcameraSoundtrackLimits.sourceBytes / 1024 / 1024)} MiB maximum).`,
|
|
1122
|
+
},
|
|
1123
|
+
output: { type: "string", description: "Optional root-relative .json path for the score document." },
|
|
1124
|
+
format: { type: "string", enum: slopcameraSoundtrackFormats, description: "Source format. Detected when omitted." },
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
outputSchema: soundtrackReceiptOutputSchema,
|
|
1128
|
+
annotations: {
|
|
1129
|
+
title: "Compose soundtrack score",
|
|
1130
|
+
readOnlyHint: false,
|
|
1131
|
+
destructiveHint: true,
|
|
1132
|
+
idempotentHint: true,
|
|
1133
|
+
openWorldHint: false,
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
name: "derive_soundtrack_grid",
|
|
1138
|
+
title: "Derive soundtrack beat grid",
|
|
1139
|
+
description:
|
|
1140
|
+
"Derive bpm, beatOffsetUs, beatsPerBar, and section cue times from one root-relative loop, song, or Standard MIDI file, in the music timing HTML scene requests accept. Optionally writes the grid. Renders no audio; no network.",
|
|
1141
|
+
inputSchema: {
|
|
1142
|
+
type: "object",
|
|
1143
|
+
additionalProperties: false,
|
|
1144
|
+
required: ["path"],
|
|
1145
|
+
properties: {
|
|
1146
|
+
path: {
|
|
1147
|
+
type: "string",
|
|
1148
|
+
description: `Root-relative path to a score or MIDI file (${String(slopcameraSoundtrackLimits.sourceBytes / 1024 / 1024)} MiB maximum).`,
|
|
1149
|
+
},
|
|
1150
|
+
output: { type: "string", description: "Optional root-relative .json path for the grid." },
|
|
1151
|
+
format: { type: "string", enum: slopcameraSoundtrackFormats, description: "Source format. Detected when omitted." },
|
|
1152
|
+
start_us: {
|
|
1153
|
+
type: "integer",
|
|
1154
|
+
minimum: 0,
|
|
1155
|
+
maximum: slopcameraSoundtrackLimits.maxTimeUs,
|
|
1156
|
+
description: "Timeline microsecond where the soundtrack's first beat lands. Defaults to 0.",
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
outputSchema: soundtrackReceiptOutputSchema,
|
|
1161
|
+
annotations: {
|
|
1162
|
+
title: "Derive soundtrack beat grid",
|
|
1163
|
+
readOnlyHint: false,
|
|
1164
|
+
destructiveHint: true,
|
|
1165
|
+
idempotentHint: true,
|
|
1166
|
+
openWorldHint: false,
|
|
1167
|
+
},
|
|
1168
|
+
},
|
|
1002
1169
|
])
|
|
1003
1170
|
|
|
1004
1171
|
class ToolFailure extends Error {
|
|
@@ -1402,6 +1569,65 @@ function parseSceneBehaviorAuditArguments(
|
|
|
1402
1569
|
}
|
|
1403
1570
|
}
|
|
1404
1571
|
|
|
1572
|
+
type LocalMediaOperation =
|
|
1573
|
+
| { readonly operation: "slopcamera.icon.compose"; readonly input: SlopcameraIconComposeInput }
|
|
1574
|
+
| { readonly operation: "slopcamera.icon.render"; readonly input: SlopcameraIconRenderInput }
|
|
1575
|
+
| { readonly operation: "slopcamera.soundtrack.compose"; readonly input: SlopcameraSoundtrackComposeInput }
|
|
1576
|
+
| { readonly operation: "slopcamera.soundtrack.grid"; readonly input: SlopcameraSoundtrackGridInput }
|
|
1577
|
+
|
|
1578
|
+
const localMediaToolOperations = Object.freeze({
|
|
1579
|
+
compose_icon: "slopcamera.icon.compose",
|
|
1580
|
+
render_icon: "slopcamera.icon.render",
|
|
1581
|
+
compose_soundtrack: "slopcamera.soundtrack.compose",
|
|
1582
|
+
derive_soundtrack_grid: "slopcamera.soundtrack.grid",
|
|
1583
|
+
} as const)
|
|
1584
|
+
|
|
1585
|
+
type LocalMediaToolName = keyof typeof localMediaToolOperations
|
|
1586
|
+
|
|
1587
|
+
function isLocalMediaTool(name: string): name is LocalMediaToolName {
|
|
1588
|
+
return Object.hasOwn(localMediaToolOperations, name)
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function isLocalMediaOperation(
|
|
1592
|
+
operation: SlopcameraOperationCode,
|
|
1593
|
+
): operation is LocalMediaOperation["operation"] {
|
|
1594
|
+
return (Object.values(localMediaToolOperations) as readonly string[]).includes(operation)
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Map a dedicated tool's snake_case arguments onto the operation's input and
|
|
1599
|
+
* validate them with the same registry parser `execute_slopcamera` uses.
|
|
1600
|
+
*/
|
|
1601
|
+
function parseLocalMediaToolArguments(
|
|
1602
|
+
name: LocalMediaToolName,
|
|
1603
|
+
value: unknown,
|
|
1604
|
+
): LocalMediaOperation {
|
|
1605
|
+
if (!isRecord(value)) {
|
|
1606
|
+
throw new ToolFailure("INVALID_ARGUMENTS", "Tool arguments must be an object.")
|
|
1607
|
+
}
|
|
1608
|
+
const allowed: Record<LocalMediaToolName, readonly string[]> = {
|
|
1609
|
+
compose_icon: ["path", "output"],
|
|
1610
|
+
render_icon: ["path", "output", "recipe", "size", "background", "palette", "language"],
|
|
1611
|
+
compose_soundtrack: ["path", "output", "format"],
|
|
1612
|
+
derive_soundtrack_grid: ["path", "output", "format", "start_us"],
|
|
1613
|
+
}
|
|
1614
|
+
rejectUnknownKeys(value, new Set(allowed[name]))
|
|
1615
|
+
const renames: Readonly<Record<string, string>> = {
|
|
1616
|
+
path: "sourcePath",
|
|
1617
|
+
output: "outputPath",
|
|
1618
|
+
recipe: "recipePath",
|
|
1619
|
+
start_us: "startUs",
|
|
1620
|
+
}
|
|
1621
|
+
const input = Object.fromEntries(
|
|
1622
|
+
Object.entries(value).map(([key, entry]) => [renames[key] ?? key, entry]),
|
|
1623
|
+
)
|
|
1624
|
+
const operation = localMediaToolOperations[name]
|
|
1625
|
+
return {
|
|
1626
|
+
operation,
|
|
1627
|
+
input: parseSlopcameraOperationInput(operation, input),
|
|
1628
|
+
} as LocalMediaOperation
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1405
1631
|
function parseSceneTemporalArguments(value: unknown): ParsedSceneTemporalArguments {
|
|
1406
1632
|
if (!isRecord(value)) {
|
|
1407
1633
|
throw new ToolFailure("INVALID_ARGUMENTS", "Tool arguments must be an object.")
|
|
@@ -1568,6 +1794,13 @@ function failureResult(error: unknown): McpToolResult {
|
|
|
1568
1794
|
error.message.replace(/^\[[A-Z_]+\]\s*/u, ""),
|
|
1569
1795
|
320,
|
|
1570
1796
|
)
|
|
1797
|
+
} else if (
|
|
1798
|
+
error instanceof SlopcameraIconError ||
|
|
1799
|
+
error instanceof SlopcameraSoundtrackError ||
|
|
1800
|
+
error instanceof BoundedFileError
|
|
1801
|
+
) {
|
|
1802
|
+
code = error.code
|
|
1803
|
+
message = safeFragment(error.message, 320)
|
|
1571
1804
|
} else if (error instanceof VectorizeError) {
|
|
1572
1805
|
code = `VECTORIZE_${error.code.toUpperCase()}`
|
|
1573
1806
|
message = "Local vectorization failed safely."
|
|
@@ -1872,6 +2105,9 @@ export class SlopcameraMcpToolRuntime {
|
|
|
1872
2105
|
async () => await this.auditSceneTemporal(options),
|
|
1873
2106
|
)
|
|
1874
2107
|
}
|
|
2108
|
+
if (isLocalMediaTool(name)) {
|
|
2109
|
+
return await this.runLocalMedia(parseLocalMediaToolArguments(name, argumentsValue))
|
|
2110
|
+
}
|
|
1875
2111
|
throw new ToolFailure("UNKNOWN_TOOL", "Requested tool is not available.")
|
|
1876
2112
|
} catch (error) {
|
|
1877
2113
|
return failureResult(error)
|
|
@@ -1896,6 +2132,18 @@ export class SlopcameraMcpToolRuntime {
|
|
|
1896
2132
|
private async execute(
|
|
1897
2133
|
options: ParsedExecuteArguments,
|
|
1898
2134
|
): Promise<McpToolResult> {
|
|
2135
|
+
if (isLocalMediaOperation(options.operation)) {
|
|
2136
|
+
const result = await this.runLocalMedia({
|
|
2137
|
+
operation: options.operation,
|
|
2138
|
+
input: parseSlopcameraOperationInput(options.operation, options.input),
|
|
2139
|
+
} as LocalMediaOperation)
|
|
2140
|
+
if (result.isError === true) return result
|
|
2141
|
+
const structured = result.structuredContent as { readonly receipt: unknown }
|
|
2142
|
+
return {
|
|
2143
|
+
content: result.content,
|
|
2144
|
+
structuredContent: { ok: true, operation: options.operation, result: structured.receipt },
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
1899
2147
|
if (options.operation === "slopcamera.diagram.check") {
|
|
1900
2148
|
const input = parseSlopcameraOperationInput(
|
|
1901
2149
|
options.operation,
|
|
@@ -2021,6 +2269,69 @@ export class SlopcameraMcpToolRuntime {
|
|
|
2021
2269
|
})
|
|
2022
2270
|
}
|
|
2023
2271
|
|
|
2272
|
+
/**
|
|
2273
|
+
* Icon and soundtrack operations: confine every path to the workspace root,
|
|
2274
|
+
* cap the source before reading, run in process under the registry's host
|
|
2275
|
+
* claim, and report root-relative paths in the receipt.
|
|
2276
|
+
*/
|
|
2277
|
+
private async runLocalMedia(request: LocalMediaOperation): Promise<McpToolResult> {
|
|
2278
|
+
// Queued with diagram renders so writes inside the root stay one at a time.
|
|
2279
|
+
return await this.enqueueRender(async () => await this.withHostAdmission(request.operation, async () => {
|
|
2280
|
+
const sourceLimit = request.operation.startsWith("slopcamera.icon.")
|
|
2281
|
+
? slopcameraIconSceneLimits.sourceBytes
|
|
2282
|
+
: slopcameraSoundtrackLimits.sourceBytes
|
|
2283
|
+
const source = await this.boundary.resolveInputFile(request.input.sourcePath, sourceLimit)
|
|
2284
|
+
const relative = new Map<string, string>([[source.absolutePath, source.relativePath]])
|
|
2285
|
+
const output = async (value: string | undefined): Promise<string | undefined> => {
|
|
2286
|
+
if (value === undefined) return undefined
|
|
2287
|
+
const prepared = await this.boundary.prepareOutputFile(value)
|
|
2288
|
+
relative.set(prepared.absolutePath, prepared.relativePath)
|
|
2289
|
+
return prepared.absolutePath
|
|
2290
|
+
}
|
|
2291
|
+
const outputPath = await output(request.input.outputPath)
|
|
2292
|
+
const paths = {
|
|
2293
|
+
sourcePath: source.absolutePath,
|
|
2294
|
+
...(outputPath === undefined ? {} : { outputPath }),
|
|
2295
|
+
}
|
|
2296
|
+
let receipt: { readonly operation: string }
|
|
2297
|
+
switch (request.operation) {
|
|
2298
|
+
case "slopcamera.icon.compose":
|
|
2299
|
+
receipt = await composeSlopcameraIcon({ ...request.input, ...paths })
|
|
2300
|
+
break
|
|
2301
|
+
case "slopcamera.icon.render": {
|
|
2302
|
+
if (paths.outputPath === undefined) {
|
|
2303
|
+
throw new ToolFailure("INVALID_ARGUMENTS", "output is required.")
|
|
2304
|
+
}
|
|
2305
|
+
const recipePath = await output(request.input.recipePath)
|
|
2306
|
+
receipt = await renderSlopcameraIcon({
|
|
2307
|
+
...request.input,
|
|
2308
|
+
...paths,
|
|
2309
|
+
outputPath: paths.outputPath,
|
|
2310
|
+
...(recipePath === undefined ? {} : { recipePath }),
|
|
2311
|
+
})
|
|
2312
|
+
break
|
|
2313
|
+
}
|
|
2314
|
+
case "slopcamera.soundtrack.compose":
|
|
2315
|
+
receipt = await composeSlopcameraSoundtrack({ ...request.input, ...paths })
|
|
2316
|
+
break
|
|
2317
|
+
case "slopcamera.soundtrack.grid":
|
|
2318
|
+
receipt = await deriveSlopcameraSoundtrackGrid({ ...request.input, ...paths })
|
|
2319
|
+
break
|
|
2320
|
+
}
|
|
2321
|
+
// Receipts carry absolute paths; the tool reports only root-relative ones.
|
|
2322
|
+
const portable = JSON.parse(
|
|
2323
|
+
JSON.stringify(receipt, (key, value: unknown) =>
|
|
2324
|
+
key === "path" && typeof value === "string" ? (relative.get(value) ?? "") : value,
|
|
2325
|
+
),
|
|
2326
|
+
) as Record<string, unknown>
|
|
2327
|
+
const written = [...relative.values()].filter((path) => path !== source.relativePath)
|
|
2328
|
+
return successResult(
|
|
2329
|
+
`Executed ${request.operation} on ${source.relativePath}${written.length === 0 ? "" : `: wrote ${written.join(", ")}`}.`,
|
|
2330
|
+
{ ok: true, operation: request.operation, receipt: portable },
|
|
2331
|
+
)
|
|
2332
|
+
}))
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2024
2335
|
private async check(options: ParsedCheckArguments): Promise<McpToolResult> {
|
|
2025
2336
|
const { source, spec } = await loadDiagram(this.boundary, options.path)
|
|
2026
2337
|
const allFindings = lintDiagram(spec)
|
package/src/mcp/types.ts
CHANGED
|
@@ -51,6 +51,10 @@ export interface McpToolDefinition {
|
|
|
51
51
|
| "check_scene_behavior"
|
|
52
52
|
| "audit_scene_behavior"
|
|
53
53
|
| "audit_scene_temporal"
|
|
54
|
+
| "compose_icon"
|
|
55
|
+
| "render_icon"
|
|
56
|
+
| "compose_soundtrack"
|
|
57
|
+
| "derive_soundtrack_grid"
|
|
54
58
|
readonly title: string
|
|
55
59
|
readonly description: string
|
|
56
60
|
readonly inputSchema: Readonly<Record<string, unknown>>
|