@hraness/slopcamera 3.8.1 → 3.9.1
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 +13 -6
- package/apps/desktop/application/html-overlay-integrity.ts +56 -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/html-overlay-renderer.ts +23 -9
- 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-6jrxaser.js → index-jmyg6t9c.js} +4 -4
- package/dist/{index-842t02pp.js → index-m6g0ca9b.js} +1 -1
- package/dist/{index-y8spxs77.js → index-mgychzh3.js} +6 -3
- 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/operations.ts
CHANGED
|
@@ -29,6 +29,29 @@ import {
|
|
|
29
29
|
slopcameraGalleryLimits,
|
|
30
30
|
type SlopcameraGalleryReceipt,
|
|
31
31
|
} from "./image-gallery.js"
|
|
32
|
+
import {
|
|
33
|
+
composeSlopcameraIcon,
|
|
34
|
+
renderSlopcameraIcon,
|
|
35
|
+
slopcameraIconLanguages,
|
|
36
|
+
slopcameraIconPalettes,
|
|
37
|
+
slopcameraIconSceneLimits,
|
|
38
|
+
type SlopcameraIconComposeInput,
|
|
39
|
+
type SlopcameraIconComposeReceipt,
|
|
40
|
+
type SlopcameraIconPalette,
|
|
41
|
+
type SlopcameraIconRenderInput,
|
|
42
|
+
type SlopcameraIconRenderReceipt,
|
|
43
|
+
} from "./icon-scene.js"
|
|
44
|
+
import {
|
|
45
|
+
composeSlopcameraSoundtrack,
|
|
46
|
+
deriveSlopcameraSoundtrackGrid,
|
|
47
|
+
slopcameraSoundtrackFormats,
|
|
48
|
+
slopcameraSoundtrackLimits,
|
|
49
|
+
type SlopcameraSoundtrackComposeInput,
|
|
50
|
+
type SlopcameraSoundtrackComposeReceipt,
|
|
51
|
+
type SlopcameraSoundtrackFormat,
|
|
52
|
+
type SlopcameraSoundtrackGridInput,
|
|
53
|
+
type SlopcameraSoundtrackGridReceipt,
|
|
54
|
+
} from "./soundtrack.js"
|
|
32
55
|
import {
|
|
33
56
|
vectorizeImage,
|
|
34
57
|
type VectorizeReceipt,
|
|
@@ -51,6 +74,10 @@ export const slopcameraOperationCodes = [
|
|
|
51
74
|
"slopcamera.image.generate",
|
|
52
75
|
"slopcamera.image.icon",
|
|
53
76
|
"slopcamera.image.gallery",
|
|
77
|
+
"slopcamera.icon.compose",
|
|
78
|
+
"slopcamera.icon.render",
|
|
79
|
+
"slopcamera.soundtrack.compose",
|
|
80
|
+
"slopcamera.soundtrack.grid",
|
|
54
81
|
] as const
|
|
55
82
|
|
|
56
83
|
export type SlopcameraOperationCode = (typeof slopcameraOperationCodes)[number]
|
|
@@ -103,6 +130,16 @@ const pathSchema = {
|
|
|
103
130
|
maxLength: 4_096,
|
|
104
131
|
} as const
|
|
105
132
|
|
|
133
|
+
const localOperationResources = [
|
|
134
|
+
{ resource: "cpu", amount: 1 },
|
|
135
|
+
{ resource: "local-io", amount: 1 },
|
|
136
|
+
] as const
|
|
137
|
+
|
|
138
|
+
const soundtrackFormatSchema = {
|
|
139
|
+
type: "string",
|
|
140
|
+
enum: slopcameraSoundtrackFormats,
|
|
141
|
+
} as const
|
|
142
|
+
|
|
106
143
|
function deepFreeze<T>(value: T): T {
|
|
107
144
|
if (typeof value !== "object" || value === null || Object.isFrozen(value)) {
|
|
108
145
|
return value
|
|
@@ -390,6 +427,102 @@ export const slopcameraOperationRegistry: readonly SlopcameraOperationDescriptor
|
|
|
390
427
|
retry: "never",
|
|
391
428
|
},
|
|
392
429
|
},
|
|
430
|
+
{
|
|
431
|
+
code: "slopcamera.icon.compose",
|
|
432
|
+
title: "Compose icon scene",
|
|
433
|
+
description:
|
|
434
|
+
"Parse and solve a local vector icon scene, collection, construction program, or recipe, and report its digests and geometry. Optionally write the canonical solved document. Local and deterministic.",
|
|
435
|
+
execution: "local",
|
|
436
|
+
authentication: "none",
|
|
437
|
+
destructive: true,
|
|
438
|
+
idempotent: true,
|
|
439
|
+
inputSchema: {
|
|
440
|
+
type: "object",
|
|
441
|
+
additionalProperties: false,
|
|
442
|
+
required: ["sourcePath"],
|
|
443
|
+
properties: {
|
|
444
|
+
sourcePath: pathSchema,
|
|
445
|
+
outputPath: pathSchema,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
resources: [...localOperationResources],
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
code: "slopcamera.icon.render",
|
|
452
|
+
title: "Render icon scene",
|
|
453
|
+
description:
|
|
454
|
+
"Draw a local vector icon scene or construction program to an inert SVG, or replay a recipe and require its recorded digests. Optionally write a replayable recipe. Local and deterministic.",
|
|
455
|
+
execution: "local",
|
|
456
|
+
authentication: "none",
|
|
457
|
+
destructive: true,
|
|
458
|
+
idempotent: true,
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
additionalProperties: false,
|
|
462
|
+
required: ["sourcePath", "outputPath"],
|
|
463
|
+
properties: {
|
|
464
|
+
sourcePath: pathSchema,
|
|
465
|
+
outputPath: pathSchema,
|
|
466
|
+
recipePath: pathSchema,
|
|
467
|
+
size: {
|
|
468
|
+
type: "integer",
|
|
469
|
+
minimum: slopcameraIconSceneLimits.sizeMin,
|
|
470
|
+
maximum: slopcameraIconSceneLimits.sizeMax,
|
|
471
|
+
},
|
|
472
|
+
background: { type: "boolean" },
|
|
473
|
+
palette: { type: "string", enum: slopcameraIconPalettes },
|
|
474
|
+
language: { type: "string", enum: slopcameraIconLanguages },
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
resources: [...localOperationResources],
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
code: "slopcamera.soundtrack.compose",
|
|
481
|
+
title: "Compose soundtrack score",
|
|
482
|
+
description:
|
|
483
|
+
"Parse and verify a local loop or song from compose text, song text, score JSON, or a Standard MIDI file, and report its canonical digest, tempo, and length. Optionally write the score document. Renders no audio.",
|
|
484
|
+
execution: "local",
|
|
485
|
+
authentication: "none",
|
|
486
|
+
destructive: true,
|
|
487
|
+
idempotent: true,
|
|
488
|
+
inputSchema: {
|
|
489
|
+
type: "object",
|
|
490
|
+
additionalProperties: false,
|
|
491
|
+
required: ["sourcePath"],
|
|
492
|
+
properties: {
|
|
493
|
+
sourcePath: pathSchema,
|
|
494
|
+
outputPath: pathSchema,
|
|
495
|
+
format: soundtrackFormatSchema,
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
resources: [...localOperationResources],
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
code: "slopcamera.soundtrack.grid",
|
|
502
|
+
title: "Derive soundtrack beat grid",
|
|
503
|
+
description:
|
|
504
|
+
"Derive bpm, beatOffsetUs, beatsPerBar, and section cue times from a local loop, song, or Standard MIDI file, in the music timing HTML scene requests accept. Optionally write the grid. Renders no audio.",
|
|
505
|
+
execution: "local",
|
|
506
|
+
authentication: "none",
|
|
507
|
+
destructive: true,
|
|
508
|
+
idempotent: true,
|
|
509
|
+
inputSchema: {
|
|
510
|
+
type: "object",
|
|
511
|
+
additionalProperties: false,
|
|
512
|
+
required: ["sourcePath"],
|
|
513
|
+
properties: {
|
|
514
|
+
sourcePath: pathSchema,
|
|
515
|
+
outputPath: pathSchema,
|
|
516
|
+
format: soundtrackFormatSchema,
|
|
517
|
+
startUs: {
|
|
518
|
+
type: "integer",
|
|
519
|
+
minimum: 0,
|
|
520
|
+
maximum: slopcameraSoundtrackLimits.maxTimeUs,
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
resources: [...localOperationResources],
|
|
525
|
+
},
|
|
393
526
|
] satisfies readonly SlopcameraOperationDescriptor[])
|
|
394
527
|
|
|
395
528
|
export interface CheckSlopcameraOperationInput {
|
|
@@ -448,6 +581,10 @@ export interface SlopcameraOperationInputMap {
|
|
|
448
581
|
readonly "slopcamera.image.generate": GenerateSlopcameraOperationInput
|
|
449
582
|
readonly "slopcamera.image.icon": IconSlopcameraOperationInput
|
|
450
583
|
readonly "slopcamera.image.gallery": GallerySlopcameraOperationInput
|
|
584
|
+
readonly "slopcamera.icon.compose": SlopcameraIconComposeInput
|
|
585
|
+
readonly "slopcamera.icon.render": SlopcameraIconRenderInput
|
|
586
|
+
readonly "slopcamera.soundtrack.compose": SlopcameraSoundtrackComposeInput
|
|
587
|
+
readonly "slopcamera.soundtrack.grid": SlopcameraSoundtrackGridInput
|
|
451
588
|
}
|
|
452
589
|
|
|
453
590
|
export interface SlopcameraOperationResultMap {
|
|
@@ -467,6 +604,10 @@ export interface SlopcameraOperationResultMap {
|
|
|
467
604
|
readonly "slopcamera.image.generate": GeneratedSlopcameraImageFile
|
|
468
605
|
readonly "slopcamera.image.icon": SlopcameraIconReceipt
|
|
469
606
|
readonly "slopcamera.image.gallery": SlopcameraGalleryReceipt
|
|
607
|
+
readonly "slopcamera.icon.compose": SlopcameraIconComposeReceipt
|
|
608
|
+
readonly "slopcamera.icon.render": SlopcameraIconRenderReceipt
|
|
609
|
+
readonly "slopcamera.soundtrack.compose": SlopcameraSoundtrackComposeReceipt
|
|
610
|
+
readonly "slopcamera.soundtrack.grid": SlopcameraSoundtrackGridReceipt
|
|
470
611
|
}
|
|
471
612
|
|
|
472
613
|
function operationFailure(message: string): never {
|
|
@@ -835,6 +976,123 @@ function parseGallery(value: unknown): GallerySlopcameraOperationInput {
|
|
|
835
976
|
}
|
|
836
977
|
}
|
|
837
978
|
|
|
979
|
+
function optionalPathValue(value: unknown, name: string): string | undefined {
|
|
980
|
+
return value === undefined ? undefined : pathValue(value, name)
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function requireSuffix(path: string | undefined, suffix: string, name: string): void {
|
|
984
|
+
if (path !== undefined && !path.toLowerCase().endsWith(suffix)) {
|
|
985
|
+
operationFailure(`${name} must end in ${suffix}.`)
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function parseIconCompose(value: unknown): SlopcameraIconComposeInput {
|
|
990
|
+
const input = record(value, ["sourcePath", "outputPath"])
|
|
991
|
+
const outputPath = optionalPathValue(input.outputPath, "outputPath")
|
|
992
|
+
requireSuffix(outputPath, ".json", "outputPath")
|
|
993
|
+
return {
|
|
994
|
+
sourcePath: pathValue(input.sourcePath, "sourcePath"),
|
|
995
|
+
...(outputPath === undefined ? {} : { outputPath }),
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function parseIconRender(value: unknown): SlopcameraIconRenderInput {
|
|
1000
|
+
const input = record(value, [
|
|
1001
|
+
"sourcePath",
|
|
1002
|
+
"outputPath",
|
|
1003
|
+
"recipePath",
|
|
1004
|
+
"size",
|
|
1005
|
+
"background",
|
|
1006
|
+
"palette",
|
|
1007
|
+
"language",
|
|
1008
|
+
])
|
|
1009
|
+
const outputPath = pathValue(input.outputPath, "outputPath")
|
|
1010
|
+
requireSuffix(outputPath, ".svg", "outputPath")
|
|
1011
|
+
const recipePath = optionalPathValue(input.recipePath, "recipePath")
|
|
1012
|
+
requireSuffix(recipePath, ".json", "recipePath")
|
|
1013
|
+
const { size, background, palette, language } = input
|
|
1014
|
+
if (
|
|
1015
|
+
size !== undefined &&
|
|
1016
|
+
(!Number.isSafeInteger(size) ||
|
|
1017
|
+
(size as number) < slopcameraIconSceneLimits.sizeMin ||
|
|
1018
|
+
(size as number) > slopcameraIconSceneLimits.sizeMax)
|
|
1019
|
+
) {
|
|
1020
|
+
operationFailure(
|
|
1021
|
+
`size must be an integer from ${slopcameraIconSceneLimits.sizeMin} through ${slopcameraIconSceneLimits.sizeMax}.`,
|
|
1022
|
+
)
|
|
1023
|
+
}
|
|
1024
|
+
if (background !== undefined && typeof background !== "boolean") {
|
|
1025
|
+
operationFailure("background must be a boolean.")
|
|
1026
|
+
}
|
|
1027
|
+
if (
|
|
1028
|
+
palette !== undefined &&
|
|
1029
|
+
!slopcameraIconPalettes.includes(palette as SlopcameraIconPalette)
|
|
1030
|
+
) {
|
|
1031
|
+
operationFailure(`palette must be one of ${slopcameraIconPalettes.join(", ")}.`)
|
|
1032
|
+
}
|
|
1033
|
+
if (
|
|
1034
|
+
language !== undefined &&
|
|
1035
|
+
(typeof language !== "string" || !slopcameraIconLanguages.includes(language as never))
|
|
1036
|
+
) {
|
|
1037
|
+
operationFailure(`language must be one of ${slopcameraIconLanguages.join(", ")}.`)
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
sourcePath: pathValue(input.sourcePath, "sourcePath"),
|
|
1041
|
+
outputPath,
|
|
1042
|
+
...(recipePath === undefined ? {} : { recipePath }),
|
|
1043
|
+
...(size === undefined ? {} : { size: size as number }),
|
|
1044
|
+
...(background === undefined ? {} : { background: background as boolean }),
|
|
1045
|
+
...(palette === undefined ? {} : { palette: palette as SlopcameraIconPalette }),
|
|
1046
|
+
...(language === undefined
|
|
1047
|
+
? {}
|
|
1048
|
+
: { language: language as NonNullable<SlopcameraIconRenderInput["language"]> }),
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function parseSoundtrackFormat(value: unknown): SlopcameraSoundtrackFormat | undefined {
|
|
1053
|
+
if (value === undefined) return undefined
|
|
1054
|
+
if (!slopcameraSoundtrackFormats.includes(value as SlopcameraSoundtrackFormat)) {
|
|
1055
|
+
operationFailure(`format must be one of ${slopcameraSoundtrackFormats.join(", ")}.`)
|
|
1056
|
+
}
|
|
1057
|
+
return value as SlopcameraSoundtrackFormat
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function parseSoundtrackCompose(value: unknown): SlopcameraSoundtrackComposeInput {
|
|
1061
|
+
const input = record(value, ["sourcePath", "outputPath", "format"])
|
|
1062
|
+
const outputPath = optionalPathValue(input.outputPath, "outputPath")
|
|
1063
|
+
requireSuffix(outputPath, ".json", "outputPath")
|
|
1064
|
+
const format = parseSoundtrackFormat(input.format)
|
|
1065
|
+
return {
|
|
1066
|
+
sourcePath: pathValue(input.sourcePath, "sourcePath"),
|
|
1067
|
+
...(outputPath === undefined ? {} : { outputPath }),
|
|
1068
|
+
...(format === undefined ? {} : { format }),
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function parseSoundtrackGrid(value: unknown): SlopcameraSoundtrackGridInput {
|
|
1073
|
+
const input = record(value, ["sourcePath", "outputPath", "format", "startUs"])
|
|
1074
|
+
const outputPath = optionalPathValue(input.outputPath, "outputPath")
|
|
1075
|
+
requireSuffix(outputPath, ".json", "outputPath")
|
|
1076
|
+
const format = parseSoundtrackFormat(input.format)
|
|
1077
|
+
const startUs = input.startUs
|
|
1078
|
+
if (
|
|
1079
|
+
startUs !== undefined &&
|
|
1080
|
+
(!Number.isSafeInteger(startUs) ||
|
|
1081
|
+
(startUs as number) < 0 ||
|
|
1082
|
+
(startUs as number) > slopcameraSoundtrackLimits.maxTimeUs)
|
|
1083
|
+
) {
|
|
1084
|
+
operationFailure(
|
|
1085
|
+
`startUs must be an integer from 0 through ${slopcameraSoundtrackLimits.maxTimeUs}.`,
|
|
1086
|
+
)
|
|
1087
|
+
}
|
|
1088
|
+
return {
|
|
1089
|
+
sourcePath: pathValue(input.sourcePath, "sourcePath"),
|
|
1090
|
+
...(outputPath === undefined ? {} : { outputPath }),
|
|
1091
|
+
...(format === undefined ? {} : { format }),
|
|
1092
|
+
...(startUs === undefined ? {} : { startUs: startUs as number }),
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
838
1096
|
export function parseSlopcameraOperationInput<C extends SlopcameraOperationCode>(
|
|
839
1097
|
code: C,
|
|
840
1098
|
input: unknown,
|
|
@@ -852,6 +1110,14 @@ export function parseSlopcameraOperationInput<C extends SlopcameraOperationCode>
|
|
|
852
1110
|
return parseIcon(input) as SlopcameraOperationInputMap[C]
|
|
853
1111
|
case "slopcamera.image.gallery":
|
|
854
1112
|
return parseGallery(input) as SlopcameraOperationInputMap[C]
|
|
1113
|
+
case "slopcamera.icon.compose":
|
|
1114
|
+
return parseIconCompose(input) as SlopcameraOperationInputMap[C]
|
|
1115
|
+
case "slopcamera.icon.render":
|
|
1116
|
+
return parseIconRender(input) as SlopcameraOperationInputMap[C]
|
|
1117
|
+
case "slopcamera.soundtrack.compose":
|
|
1118
|
+
return parseSoundtrackCompose(input) as SlopcameraOperationInputMap[C]
|
|
1119
|
+
case "slopcamera.soundtrack.grid":
|
|
1120
|
+
return parseSoundtrackGrid(input) as SlopcameraOperationInputMap[C]
|
|
855
1121
|
default:
|
|
856
1122
|
throw new SlopcameraOperationError(
|
|
857
1123
|
"INVALID_OPERATION",
|
|
@@ -1166,6 +1432,46 @@ async function executeSlopcameraOperationUncoordinated<
|
|
|
1166
1432
|
dependencies,
|
|
1167
1433
|
)) as SlopcameraOperationResultMap[C]
|
|
1168
1434
|
}
|
|
1435
|
+
case "slopcamera.icon.compose": {
|
|
1436
|
+
const options = input as SlopcameraIconComposeInput
|
|
1437
|
+
return (await composeSlopcameraIcon({
|
|
1438
|
+
sourcePath: resolve(options.sourcePath),
|
|
1439
|
+
...(options.outputPath === undefined
|
|
1440
|
+
? {}
|
|
1441
|
+
: { outputPath: resolve(options.outputPath) }),
|
|
1442
|
+
})) as SlopcameraOperationResultMap[C]
|
|
1443
|
+
}
|
|
1444
|
+
case "slopcamera.icon.render": {
|
|
1445
|
+
const options = input as SlopcameraIconRenderInput
|
|
1446
|
+
return (await renderSlopcameraIcon({
|
|
1447
|
+
...options,
|
|
1448
|
+
sourcePath: resolve(options.sourcePath),
|
|
1449
|
+
outputPath: resolve(options.outputPath),
|
|
1450
|
+
...(options.recipePath === undefined
|
|
1451
|
+
? {}
|
|
1452
|
+
: { recipePath: resolve(options.recipePath) }),
|
|
1453
|
+
})) as SlopcameraOperationResultMap[C]
|
|
1454
|
+
}
|
|
1455
|
+
case "slopcamera.soundtrack.compose": {
|
|
1456
|
+
const options = input as SlopcameraSoundtrackComposeInput
|
|
1457
|
+
return (await composeSlopcameraSoundtrack({
|
|
1458
|
+
...options,
|
|
1459
|
+
sourcePath: resolve(options.sourcePath),
|
|
1460
|
+
...(options.outputPath === undefined
|
|
1461
|
+
? {}
|
|
1462
|
+
: { outputPath: resolve(options.outputPath) }),
|
|
1463
|
+
})) as SlopcameraOperationResultMap[C]
|
|
1464
|
+
}
|
|
1465
|
+
case "slopcamera.soundtrack.grid": {
|
|
1466
|
+
const options = input as SlopcameraSoundtrackGridInput
|
|
1467
|
+
return (await deriveSlopcameraSoundtrackGrid({
|
|
1468
|
+
...options,
|
|
1469
|
+
sourcePath: resolve(options.sourcePath),
|
|
1470
|
+
...(options.outputPath === undefined
|
|
1471
|
+
? {}
|
|
1472
|
+
: { outputPath: resolve(options.outputPath) }),
|
|
1473
|
+
})) as SlopcameraOperationResultMap[C]
|
|
1474
|
+
}
|
|
1169
1475
|
default:
|
|
1170
1476
|
throw new SlopcameraOperationError(
|
|
1171
1477
|
"INVALID_OPERATION",
|