stillmade-sdk 0.1.40 → 0.1.42
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/.stillmade/sdk-baseline.json +19 -19
- package/AGENTS.md +6 -2
- package/FRAME_VIEWS.md +41 -0
- package/INTERFACE_DESIGN.md +100 -8
- package/MOBILE_UI.md +3 -1
- package/PASTE_TO_LLM.md +18 -13
- package/SDK.md +200 -37
- package/UI_AESTHETIC.md +93 -4
- package/examples/check-clip.stillmade.json +4 -0
- package/examples/clean-text.stillmade.json +4 -0
- package/examples/custom-interface.stillmade.json +4 -0
- package/examples/describe-image.stillmade.json +4 -0
- package/examples/foley-cue.stillmade.json +4 -0
- package/examples/generate-image.stillmade.json +4 -0
- package/examples/generate-video.stillmade.json +4 -0
- package/examples/module-word-stats/stillmade.block.json +4 -0
- package/examples/narrate-script.stillmade.json +4 -0
- package/examples/numbered-takes.stillmade.json +4 -0
- package/examples/research-topic.stillmade.json +4 -0
- package/examples/score-a-scene.stillmade.json +4 -0
- package/examples/script-draft.stillmade.json +4 -0
- package/examples/summarize-page.stillmade.json +4 -0
- package/examples/text-workflow.stillmade.json +4 -0
- package/examples/thicken-lines.stillmade.json +4 -0
- package/examples/timeline-polish.stillmade.json +4 -0
- package/examples/transcribe-audio.stillmade.json +4 -0
- package/examples/word-count.stillmade.json +4 -0
- package/package.json +1 -1
- package/packages/block-cli/dev-host/assets/host.js +208 -37
- package/packages/block-cli/dev-host/assets/index.css +1 -1
- package/packages/block-cli/ready-gate.js +4 -0
- package/packages/block-cli/templates.js +7 -7
- package/packages/block-platform/block-readiness.js +24 -2
- package/packages/block-platform/canvas.js +17 -0
- package/packages/block-platform/catalog.js +21 -6
- package/packages/block-platform/host-release-manifests.js +95 -0
- package/packages/block-platform/native-host-inventory.js +29 -4
- package/packages/block-platform/node-defaults.js +1 -0
- package/packages/block-platform/project-context.js +1 -1
- package/packages/block-platform/script-handoff.js +1 -1
- package/packages/block-platform/step-groups.js +3 -1
- package/packages/block-sdk/audio-generation-example.js +2 -2
- package/packages/block-sdk/builder-contract.js +15 -4
- package/packages/block-sdk/capability-example.js +1 -1
- package/packages/block-sdk/contracts.js +3 -1
- package/packages/block-sdk/examples.js +3 -3
- package/packages/block-sdk/image-description-example.js +1 -1
- package/packages/block-sdk/image-generation-example.js +1 -1
- package/packages/block-sdk/index.d.ts +2 -2
- package/packages/block-sdk/media-analysis-example.js +1 -1
- package/packages/block-sdk/module-example.js +1 -1
- package/packages/block-sdk/speech-example.js +1 -1
- package/packages/block-sdk/state-example.js +1 -1
- package/packages/block-sdk/timeline-proposal-example.js +1 -1
- package/packages/block-sdk/transcription-example.js +1 -1
- package/packages/block-sdk/trigger-examples.js +2 -2
- package/packages/block-sdk/video-generation-example.js +1 -1
- package/packages/block-sdk/view-design.js +119 -18
- package/packages/block-sdk/web-example.js +2 -2
- package/packages/block-sdk/workspace-navigation.js +6 -2
- package/packages/design-system/block-kit.css +33 -6
- package/packages/design-system/block-kit.html +14 -4
- package/packages/design-system/theme.css +6 -5
|
@@ -500,5 +500,100 @@ export const HOST_RELEASE_MANIFEST_SNAPSHOTS=Object.freeze([
|
|
|
500
500
|
"filesystem": [],
|
|
501
501
|
"secrets": []
|
|
502
502
|
}
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"schemaVersion": 1,
|
|
506
|
+
"sdkVersion": "0.1.0",
|
|
507
|
+
"id": "sm.voiceover",
|
|
508
|
+
"name": "Voiceover",
|
|
509
|
+
"version": "1.2.0",
|
|
510
|
+
"kind": "workspace",
|
|
511
|
+
"runtime": "host",
|
|
512
|
+
"description": "Turn your script into speech, or record your own voice.",
|
|
513
|
+
"inputs": {
|
|
514
|
+
"input": {
|
|
515
|
+
"type": "script",
|
|
516
|
+
"primary": true,
|
|
517
|
+
"role": "narration_script",
|
|
518
|
+
"required": false,
|
|
519
|
+
"description": "The words the voice says, from any Step before this one. They arrive as a suggestion you accept before they replace your script.",
|
|
520
|
+
"semantic": "narration_script"
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
"outputs": {
|
|
524
|
+
"output": {
|
|
525
|
+
"type": "audio",
|
|
526
|
+
"primary": true,
|
|
527
|
+
"role": "narration_audio",
|
|
528
|
+
"description": "The voiceover audio.",
|
|
529
|
+
"semantic": "narration_audio"
|
|
530
|
+
},
|
|
531
|
+
"timing": {
|
|
532
|
+
"type": "transcript",
|
|
533
|
+
"description": "Every spoken word with its start and end time, for Steps that cut to the voice.",
|
|
534
|
+
"semantic": "timed_transcript"
|
|
535
|
+
},
|
|
536
|
+
"spokenScript": {
|
|
537
|
+
"type": "script",
|
|
538
|
+
"description": "The words of the voice, as the script for Steps that work from it.",
|
|
539
|
+
"semantic": "narration_script"
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
"permissions": {
|
|
543
|
+
"project": [],
|
|
544
|
+
"network": [],
|
|
545
|
+
"filesystem": [],
|
|
546
|
+
"secrets": []
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"schemaVersion": 1,
|
|
551
|
+
"sdkVersion": "0.1.0",
|
|
552
|
+
"id": "sm.panel-strip",
|
|
553
|
+
"name": "Image Panels",
|
|
554
|
+
"version": "1.2.0",
|
|
555
|
+
"kind": "workspace",
|
|
556
|
+
"runtime": "host",
|
|
557
|
+
"description": "Turn your script into pictures that play with the voice.",
|
|
558
|
+
"inputs": {
|
|
559
|
+
"narrationTiming": {
|
|
560
|
+
"type": "transcript",
|
|
561
|
+
"primary": true,
|
|
562
|
+
"required": false,
|
|
563
|
+
"description": "Timed narration words from an earlier Step. When connected and filled, the panels are cut to these timings instead of the project's own voiceover.",
|
|
564
|
+
"semantic": "timed_transcript"
|
|
565
|
+
},
|
|
566
|
+
"narrationAudio": {
|
|
567
|
+
"type": "audio",
|
|
568
|
+
"required": false,
|
|
569
|
+
"description": "The narration audio that goes with those timings.",
|
|
570
|
+
"semantic": "narration_audio"
|
|
571
|
+
},
|
|
572
|
+
"narrationScript": {
|
|
573
|
+
"type": "script",
|
|
574
|
+
"required": false,
|
|
575
|
+
"description": "Narration script from an earlier Step, used for the story when no timed words are connected.",
|
|
576
|
+
"semantic": "narration_script"
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
"outputs": {
|
|
580
|
+
"panels": {
|
|
581
|
+
"type": "panel-document",
|
|
582
|
+
"primary": true,
|
|
583
|
+
"role": "image_panel_document",
|
|
584
|
+
"description": "The complete Image Panels document for the next Step.",
|
|
585
|
+
"semantic": "image_panel_document"
|
|
586
|
+
},
|
|
587
|
+
"images": {
|
|
588
|
+
"type": "image[]",
|
|
589
|
+
"description": "Each panel's current picture, in story order."
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
"permissions": {
|
|
593
|
+
"project": [],
|
|
594
|
+
"network": [],
|
|
595
|
+
"filesystem": [],
|
|
596
|
+
"secrets": []
|
|
597
|
+
}
|
|
503
598
|
}
|
|
504
599
|
].map(manifest=>Object.freeze(manifest)));
|
|
@@ -352,7 +352,8 @@ export const NATIVE_HOST_INVENTORY=[
|
|
|
352
352
|
"id": "sm.panel-strip",
|
|
353
353
|
"versions": [
|
|
354
354
|
"1.0.0",
|
|
355
|
-
"1.1.0"
|
|
355
|
+
"1.1.0",
|
|
356
|
+
"1.2.0"
|
|
356
357
|
],
|
|
357
358
|
"phase": "shotMap",
|
|
358
359
|
"kind": "image-panels",
|
|
@@ -381,7 +382,19 @@ export const NATIVE_HOST_INVENTORY=[
|
|
|
381
382
|
"priority": 10
|
|
382
383
|
}
|
|
383
384
|
],
|
|
384
|
-
"host": {}
|
|
385
|
+
"host": {},
|
|
386
|
+
"releaseHosts": {
|
|
387
|
+
"1.2.0": {
|
|
388
|
+
"canvasNode": "workspaceHostNode",
|
|
389
|
+
"ports": {
|
|
390
|
+
"narrationAudio": "in:narrationAudio",
|
|
391
|
+
"narrationTiming": "in:narrationTiming",
|
|
392
|
+
"narrationScript": "in:narrationScript",
|
|
393
|
+
"panels": "out:panels",
|
|
394
|
+
"images": "out:images"
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
385
398
|
},
|
|
386
399
|
{
|
|
387
400
|
"schemaVersion": 1,
|
|
@@ -609,11 +622,23 @@ export const NATIVE_HOST_INVENTORY=[
|
|
|
609
622
|
"id": "sm.voiceover",
|
|
610
623
|
"versions": [
|
|
611
624
|
"1.0.0",
|
|
612
|
-
"1.1.0"
|
|
625
|
+
"1.1.0",
|
|
626
|
+
"1.2.0"
|
|
613
627
|
],
|
|
614
628
|
"phase": "voiceover",
|
|
615
629
|
"kind": "voiceover",
|
|
616
|
-
"host": {}
|
|
630
|
+
"host": {},
|
|
631
|
+
"releaseHosts": {
|
|
632
|
+
"1.2.0": {
|
|
633
|
+
"canvasNode": "workspaceHostNode",
|
|
634
|
+
"ports": {
|
|
635
|
+
"input": "in:input",
|
|
636
|
+
"output": "out:output",
|
|
637
|
+
"timing": "out:timing",
|
|
638
|
+
"spokenScript": "out:spokenScript"
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
617
642
|
},
|
|
618
643
|
{
|
|
619
644
|
"schemaVersion": 1,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export function defaultNodeData(type, request = {}, sceneNumber = 1, {llmModel, voiceId} = {}) {
|
|
2
2
|
if (type === 'productionWorkspaceNode')return {title:request.name || 'Production workspace',prompt:'',messages:[],versions:[],status:'pending'};
|
|
3
|
+
if (type === 'workspaceHostNode')return {title:request.name || 'Workspace',workspaceOutputs:{}};
|
|
3
4
|
if (type === 'bibleNode') return { title: request.name || 'Manual Blueprint Node', description: request.description || 'Manual node.' };
|
|
4
5
|
if (type === 'textPromptNode') {
|
|
5
6
|
return {
|
|
@@ -22,7 +22,7 @@ const sceneFields=['title','name','sceneNumber','location','locationId','timeOfD
|
|
|
22
22
|
const shotFields=['shotId','sceneId','sceneNumber','shotNumber','title','name','description','shotDescription','voiceoverText','text','dialogue','hasDialogue','characterSpeaking','characterId','characterIds','charactersPresent','location','locationId','startTime','endTime','startMs','durationMs','estimatedScreenTime','shotSize','shotType','cameraSize','cameraMotion','fulfillment','vsmId','panelId','type'];
|
|
23
23
|
// URL-only histories (Panels and imported editor media) have no persisted version
|
|
24
24
|
// IDs. A content-derived identifier survives history trimming and reordering.
|
|
25
|
-
function urlVersion(url) {
|
|
25
|
+
export function urlVersion(url) {
|
|
26
26
|
let a=2166136261,b=5381;
|
|
27
27
|
for(let i=0;i<url.length;i++){a=Math.imul(a^url.charCodeAt(i),16777619);b=Math.imul(b,33)^url.charCodeAt(i);}
|
|
28
28
|
return `url-${(a>>>0).toString(16)}${(b>>>0).toString(16)}-${url.length}`;
|
|
@@ -3,7 +3,7 @@ import {isScriptWriterHandoff} from './script-writer.js';
|
|
|
3
3
|
import {packageForStage} from './stage-package.js';
|
|
4
4
|
|
|
5
5
|
const SDK_SCRIPT_RUNTIMES = new Set(['recipe', 'javascript', 'capability']);
|
|
6
|
-
const VOICEOVER_VERSIONS = new Set(['1.0.0', '1.1.0']);
|
|
6
|
+
const VOICEOVER_VERSIONS = new Set(['1.0.0', '1.1.0', '1.2.0']);
|
|
7
7
|
|
|
8
8
|
// This identifies an explicit document handoff, not a Canvas wire or permission
|
|
9
9
|
// to replace narration. The host must separately verify the pinned source,
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// Steps in `stages`. Each part stays a real Step, so its permissions, approvals,
|
|
4
4
|
// credits, memory and runs are unchanged; groups only change what people see.
|
|
5
5
|
import {assert,plain} from '../block-sdk/contracts.js';
|
|
6
|
-
|
|
6
|
+
// The Block name limit, mirrored from builder-contract.js (the rule lives there; scripts/step-groups-name-length.test.js
|
|
7
|
+
// pins the two): importing it pulled 36 KB of builder prompt text into the studio's startup graph for one number.
|
|
8
|
+
export const BLOCK_NAME_MAX_LENGTH=40;
|
|
7
9
|
|
|
8
10
|
export const MAX_GROUP_PARTS=12;
|
|
9
11
|
const GROUP_KEYS=['id','label','stages','settings','result'];
|
|
@@ -3,12 +3,12 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
3
3
|
// prompt and a suggested length; the person running it chooses the length and
|
|
4
4
|
// pays with StillMade credits at the app's prices.
|
|
5
5
|
export const audioMusic={
|
|
6
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.score-a-scene',version:'1.0.0',name:'Score a scene',description:'Generate a short music bed for a scene from a written mood brief.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{brief:{type:'brief',primary:true,semantic:'music_brief',required:true,description:'Mood, tempo and instruments for the music bed.'}},outputs:{music:{type:'audio',primary:true,semantic:'music_bed',description:'Generated music bed.'}},permissions:{project:[],capabilities:['audio.music'],network:[],filesystem:[],secrets:[]},ui:[]},
|
|
6
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.score-a-scene',action:{label:'Generate music',running:'Generating music…'},version:'1.0.0',name:'Score a scene',description:'Generate a short music bed for a scene from a written mood brief.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{brief:{type:'brief',primary:true,semantic:'music_brief',required:true,description:'Mood, tempo and instruments for the music bed.'}},outputs:{music:{type:'audio',primary:true,semantic:'music_bed',description:'Generated music bed.'}},permissions:{project:[],capabilities:['audio.music'],network:[],filesystem:[],secrets:[]},ui:[]},
|
|
7
7
|
capability:{schemaVersion:1,operation:'audio.music',prompt:{$input:'brief'},output:'music',settings:{durationSec:30}},
|
|
8
8
|
tests:[{name:'Calm piano bed',input:{brief:{schemaVersion:1,text:'Calm solo piano, slow tempo, warm and hopeful.'}},expectations:{music:{kind:'audio',format:'any',minDuration:1,maxDuration:241,minBytes:45,maxBytes:134217728}}}],
|
|
9
9
|
};
|
|
10
10
|
export const audioSfx={
|
|
11
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.foley-cue',version:'1.0.0',name:'Foley cue',description:'Generate a short sound effect from a written description.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{description:{type:'text',primary:true,semantic:'sound_description',required:true,description:'The sound to create.'}},outputs:{sound:{type:'audio',primary:true,semantic:'sound_effect',description:'Generated sound effect.'}},permissions:{project:[],capabilities:['audio.sfx'],network:[],filesystem:[],secrets:[]},ui:[]},
|
|
11
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.foley-cue',action:{label:'Generate sound',running:'Generating sound…'},version:'1.0.0',name:'Foley cue',description:'Generate a short sound effect from a written description.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{description:{type:'text',primary:true,semantic:'sound_description',required:true,description:'The sound to create.'}},outputs:{sound:{type:'audio',primary:true,semantic:'sound_effect',description:'Generated sound effect.'}},permissions:{project:[],capabilities:['audio.sfx'],network:[],filesystem:[],secrets:[]},ui:[]},
|
|
12
12
|
capability:{schemaVersion:1,operation:'audio.sfx',prompt:{$input:'description'},output:'sound',settings:{durationSec:3}},
|
|
13
13
|
tests:[{name:'Door creak',input:{description:'A slow wooden door creak in a quiet room.'},expectations:{sound:{kind:'audio',format:'any',minDuration:.1,maxDuration:23,minBytes:45,maxBytes:134217728}}}],
|
|
14
14
|
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import {HOSTED_OPERATION_NAMES} from './contracts.js';
|
|
10
10
|
|
|
11
11
|
export const BUILDER_CONTRACT_ID='stillmade.block-builder';
|
|
12
|
-
export const BUILDER_CONTRACT_VERSION='1.
|
|
12
|
+
export const BUILDER_CONTRACT_VERSION='1.17.0';
|
|
13
13
|
export const BUILDER_CONTRACT_STAMP=`${BUILDER_CONTRACT_ID}@${BUILDER_CONTRACT_VERSION}`;
|
|
14
14
|
export const BUILDER_CONTRACT_DOCS_PATH='/docs/build/block-builder-contract';
|
|
15
15
|
// Block names appear in step bars, cards and pickers; new releases keep them short.
|
|
@@ -72,9 +72,20 @@ const SECTIONS=Object.freeze([
|
|
|
72
72
|
rule('input-words','block',
|
|
73
73
|
'Name each input in the job\'s words (`sermonVideo`, not `video`): StillMade words its drop area from the name.',
|
|
74
74
|
'Name each input for what the person brings, in the job\'s words: `sermonVideo` for a sermon clip Block, `listingPhotos` for a listing, `lessonSlides` for a lesson podcast. StillMade titles the input from that name ("Sermon video") and words its drop area from it ("Drop your sermon video here", the formats, "Choose a video"), in StillMade and the SDK preview, so never draw your own upload box for an input. Keep a bare type name (`video`, `file`) only when any file of that kind does the job. The `description` says what makes a good one.'),
|
|
75
|
+
// November 2, 2026 (owner): every screen speaks the job, as a product made only for it would, and stays quiet. The same
|
|
76
|
+
// day (owner, of four tiles each saying "Only on the device it was added from"): "if it's a notification make it a
|
|
77
|
+
// notification": state is one StillMade notification or nothing, never a sentence on the page or one per item.
|
|
78
|
+
rule('job-words','block',
|
|
79
|
+
'Speak the job unless the person or creator asks otherwise: `manifest.action` names the button, required whenever StillMade draws it ("Plan the tour"; AI pictures, video or sound start with Generate, never "Make it"), one solid `sm-primary`, progress in the job\'s units, one line per problem, no notes on how the Block works or its state (one StillMade notification at most, never one per item), `date` inputs for days and times, plain input names so "Use a reference" fills them, posting as its own connector Step. Clear every Wording note `gate` lists.',
|
|
80
|
+
'These are StillMade\'s defaults: unless the person or the creator asked for something else, every screen speaks the job in its own words and adds nothing else. `manifest.action` names the Step\'s button, {label:"Plan the tour", running:"Planning the tour…"}, and StillMade adds the price ("Plan the tour · 1 credit"). Every Block whose Step shows StillMade\'s button declares it: any Block without its own screen, and any that calls operations. Without it StillMade can only guess from the Block\'s name or output ("Make clips"), and the readiness gate and StillMade\'s own builder send the Block back until it names the job. When the Block makes a picture, video or sound with AI the label starts with Generate and says what ("Generate tour", "Generate voiceover"); one that cuts or renders the person\'s own media says Make ("Make 6 clips"); never "Make it", "Run", "Submit", "Process" or a bare "Generate". The main button stands out: one solid `sm-primary` in the kit\'s ink, never restyled to the background; everything else is `sm-quiet` or an `sm-icon`, and close is an X icon. Report progress in the job\'s units with `stillmade.progress(fraction, label)` ("Moving through the kitchen, 4 of 9 rooms"), never "Processing". Head the result in the job\'s words ("9 rooms", "Your tour · 44 s"), never "Output". Say a problem in one line with its fix beside it ("No bathroom photo. Guests look for one." and Add photo). Stay quiet: no welcome text, tips under fields, tours or banners, and at most one line of guidance on a screen, only where the next move is not obvious; anything more goes in the input\'s `description`, which StillMade shows behind an (i). Say nothing about how the Block works (where it runs, saving, syncing, recovery, accounts, credits) or where a file is kept: StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. Every drop area offers "Use a reference", which fills the Step from the person\'s saved photos, videos and notes: media inputs fill by kind, so take `video` or `asset` where a clip works as well as a photo, and text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference\'s note); never build your own reference picker. Days and times are picked, never typed: use the `date` type, which StillMade shows as a calendar, with a list of times when the input is about a time (`postAt`, `startsAt`); a custom view uses `<input type="date">` or `type="datetime-local"`. Posting is a connector Step of its own, and StillMade words its button from the app and action ("Post to Instagram"). Before handing the Block back, clear every Wording note that `gate` and readiness list, unless the person asked for those words.'),
|
|
81
|
+
// November 2, 2026 (owner): StillMade centers and zooms a screen; an app that lays itself out (a CAD tool) opts out with
|
|
82
|
+
// data-stillmade-layout="own" and keeps its own layout and Ctrl/Cmd +/- keys. The same day (owner: "implement A absolutely
|
|
83
|
+
// universally, like in app, platform, sdk, just everywhere"): every <video controls> gets StillMade's player and result
|
|
84
|
+
// pictures its full-screen button; a video without controls stays the Block's own. Also that day (owner: "there is no
|
|
85
|
+
// separation and I cannot tell what is going on"): a pick is the kit's ink fill, and media that cannot show is a quiet tile.
|
|
75
86
|
rule('ui','block',
|
|
76
|
-
'Prefer `manifest.ui`; custom views use the Block kit (`sm-*`) unless a look is named. Pictures over words: preview each option before choosing; few words, fold the rest; buttons carry `data-stillmade-action`; the start button names the action (never "Run") and shows no price (StillMade adds it). A job fits one screen unscrolled and a 320px phone; an app (editor, configurator, CAD tool) works like that app: full canvas, panels, tools, undo, never a form.',
|
|
77
|
-
'If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (
|
|
87
|
+
'Prefer `manifest.ui`; custom views use the Block kit (`sm-*`) unless a look is named. Pictures over words: preview each option before choosing; few words, fold the rest; buttons carry `data-stillmade-action`; the start button names the action (never "Run" or "Make it") and shows no price (StillMade adds it). A job fits one screen unscrolled and a 320px phone; an app (editor, configurator, CAD tool) works like that app: full canvas, panels, tools, undo, never a form. An app puts `data-stillmade-layout="own"` on its root: StillMade never centers or zooms it. `<video controls>` gets StillMade\'s player; leave out `controls` when the Block drives playback. A pick keeps the kit\'s ink fill; media that cannot show is a wordless `sm-media-off` tile.',
|
|
88
|
+
'If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, the picked one in the kit\'s ink fill (never restyled to an outline, a tint or the page\'s background) so it reads at a glance, one solid `sm-primary` naming the action in the job\'s words ("Plan the tour", "Generate voiceover"; never "Run" or "Make it") and no price (StillMade\'s run button adds it), extras in a closed `sm-more`; a look the person names wins. Fit one laptop screen without scrolling: label beside control, status in a few words, files as tiles, rare tools as icons, (i) on unclear controls. These compact rules are for a job or an ingredient. Size the view to the Block\'s tier (block-tier.js): an app, a workspace a person works in for a while (video editor, 3D configurator, CAD or drawing tool), looks and works like that kind of app instead of a compact page: its canvas or stage fills the view, with the panels, toolbars, inspector or timeline such an app has, direct manipulation (select, drag, zoom), keyboard shortcuts and undo; it keeps the host appearance and Block kit controls, its panels may scroll, and on a phone its tools fold into sheets while the canvas stays usable. Never shrink an app into a form of fields and one button. StillMade centers a standard view narrower than its Step and zooms a view to fill its space; an app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. StillMade draws one player for every `<video controls>` (play, timeline, time, sound and full screen, shown while the pointer is on the video, with keys and touch) and a full-screen button on result pictures in `sm-stage` or `sm-gallery` and on any element marked `data-stillmade-expand` (the same value makes one set of pictures, `off` removes it); `data-stillmade-player="native"` keeps the browser\'s controls, and a `<video>` without `controls` is the Block\'s own and is never touched. A picture or clip that cannot show here (kept on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape with the `image-off` glyph and the reason in its label, never words in its place. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Mark each button `data-stillmade-action="what it does"` and label each field: Chat presses and fills them. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Show no raw JSON, internal IDs, filesystem details, provider terms or developer settings unless the Block is a developer tool. When a person chooses among options they need to hear or see (voices, music, fonts, looks, templates, images), let them preview each option before choosing: a sample or thumbnail from the Block\'s own files or the host\'s free samples, never a paid generation. Skip previews only when there is nothing to preview. Choices StillMade owns (voices, models in the run review) already show samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.'),
|
|
78
89
|
rule('multiplayer','block',
|
|
79
90
|
'Custom views share edits: `StillMade.onShared`, publish every editable field (`data-stillmade-share`, `bindShared`, `updateShared`), respect `canEdit`, never re-run on remote updates.',
|
|
80
91
|
'Projects are multiplayer. A custom view subscribes through `StillMade.onShared(callback)` and publishes every editable field: give static controls a unique `id` and `data-stillmade-share="field"` (the host owns their synchronization), use `StillMade.bindShared(field, elementId)` for controls created dynamically, and `StillMade.updateShared(values, before, options)` for structured values; private variables and DOM values are not shared. Mark a truly device-only control (playback, hover, device selection) with `data-stillmade-local`. Observe shared outputs as well as state, honor `canEdit` in every mutating callback (read-only participants cannot write), never run, click or call a paid integration in response to a remote update, and preserve conflicting drafts. Shared background work uses `sharedWork` with the `work.shared` permission.'),
|
|
@@ -147,7 +158,7 @@ export const BUILDER_CONTRACT=Object.freeze({
|
|
|
147
158
|
gapClasses:GAP_CLASSES,
|
|
148
159
|
});
|
|
149
160
|
|
|
150
|
-
const SECTION_TITLES=Object.freeze({'runtime-choice':'Choose the runtime','grounded-text':'Only supplied facts','source-of-truth':'Source of truth','reuse-first':'Reuse first','block-tier':'Three sizes of Block','block-scope':'One job per Step','capability-contract':'Define the capability first','feature-map':'Feature map','compose':'Blocks compose','platform-primitives':'Use platform primitives','no-workarounds':'No hidden workarounds','naming':'Name it briefly','input-words':'Name inputs in the job\'s words','ui':'Interface','multiplayer':'Shared editing','external-services':'External services','dependencies':'Dependencies','security':'Security','lifecycle':'Execution lifecycle','errors':'Useful failures','adapt-to-platform':'Adapt to the platform','verification':'What StillMade verifies','project-types':'Project Types','readiness-report':'Readiness report'});
|
|
161
|
+
const SECTION_TITLES=Object.freeze({'runtime-choice':'Choose the runtime','grounded-text':'Only supplied facts','source-of-truth':'Source of truth','reuse-first':'Reuse first','block-tier':'Three sizes of Block','block-scope':'One job per Step','capability-contract':'Define the capability first','feature-map':'Feature map','compose':'Blocks compose','platform-primitives':'Use platform primitives','no-workarounds':'No hidden workarounds','naming':'Name it briefly','input-words':'Name inputs in the job\'s words','job-words':'Speak the job','ui':'Interface','multiplayer':'Shared editing','external-services':'External services','dependencies':'Dependencies','security':'Security','lifecycle':'Execution lifecycle','errors':'Useful failures','adapt-to-platform':'Adapt to the platform','verification':'What StillMade verifies','project-types':'Project Types','readiness-report':'Readiness report'});
|
|
151
162
|
const inScope=(scope,wanted)=>wanted==='all'||scope==='both'||scope===wanted;
|
|
152
163
|
|
|
153
164
|
/** Render the contract for a model prompt ('compact'), a guide ('full') or an SDK folder ('agents-md'). */
|
|
@@ -2,7 +2,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
2
2
|
// Portable request; provider/model/payment and real execution approval are
|
|
3
3
|
// selected in StillMade. These fixtures are expectations, not recorded results.
|
|
4
4
|
export const textGenerate={
|
|
5
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.script-draft',version:'1.0.0',name:'Draft a short script',description:'Turn a production brief into a short narration draft using an approved text-generation call.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{prompt:{type:'text',primary:true,semantic:'production_brief',required:true,description:'Production brief that guides the narration draft.'}},outputs:{script:{type:'script',primary:true,semantic:'narration_script',description:'A concise narration script based on the supplied brief.'}},permissions:{project:[],capabilities:['text.generate'],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'prompt'}]},
|
|
5
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.script-draft',action:{label:'Write script',running:'Writing script…'},version:'1.0.0',name:'Draft a short script',description:'Turn a production brief into a short narration draft using an approved text-generation call.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{prompt:{type:'text',primary:true,semantic:'production_brief',required:true,description:'Production brief that guides the narration draft.'}},outputs:{script:{type:'script',primary:true,semantic:'narration_script',description:'A concise narration script based on the supplied brief.'}},permissions:{project:[],capabilities:['text.generate'],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'prompt'}]},
|
|
6
6
|
capability:{schemaVersion:1,operation:'text.generate',prompt:{$input:'prompt'},system:'Write a concise narration script. Return only the script text.',maxTokens:1200,output:'script'},
|
|
7
7
|
tests:[{name:'A short opening narration',input:{prompt:'Write two narration sentences about a quiet forest at sunrise.'},expectations:{script:{kind:'script',minLength:20,maxLength:6000}}}],
|
|
8
8
|
};
|
|
@@ -128,7 +128,7 @@ export function validateManifest(manifest, { builtIn = false } = {}) {
|
|
|
128
128
|
const check = (ok, path, message) => { if (!ok) errors.push({ path, message }); };
|
|
129
129
|
try { safeDocument(manifest); } catch (e) { return { valid: false, errors: [{ path: '$', message: e.message }] }; }
|
|
130
130
|
if (!plain(manifest)) return { valid: false, errors: [{ path: '$', message: 'Manifest must be an object' }] };
|
|
131
|
-
const allowed = ['schemaVersion', 'id', 'name', 'version', 'sdkVersion', 'description', 'kind', 'inputs', 'outputs', 'permissions', 'runtime', 'jobs', 'ui', 'uiLayout', 'entry', 'license', 'provenance', 'summary', 'category', 'platforms', 'state', 'storage', 'semantics', 'desktopTools', 'operations', 'sharedWork', 'triggers', 'features'];
|
|
131
|
+
const allowed = ['schemaVersion', 'id', 'name', 'version', 'sdkVersion', 'description', 'kind', 'inputs', 'outputs', 'permissions', 'runtime', 'jobs', 'ui', 'uiLayout', 'action', 'entry', 'license', 'provenance', 'summary', 'category', 'platforms', 'state', 'storage', 'semantics', 'desktopTools', 'operations', 'sharedWork', 'triggers', 'features'];
|
|
132
132
|
for (const key of Object.keys(manifest)) check(allowed.includes(key), key, 'Unknown manifest field');
|
|
133
133
|
for (const message of featureMapErrors(manifest)) check(false, 'features', message);
|
|
134
134
|
if(manifest.sharedWork!==undefined){try{validateSharedWorkContract(manifest.sharedWork);}catch(error){check(false,'sharedWork',error.message);}check(manifest.permissions?.project?.includes('work.shared'),'sharedWork','Declare the work.shared permission');}
|
|
@@ -158,6 +158,8 @@ export function validateManifest(manifest, { builtIn = false } = {}) {
|
|
|
158
158
|
check(typeof manifest.name === 'string' && manifest.name.trim().length > 0 && manifest.name.length <= 120, 'name', 'Name must be 1–120 characters');
|
|
159
159
|
check(typeof manifest.description === 'string' && manifest.description.trim().length > 0 && manifest.description.length <= 1000, 'description', 'Description must be 1–1000 characters');
|
|
160
160
|
check(KINDS.includes(manifest.kind), 'kind', 'Unsupported block kind');
|
|
161
|
+
// The Step's button in the job's words ("Plan the tour"), November 2, 2026; StillMade adds the price.
|
|
162
|
+
if(manifest.action!==undefined){const action=manifest.action,text=(value,max)=>typeof value==='string'&&value.trim().length>0&&value.length<=max&&!/[\r\n]/.test(value);check(plain(action)&&Object.keys(action).every(key=>['label','running'].includes(key))&&text(action.label,40)&&(action.running===undefined||text(action.running,48)),'action','Name the button as {label:"Plan the tour", running:"Planning the tour…"}: a label up to 40 characters, and optional running words up to 48');}
|
|
161
163
|
check(['recipe', 'javascript', 'module', 'comfyui', 'capability', ...(builtIn ? ['host'] : [])].includes(manifest.runtime), 'runtime', 'Installable runtimes are recipe, isolated JavaScript, sandboxed JavaScript modules with WebAssembly, the reviewed ComfyUI adapter, and approved host capabilities');
|
|
162
164
|
if(manifest.state!==undefined){
|
|
163
165
|
const state=manifest.state;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
2
2
|
export const textCleanup = {
|
|
3
|
-
manifest: { schemaVersion: 1, license: 'MIT', provenance: EXAMPLE_PROVENANCE, sdkVersion: '0.1.0', id: 'example.clean-text', version: '1.0.0', name: 'Clean text', description: 'Trims extra space from text and returns a clean copy for the next step.', kind: 'task', runtime: 'recipe', inputs: { text: { type: 'text', semantic: 'plain_text', required: true, description: 'Text whose surrounding whitespace should be removed.' } }, outputs: { text: { type: 'text', semantic: 'plain_text', description: 'The same text with surrounding whitespace removed.' } }, permissions: { project: [], network: [], filesystem: [], secrets: [] }, ui: [{ control: 'text', port: 'text' }] },
|
|
3
|
+
manifest: { schemaVersion: 1, license: 'MIT', provenance: EXAMPLE_PROVENANCE, sdkVersion: '0.1.0', id: 'example.clean-text',action:{label:'Clean text',running:'Cleaning text…'}, version: '1.0.0', name: 'Clean text', description: 'Trims extra space from text and returns a clean copy for the next step.', kind: 'task', runtime: 'recipe', inputs: { text: { type: 'text', semantic: 'plain_text', required: true, description: 'Text whose surrounding whitespace should be removed.' } }, outputs: { text: { type: 'text', semantic: 'plain_text', description: 'The same text with surrounding whitespace removed.' } }, permissions: { project: [], network: [], filesystem: [], secrets: [] }, ui: [{ control: 'text', port: 'text' }] },
|
|
4
4
|
recipe: { schemaVersion: 1, steps: [{ id: 'clean', op: 'text.trim', args: { text: { $input: 'text' } } }], outputs: { text: { $step: 'clean' } } },
|
|
5
5
|
tests: [{ name: 'Trims surrounding whitespace', input: { text: ' StillMade\n' }, expected: { text: 'StillMade' } }],
|
|
6
6
|
};
|
|
7
7
|
const pixels = Array.from({ length: 5 * 5 * 4 }, (_, i) => i >= 48 && i <= 50 ? 0 : 255);
|
|
8
8
|
const expanded = pixels.map((v, i) => { const p = Math.floor(i / 4), x = p % 5, y = Math.floor(p / 5); return x >= 1 && x <= 3 && y >= 1 && y <= 3 && i % 4 !== 3 ? 0 : v; });
|
|
9
9
|
export const lineArt = {
|
|
10
|
-
manifest: { schemaVersion: 1, license: 'MIT', provenance: EXAMPLE_PROVENANCE, sdkVersion: '0.1.0', id: 'example.thicken-lines', version: '1.0.1', name: 'Thicken line art', description: 'Expands dark outlines in an image while preserving surrounding colors, creating a new image for the next production step.', kind: 'task', runtime: 'recipe', inputs: { image: { type: 'image', primary: true, semantic: 'source_image', required: true, description: 'Line-art image whose dark outlines should be expanded.' }, thickness: { type: 'integer', default: 2, min: 1, max: 8, semantic: 'line_thickness', required: false, description: 'Number of pixels by which to expand dark outlines.' }, threshold: { type: 'number', default: 100, min: 0, max: 255, semantic: 'darkness_threshold', required: false, description: 'Brightness threshold used to identify dark outline pixels.' } }, outputs: { image: { type: 'image', primary: true, semantic: 'processed_image', description: 'A new image with expanded dark outlines.' } }, permissions: { project: [], network: [], filesystem: [], secrets: [] }, ui: [{ control: 'asset', port: 'image' }, { control: 'slider', port: 'thickness' }, { control: 'slider', port: 'threshold' }] },
|
|
10
|
+
manifest: { schemaVersion: 1, license: 'MIT', provenance: EXAMPLE_PROVENANCE, sdkVersion: '0.1.0', id: 'example.thicken-lines',action:{label:'Thicken lines',running:'Thickening lines…'}, version: '1.0.1', name: 'Thicken line art', description: 'Expands dark outlines in an image while preserving surrounding colors, creating a new image for the next production step.', kind: 'task', runtime: 'recipe', inputs: { image: { type: 'image', primary: true, semantic: 'source_image', required: true, description: 'Line-art image whose dark outlines should be expanded.' }, thickness: { type: 'integer', default: 2, min: 1, max: 8, semantic: 'line_thickness', required: false, description: 'Number of pixels by which to expand dark outlines.' }, threshold: { type: 'number', default: 100, min: 0, max: 255, semantic: 'darkness_threshold', required: false, description: 'Brightness threshold used to identify dark outline pixels.' } }, outputs: { image: { type: 'image', primary: true, semantic: 'processed_image', description: 'A new image with expanded dark outlines.' } }, permissions: { project: [], network: [], filesystem: [], secrets: [] }, ui: [{ control: 'asset', port: 'image' }, { control: 'slider', port: 'thickness' }, { control: 'slider', port: 'threshold' }] },
|
|
11
11
|
recipe: { schemaVersion: 1, steps: [{ id: 'outlines', op: 'image.thicken-lines', args: { image: { $input: 'image' }, thickness: { $input: 'thickness' }, threshold: { $input: 'threshold' } } }], outputs: { image: { $step: 'outlines' } } },
|
|
12
12
|
tests: [{ name: 'One dark point expands to a 3×3 square', input: { image: { width: 5, height: 5, data: pixels }, thickness: 1 }, expected: { image: { width: 5, height: 5, data: expanded } } }],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const javascriptText = {
|
|
16
|
-
manifest: { ...textCleanup.manifest, id: 'example.word-count', name: 'Word count', description: 'Counts words and returns trimmed text using isolated JavaScript.', runtime: 'javascript', outputs: { text: { type: 'text', semantic: 'plain_text', description: 'The trimmed source text.' }, words: { type: 'integer', semantic: 'word_count', description: 'The number of whitespace-separated words.' } } },
|
|
16
|
+
manifest: { ...textCleanup.manifest, id: 'example.word-count',action:{label:'Count words',running:'Counting words…'}, name: 'Word count', description: 'Counts words and returns trimmed text using isolated JavaScript.', runtime: 'javascript', outputs: { text: { type: 'text', semantic: 'plain_text', description: 'The trimmed source text.' }, words: { type: 'integer', semantic: 'word_count', description: 'The number of whitespace-separated words.' } } },
|
|
17
17
|
code: 'const text = input.text.trim();\nreturn { text, words: text ? text.split(/\\s+/).length : 0 };',
|
|
18
18
|
tests: [{ name: 'Counts words', input: { text: ' Hello StillMade ' }, expected: { text: 'Hello StillMade', words: 2 } }, { name: 'Empty text', input: { text: ' ' }, expected: { text: '', words: 0 } }],
|
|
19
19
|
};
|
|
@@ -3,7 +3,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
3
3
|
// Provider, model, payment and image loading belong to the trusted host. The
|
|
4
4
|
// package owns only the typed contract, bounded analysis instructions and tests.
|
|
5
5
|
export const imageDescription={
|
|
6
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.describe-image',version:'1.0.0',name:'Describe an image',description:'Turn one owned image into concise production-ready visual notes.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{image:{type:'image',primary:true,semantic:'source_image',required:true,description:'Owned image to inspect for visible production details.'}},outputs:{description:{type:'text',primary:true,role:'visual_description',semantic:'visual_description',description:'Concise production notes based only on visible image details.'}},permissions:{project:[],capabilities:['image.describe'],network:[],filesystem:[],secrets:[]},ui:[{control:'asset',port:'image'}]},
|
|
6
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.describe-image',action:{label:'Describe image',running:'Describing image…'},version:'1.0.0',name:'Describe an image',description:'Turn one owned image into concise production-ready visual notes.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{image:{type:'image',primary:true,semantic:'source_image',required:true,description:'Owned image to inspect for visible production details.'}},outputs:{description:{type:'text',primary:true,role:'visual_description',semantic:'visual_description',description:'Concise production notes based only on visible image details.'}},permissions:{project:[],capabilities:['image.describe'],network:[],filesystem:[],secrets:[]},ui:[{control:'asset',port:'image'}]},
|
|
7
7
|
capability:{schemaVersion:1,operation:'image.describe',image:{$input:'image'},instruction:'Describe only visibly supported subject matter, composition, lighting, color, texture, camera feel and visual style. Return concise production-ready plain text.',maxTokens:700,output:'description'},
|
|
8
8
|
tests:[{name:'Blue image',input:{image:{width:1,height:1,data:[20,80,200,255]}},expectations:{description:{kind:'text',minLength:20,maxLength:32768,includes:['blue']}}}],
|
|
9
9
|
};
|
|
@@ -2,7 +2,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
2
2
|
// Portable image request. This fixture describes a result to review; it is not
|
|
3
3
|
// evidence of a live generation. Model, quality and payment belong to the host.
|
|
4
4
|
export const imageGenerate={
|
|
5
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.generate-image',version:'1.0.0',name:'Generate an image',description:'Generate a square PNG from a short visual prompt using an approved host request.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{prompt:{type:'text',primary:true,semantic:'image_prompt',required:true,description:'Visual description of the image to generate.'}},outputs:{image:{type:'image',primary:true,semantic:'generated_image',description:'The generated square PNG image.'}},permissions:{project:[],capabilities:['image.generate'],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'prompt'}]},
|
|
5
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.generate-image',action:{label:'Generate image',running:'Generating image…'},version:'1.0.0',name:'Generate an image',description:'Generate a square PNG from a short visual prompt using an approved host request.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{prompt:{type:'text',primary:true,semantic:'image_prompt',required:true,description:'Visual description of the image to generate.'}},outputs:{image:{type:'image',primary:true,semantic:'generated_image',description:'The generated square PNG image.'}},permissions:{project:[],capabilities:['image.generate'],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'prompt'}]},
|
|
6
6
|
capability:{schemaVersion:1,operation:'image.generate',prompt:{$input:'prompt'},output:'image'},
|
|
7
7
|
tests:[{name:'A simple forest illustration',input:{prompt:'A simple illustration of a quiet forest with clear dark outlines and soft green colors.'},expectations:{image:{kind:'image',format:'png',width:1024,height:1024,minBytes:67,maxBytes:12000000}}}],
|
|
8
8
|
};
|
|
@@ -14,7 +14,7 @@ export interface BlockViewBridge { connectDocument(options:import('./document-st
|
|
|
14
14
|
export interface SdkCompatibility { status:'supported'|'unsupported'|'invalid'; declared:unknown; runtimeVersion:string; message:string; }
|
|
15
15
|
export function sdkCompatibility(declared:unknown,runtimeVersion?:string):SdkCompatibility;
|
|
16
16
|
export type Port = { type: string; imageMode?: 'pixels'|'reference'; role?: string; context?: keyof typeof CONTEXT_FIELDS; primary?: boolean; required?: boolean; default?: unknown; min?: number; max?: number; description?: string } & Partial<import('./semantic-contracts.js').SemanticContract>;
|
|
17
|
-
export interface Manifest { schemaVersion: 1; id: string; name: string; version: string; sdkVersion: SdkVersionRange; description: string; kind: 'task'|'workspace'|'editor-extension'|'composite'; inputs: Record<string, Port>; outputs: Record<string, Port>; runtime: 'recipe'|'javascript'|'module'|'comfyui'|'capability'; jobs?: {kind:'cooperative';version:1}; state?: {scope:'step';version:number;initial:Record<string,unknown>;migrations?:StateMigration[]}; entry?: string; permissions: { project: string[]; desktop?: DesktopPermission[]; capabilities?: ['comfyui.execute']|['text.generate']|['audio.speech']|['audio.transcribe']|['image.generate']|['video.generate']|['image.describe']|['timeline.propose']|['workspace.propose']|['connection.execute']; network?: never[]; filesystem?: never[]; secrets?: never[] }; desktopTools?:DesktopToolDeclaration[]; uiLayout?: {kind:'tabs'|'sections';groups:{id:string;label:string;inputs:string[];outputs:string[];collapsed?:boolean}[]}; ui?: ({ control: 'text'|'number'|'slider'|'checkbox'|'asset'; port: string; label?: string; when?: {port:string;equals:string|boolean} } | {control:'select'|'multiselect';port:string;options:string[];label?:string;when?:{port:string;equals:string|boolean}} | {control:'gallery';port:string;label?:string} | {control:'before-after';port:string;before:string;label?:string})[]; }
|
|
17
|
+
export interface Manifest { schemaVersion: 1; id: string; name: string; version: string; sdkVersion: SdkVersionRange; description: string; action?: { label: string; running?: string }; kind: 'task'|'workspace'|'editor-extension'|'composite'; inputs: Record<string, Port>; outputs: Record<string, Port>; runtime: 'recipe'|'javascript'|'module'|'comfyui'|'capability'; jobs?: {kind:'cooperative';version:1}; state?: {scope:'step';version:number;initial:Record<string,unknown>;migrations?:StateMigration[]}; entry?: string; permissions: { project: string[]; desktop?: DesktopPermission[]; capabilities?: ['comfyui.execute']|['text.generate']|['audio.speech']|['audio.transcribe']|['image.generate']|['video.generate']|['image.describe']|['timeline.propose']|['workspace.propose']|['connection.execute']; network?: never[]; filesystem?: never[]; secrets?: never[] }; desktopTools?:DesktopToolDeclaration[]; uiLayout?: {kind:'tabs'|'sections';groups:{id:string;label:string;inputs:string[];outputs:string[];collapsed?:boolean}[]}; ui?: ({ control: 'text'|'number'|'slider'|'checkbox'|'asset'; port: string; label?: string; when?: {port:string;equals:string|boolean} } | {control:'select'|'multiselect';port:string;options:string[];label?:string;when?:{port:string;equals:string|boolean}} | {control:'gallery';port:string;label?:string} | {control:'before-after';port:string;before:string;label?:string})[]; }
|
|
18
18
|
export interface BlockView { html: string; css?: string; themedCss?: string; javascript?: string; appearance?: 'host'|'original'; }
|
|
19
19
|
/** A frame view: any interface code (frameworks, SVG, canvas, drag) with assets named by SHA-256. */
|
|
20
20
|
export interface FrameView { runtime: 'frame'; html: string; css?: string; themedCss?: string; javascript?: string; appearance?: 'host'|'original'; files?: {schemaVersion: 1; files: Record<string, {sha256: string; bytes: number; mediaType: string}>}; }
|
|
@@ -414,7 +414,7 @@ export * from './native-host-providers.js';
|
|
|
414
414
|
|
|
415
415
|
export {blockRemixPolicy,assertRemixReady,REMIX_AUTHORING_REQUIREMENT} from './remix-policy.js';
|
|
416
416
|
export interface SdkCompatibility {status:'supported'|'unsupported'|'invalid';declared:unknown;runtimeVersion:string;message:string}
|
|
417
|
-
export interface Manifest { schemaVersion: 1; id: string; name: string; version: string; sdkVersion: SdkVersionRange; description: string; kind: 'task'|'workspace'|'editor-extension'|'composite'; inputs: Record<string, Port>; outputs: Record<string, Port>; runtime: 'recipe'|'javascript'|'module'|'comfyui'|'capability'; jobs?: {kind:'cooperative';version:1}; state?: {scope:'step';version:number;initial:Record<string,unknown>;migrations?:StateMigration[]}; entry?: string; permissions: { project: string[]; desktop?: DesktopPermission[]; capabilities?: ['comfyui.execute']|['text.generate']|['audio.speech']|['audio.transcribe']|['image.generate']|['video.generate']|['image.describe']|['timeline.propose']|['workspace.propose']|['connection.execute']; network?: never[]; filesystem?: never[]; secrets?: never[] }; uiLayout?: {kind:'tabs'|'sections';groups:{id:string;label:string;inputs:string[];outputs:string[];collapsed?:boolean}[]}; ui?: ({ control: 'text'|'number'|'slider'|'checkbox'|'asset'; port: string; label?: string; when?: {port:string;equals:string|boolean} } | {control:'select'|'multiselect';port:string;options:string[];label?:string;when?:{port:string;equals:string|boolean}} | {control:'gallery';port:string;label?:string} | {control:'before-after';port:string;before:string;label?:string})[]; }
|
|
417
|
+
export interface Manifest { schemaVersion: 1; id: string; name: string; version: string; sdkVersion: SdkVersionRange; description: string; action?: { label: string; running?: string }; kind: 'task'|'workspace'|'editor-extension'|'composite'; inputs: Record<string, Port>; outputs: Record<string, Port>; runtime: 'recipe'|'javascript'|'module'|'comfyui'|'capability'; jobs?: {kind:'cooperative';version:1}; state?: {scope:'step';version:number;initial:Record<string,unknown>;migrations?:StateMigration[]}; entry?: string; permissions: { project: string[]; desktop?: DesktopPermission[]; capabilities?: ['comfyui.execute']|['text.generate']|['audio.speech']|['audio.transcribe']|['image.generate']|['video.generate']|['image.describe']|['timeline.propose']|['workspace.propose']|['connection.execute']; network?: never[]; filesystem?: never[]; secrets?: never[] }; uiLayout?: {kind:'tabs'|'sections';groups:{id:string;label:string;inputs:string[];outputs:string[];collapsed?:boolean}[]}; ui?: ({ control: 'text'|'number'|'slider'|'checkbox'|'asset'; port: string; label?: string; when?: {port:string;equals:string|boolean} } | {control:'select'|'multiselect';port:string;options:string[];label?:string;when?:{port:string;equals:string|boolean}} | {control:'gallery';port:string;label?:string} | {control:'before-after';port:string;before:string;label?:string})[]; }
|
|
418
418
|
export interface BlockPackage { portable?: PortableMetadata; manifest: Manifest; view?: BlockView|FrameView; code?: string; module?: ModuleDescriptor; comfyui?: ComfyWorkflow; capability?: ConnectedCapability|TextGenerationCapability|SpeechCapability|TranscriptionCapability|ImageGenerationCapability|VideoGenerationCapability|ImageDescriptionCapability|TimelineProposalCapability; recipe?: { schemaVersion: 1; steps: { id: string; op: string; args: Record<string, unknown> }[]; outputs: Record<string, unknown> }; tests: { name: string; input: Record<string, unknown>; expected?: Record<string, unknown>; state?: BlockState; expectedState?: BlockState; expectations?: Record<string, ConnectedResultExpectation|ComfyImageExpectation|CapabilityTextExpectation|CapabilityAudioExpectation|CapabilityTranscriptExpectation|CapabilityImageExpectation|CapabilityVideoExpectation|CapabilityTimelineExpectation> }[]; }
|
|
419
419
|
export type CapabilityInvocation = TextGenerationInvocation|SpeechInvocation|TranscriptionInvocation|ImageGenerationInvocation|VideoGenerationInvocation|ImageDescriptionInvocation|TimelineProposalInvocation;
|
|
420
420
|
export interface CapabilityResult { outputs: Record<string, unknown>; metadata?: Record<string, unknown>; mediaReceipts?: Record<string, SpeechMediaReceipt|GeneratedAudioMediaReceipt|GeneratedImageMediaReceipt|GeneratedVideoMediaReceipt>; }
|
|
@@ -2,7 +2,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
2
2
|
// Ask StillMade's media vision about a saved video, optionally inside one time
|
|
3
3
|
// range. Each analyzed image or video is one priced call on StillMade credits.
|
|
4
4
|
export const mediaAnalysis={
|
|
5
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.check-clip',version:'1.0.0',name:'Check a clip',description:'Answer a production question about a saved video clip, with timestamps.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
5
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.check-clip',action:{label:'Check clip',running:'Checking clip…'},version:'1.0.0',name:'Check a clip',description:'Answer a production question about a saved video clip, with timestamps.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
6
6
|
inputs:{clip:{type:'video',primary:true,semantic:'source_video',required:true,description:'Saved video to check.'},start:{type:'number',required:false,min:0,semantic:'range_start',description:'Optional start of the range to look at closely, in seconds.'},end:{type:'number',required:false,min:0,semantic:'range_end',description:'Optional end of the range, in seconds (at most 12 seconds after the start).'}},
|
|
7
7
|
outputs:{report:{type:'json',primary:true,semantic:'media_findings',description:'Summary, timeline, on-screen text and issues found.'}},
|
|
8
8
|
permissions:{project:[],capabilities:['media.analyze'],network:[],filesystem:[],secrets:[]},ui:[{control:'asset',port:'clip'},{control:'number',port:'start'},{control:'number',port:'end'}]},
|
|
@@ -12,7 +12,7 @@ export default async function run(input, stillmade) {
|
|
|
12
12
|
}
|
|
13
13
|
`;
|
|
14
14
|
export const moduleWordStats={
|
|
15
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.word-stats',version:'1.0.0',name:'Word stats',description:'Counts words and finds the longest one, as a modern JavaScript module.',kind:'task',runtime:'module',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
15
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.word-stats',action:{label:'Count words',running:'Counting words…'},version:'1.0.0',name:'Word stats',description:'Counts words and finds the longest one, as a modern JavaScript module.',kind:'task',runtime:'module',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
16
16
|
inputs:{text:{type:'text',primary:true,semantic:'plain_text',required:true,description:'Text to measure.'}},
|
|
17
17
|
outputs:{words:{type:'integer',semantic:'word_count',description:'Number of words.'},longest:{type:'text',primary:true,semantic:'longest_word',description:'The longest word.'}},
|
|
18
18
|
permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'text'}]},
|
|
@@ -2,7 +2,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
2
2
|
// Portable speech request. These fixtures are expectations to review, not
|
|
3
3
|
// evidence that a provider has generated audio. Host controls choose the voice.
|
|
4
4
|
export const audioSpeech={
|
|
5
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.narrate-script',version:'1.0.0',name:'Narrate a script',description:'Turn a short script into a reviewed speech recording using a host-selected voice.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{script:{type:'script',primary:true,semantic:'narration_script',required:true,description:'Narration script to render as speech.'}},outputs:{audio:{type:'audio',primary:true,semantic:'speech_audio',description:'Speech audio generated from the narration script.'}},permissions:{project:[],capabilities:['audio.speech'],network:[],filesystem:[],secrets:[]},ui:[]},
|
|
5
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.narrate-script',action:{label:'Generate voiceover',running:'Generating voiceover…'},version:'1.0.0',name:'Narrate a script',description:'Turn a short script into a reviewed speech recording using a host-selected voice.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{script:{type:'script',primary:true,semantic:'narration_script',required:true,description:'Narration script to render as speech.'}},outputs:{audio:{type:'audio',primary:true,semantic:'speech_audio',description:'Speech audio generated from the narration script.'}},permissions:{project:[],capabilities:['audio.speech'],network:[],filesystem:[],secrets:[]},ui:[]},
|
|
6
6
|
capability:{schemaVersion:1,operation:'audio.speech',text:{$input:'script'},output:'audio'},
|
|
7
7
|
tests:[{name:'Read a short welcome',input:{script:{schemaVersion:1,text:'Welcome to the quiet forest.'}},expectations:{audio:{kind:'audio',format:'wav',minDuration:0.1,maxDuration:30,minBytes:46,maxBytes:1500000}}}],
|
|
8
8
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
2
2
|
export const numberedTakes={
|
|
3
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.numbered-takes',version:'1.0.0',name:'Numbered takes',description:'Label successive creative takes with an independent counter for each production placement.',kind:'task',runtime:'javascript',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{text:{type:'text',primary:true,semantic:'draft_text',required:true,description:'Creative take text to label with the next number.'}},outputs:{text:{type:'text',primary:true,semantic:'numbered_take',description:'The supplied take prefixed with its saved sequence number.'}},permissions:{project:['state.step']},state:{scope:'step',version:1,initial:{count:0}},ui:[{port:'text',control:'text'}]},
|
|
3
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.numbered-takes',action:{label:'Number takes',running:'Numbering takes…'},version:'1.0.0',name:'Numbered takes',description:'Label successive creative takes with an independent counter for each production placement.',kind:'task',runtime:'javascript',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{text:{type:'text',primary:true,semantic:'draft_text',required:true,description:'Creative take text to label with the next number.'}},outputs:{text:{type:'text',primary:true,semantic:'numbered_take',description:'The supplied take prefixed with its saved sequence number.'}},permissions:{project:['state.step']},state:{scope:'step',version:1,initial:{count:0}},ui:[{port:'text',control:'text'}]},
|
|
4
4
|
code:'state.count += 1; return {text: "Take " + state.count + ": " + input.text.trim()};',
|
|
5
5
|
tests:[{name:'First take',input:{text:' Opening scene '},expected:{text:'Take 1: Opening scene'},expectedState:{version:1,value:{count:1}}},{name:'Continues saved numbering',input:{text:'Alternate ending'},state:{version:1,value:{count:4}},expected:{text:'Take 5: Alternate ending'},expectedState:{version:1,value:{count:5}}}]
|
|
6
6
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
2
2
|
|
|
3
3
|
export const timelinePolish={
|
|
4
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.timeline-polish',version:'1.0.0',name:'Timeline polish',description:'Turn a timeline and editing direction into a bounded proposal that remains reviewable before Apply.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{timeline:{type:'timeline',context:'timeline',primary:true},direction:{type:'text',default:'Polish this cut conservatively.'}},outputs:{edit:{type:'timeline-edit',primary:true}},permissions:{project:['context.timeline.read','timeline.propose'],capabilities:['timeline.propose'],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'direction'}]},
|
|
4
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.timeline-polish',action:{label:'Suggest edit',running:'Suggesting edit…'},version:'1.0.0',name:'Timeline polish',description:'Turn a timeline and editing direction into a bounded proposal that remains reviewable before Apply.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{timeline:{type:'timeline',context:'timeline',primary:true},direction:{type:'text',default:'Polish this cut conservatively.'}},outputs:{edit:{type:'timeline-edit',primary:true}},permissions:{project:['context.timeline.read','timeline.propose'],capabilities:['timeline.propose'],network:[],filesystem:[],secrets:[]},ui:[{control:'text',port:'direction'}]},
|
|
5
5
|
capability:{schemaVersion:1,operation:'timeline.propose',context:{timeline:{$input:'timeline'},direction:{$input:'direction'}},instruction:'Use only supported timeline-edit commands, exact before snapshots, and the current activeTimelineId. Preserve media and locked items. Return strict JSON only.',maxTokens:2000,output:'edit'},
|
|
6
6
|
tests:[{name:'one restrained edit',input:{timeline:{schemaVersion:1,activeTimelineId:'timeline-1',tracks:[{id:'video'}],clips:[{id:'clip-1',trackId:'video',start:0,duration:4}],audioItems:[],captions:[]},direction:'Add a soft transition.'},expectations:{edit:{kind:'timeline-edit',minCommands:1,maxCommands:10}}}],
|
|
7
7
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
2
2
|
|
|
3
3
|
export const audioTranscription={
|
|
4
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.transcribe-audio',version:'1.0.0',name:'Transcribe audio',description:'Turn one persistent audio file into reviewed text with word timings.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{audio:{type:'audio',primary:true},language:{type:'text',default:'auto'}},outputs:{transcript:{type:'transcript',primary:true}},permissions:{project:[],capabilities:['audio.transcribe'],network:[],filesystem:[],secrets:[]},ui:[{control:'asset',port:'audio'},{control:'select',port:'language',options:['auto','en','es','fr','de','it','pt','ja','ko','zh']}]},
|
|
4
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.transcribe-audio',action:{label:'Transcribe audio',running:'Transcribing…'},version:'1.0.0',name:'Transcribe audio',description:'Turn one persistent audio file into reviewed text with word timings.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{audio:{type:'audio',primary:true},language:{type:'text',default:'auto'}},outputs:{transcript:{type:'transcript',primary:true}},permissions:{project:[],capabilities:['audio.transcribe'],network:[],filesystem:[],secrets:[]},ui:[{control:'asset',port:'audio'},{control:'select',port:'language',options:['auto','en','es','fr','de','it','pt','ja','ko','zh']}]},
|
|
5
5
|
capability:{schemaVersion:1,operation:'audio.transcribe',audio:{$input:'audio'},language:{$input:'language'},output:'transcript'},
|
|
6
6
|
tests:[{name:'timed speech',input:{audio:{kind:'audio',assetId:'audio-1',versionId:'version-1',url:'/api/media/example/audio.mp3',mimeType:'audio/mpeg'},language:'en'},expectations:{transcript:{kind:'transcript',minWords:1,maxWords:1000}}}]
|
|
7
7
|
};
|
|
@@ -5,7 +5,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
5
5
|
// credits per call, total credits, expiry), every new video saved to the
|
|
6
6
|
// project is transcribed unattended into timed captions.
|
|
7
7
|
export const autoCaptions={
|
|
8
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.auto-captions',version:'1.0.0',name:'Auto-caption new uploads',description:'Transcribe each new video into timed captions, automatically within the budget the project owner approves.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
8
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.auto-captions',action:{label:'Make captions',running:'Making captions…'},version:'1.0.0',name:'Auto-caption new uploads',description:'Transcribe each new video into timed captions, automatically within the budget the project owner approves.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
9
9
|
inputs:{video:{type:'video',primary:true,required:true,semantic:'source_video',description:'The new video to caption.'},language:{type:'text',default:'auto',required:false,semantic:'language_code',description:'auto, or a two-letter language code such as en.'}},
|
|
10
10
|
outputs:{captions:{type:'transcript',primary:true,semantic:'timed_captions',description:'The spoken words with their timings.'}},
|
|
11
11
|
permissions:{project:[],capabilities:['audio.transcribe'],network:[],filesystem:[],secrets:[]},
|
|
@@ -19,7 +19,7 @@ export const autoCaptions={
|
|
|
19
19
|
// turns it into a short production note. It runs in StillMade's sandbox with
|
|
20
20
|
// no network and no credits.
|
|
21
21
|
export const orderNote={
|
|
22
|
-
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.order-note',version:'1.0.0',name:'Order to production note',description:'Turn an order a shop sends by webhook into a short production note.',kind:'task',runtime:'javascript',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
22
|
+
manifest:{schemaVersion:1,sdkVersion:'0.1.0',id:'example.order-note',action:{label:'Write order note',running:'Writing order note…'},version:'1.0.0',name:'Order to production note',description:'Turn an order a shop sends by webhook into a short production note.',kind:'task',runtime:'javascript',license:'MIT',provenance:EXAMPLE_PROVENANCE,
|
|
23
23
|
inputs:{event:{type:'object',semantic:'workflow_event',schema:{id:'string',kind:'string',occurredAt:'string',preview:'boolean',data:'object'},sources:['project'],required:true,description:'The webhook event StillMade received.'}},
|
|
24
24
|
outputs:{note:{type:'text',primary:true,semantic:'production_note',description:'A one-line note for the team.'}},
|
|
25
25
|
permissions:{project:[],network:[],filesystem:[],secrets:[]},
|
|
@@ -2,7 +2,7 @@ import {EXAMPLE_PROVENANCE} from './example-license.js';
|
|
|
2
2
|
// Declarative request; the host chooses provider, model, duration, payment and
|
|
3
3
|
// storage. This fixture is executed only after explicit host approval.
|
|
4
4
|
export const videoGenerate={
|
|
5
|
-
manifest:{schemaVersion:1,id:'example.generate-video',name:'Generate video',version:'1.0.0',sdkVersion:'0.1.0',description:'Turn a production prompt into a short video using an approved host provider.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{prompt:{type:'text',primary:true,required:true}},outputs:{video:{type:'video',primary:true}},permissions:{project:[],capabilities:['video.generate'],network:[],filesystem:[],secrets:[]},ui:[{port:'prompt',control:'text'}]},
|
|
5
|
+
manifest:{schemaVersion:1,id:'example.generate-video',action:{label:'Generate video',running:'Generating video…'},name:'Generate video',version:'1.0.0',sdkVersion:'0.1.0',description:'Turn a production prompt into a short video using an approved host provider.',kind:'task',runtime:'capability',entry:'src/capability.json',license:'MIT',provenance:EXAMPLE_PROVENANCE,inputs:{prompt:{type:'text',primary:true,required:true}},outputs:{video:{type:'video',primary:true}},permissions:{project:[],capabilities:['video.generate'],network:[],filesystem:[],secrets:[]},ui:[{port:'prompt',control:'text'}]},
|
|
6
6
|
capability:{schemaVersion:1,operation:'video.generate',prompt:{$input:'prompt'},output:'video'},
|
|
7
7
|
tests:[{name:'Produces a bounded MP4 sample',input:{prompt:'A paper boat floating across a calm blue pond. Gentle camera movement.'},expectations:{video:{kind:'video',format:'mp4',minDuration:1,maxDuration:60,minBytes:32,maxBytes:67108864,minWidth:1,maxWidth:3840,minHeight:1,maxHeight:3840}}}],
|
|
8
8
|
};
|