stillmade-sdk 0.1.36 → 0.1.38
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/AGENTS.md +8 -8
- package/IMAGE_DESCRIPTION.md +2 -0
- package/MODULES.md +68 -10
- package/NATIVE_MCP.md +18 -7
- package/PASTE_TO_LLM.md +121 -50
- package/PORTABLE.md +7 -3
- package/README.md +19 -6
- package/REMIX.md +6 -0
- package/SDK.md +226 -103
- package/TRANSCRIPTION.md +4 -2
- package/UI_AESTHETIC.md +2 -0
- package/api-index.json +1 -1
- package/blocks/narration/package.js +1 -1
- package/blocks/narration/versions/1.0.1/package.js +221 -0
- package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +769 -0
- package/blocks/native/sm.board-editor/versions/2.2.11/package.js +551 -0
- package/blocks/native/sm.describe-media/versions/2.1.0/package.js +140 -0
- package/blocks/native/sm.panel-strip/versions/2.4.2/package.js +1316 -0
- package/blocks/native/sm.photo-notes/versions/1.1.0/package.js +170 -0
- package/blocks/native/sm.pipeline/versions/2.2.6/package.js +1095 -0
- package/blocks/screen-recorder/package.js +1 -1
- package/blocks/screen-recorder/versions/1.0.1/package.js +223 -0
- package/package.json +1 -1
- package/packages/block-cli/cli.js +91 -35
- package/packages/block-cli/dev-host/assets/host.js +35 -32
- package/packages/block-cli/dev-host/assets/index.css +1 -1
- package/packages/block-cli/dev-server.js +4 -0
- package/packages/block-cli/ready-gate.js +19 -4
- package/packages/block-cli/templates.js +33 -21
- package/packages/block-platform/block-readiness.js +10 -1
- package/packages/block-platform/catalog.js +14 -3
- package/packages/block-platform/chat-packages.js +6 -0
- package/packages/block-platform/flow-inspection.js +3 -1
- package/packages/block-platform/project-target.js +57 -0
- package/packages/block-platform/project-type-composer.js +4 -3
- package/packages/block-platform/project-types.js +5 -2
- package/packages/block-sdk/admission.js +4 -1
- package/packages/block-sdk/block-tier.js +23 -0
- package/packages/block-sdk/builder-contract.js +12 -10
- package/packages/block-sdk/capabilities.js +23 -10
- package/packages/block-sdk/collaboration-admission.js +3 -0
- package/packages/block-sdk/contracts.js +13 -1
- package/packages/block-sdk/example-media-data.js +16 -0
- package/packages/block-sdk/example-media.js +42 -0
- package/packages/block-sdk/first-party-identities.js +8 -0
- package/packages/block-sdk/first-party-packages.js +11 -1
- package/packages/block-sdk/index.d.ts +6 -2
- package/packages/block-sdk/local-admission.js +6 -5
- package/packages/block-sdk/media-transform.js +59 -3
- package/packages/block-sdk/mock-host.js +23 -2
- package/packages/block-sdk/mocks.js +4 -1
- package/packages/block-sdk/module-hosted.js +12 -2
- package/packages/block-sdk/port-types.d.ts +6 -0
- package/packages/block-sdk/port-types.js +32 -10
- package/packages/block-sdk/shared-agent-controls.js +25 -2
- package/packages/block-sdk/shared-controls.js +17 -3
- package/packages/block-sdk/testing.js +46 -3
- package/packages/block-sdk/view-design.js +67 -0
- package/packages/block-sdk/view-renderer.js +50 -8
- package/packages/design-system/block-kit.css +6 -4
package/PASTE_TO_LLM.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
|
|
4
4
|
|
|
5
|
-
## Block builder contract (stillmade.block-builder@1.
|
|
5
|
+
## Block builder contract (stillmade.block-builder@1.11.0)
|
|
6
6
|
|
|
7
|
-
Contract `stillmade.block-builder@1.
|
|
7
|
+
Contract `stillmade.block-builder@1.11.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
|
|
8
8
|
|
|
9
9
|
### Source of truth
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ Treat the current SDK (`stillmade.block.json` manifest, `packages/block-sdk` con
|
|
|
12
12
|
|
|
13
13
|
### Reuse first
|
|
14
14
|
|
|
15
|
-
Before building, check what
|
|
15
|
+
Before building, check what exists (`find_existing_blocks` over MCP, or the Marketplace and your library) by the goal's inputs, results and operations. Only a `likely` or `verified-contract` match (a Block or a chain of two) does the job: use it. `partial` is a close Block to look at: remix it (license, notices and credit stay attached) or build only the missing step. Never republish another creator's source without remix lineage: public copies are refused. Build new when nothing does the job or the person wants their own, and name similar Blocks you considered.
|
|
16
16
|
|
|
17
17
|
### Three sizes of Block
|
|
18
18
|
|
|
@@ -26,11 +26,11 @@ Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`,
|
|
|
26
26
|
|
|
27
27
|
A Step is one job: the work between two moments where a person or the outside world must see the result. Start a new Step only at one of three points; everything else is a part of the neighboring Step.
|
|
28
28
|
|
|
29
|
-
1. **A person works with the result**: reads, edits, picks or approves it (a workspace Block such as a script, a Canvas or an Editor, or an approval).
|
|
29
|
+
1. **A person works with the result**: reads, edits, picks or approves it (a workspace Block such as a script, a Canvas or an Editor, or an approval). So Bible, Canvas and Editor are separate Steps after Chat.
|
|
30
30
|
2. **Finding new things turns into handling each one**: the trigger (a schedule or an app event), the app read, its filter and "only new" are the first job; what is done with each item is the next job. A trigger is a setting of the first job, never a Step of its own.
|
|
31
31
|
3. **The result leaves StillMade**: it is sent, posted, published, paid or written into another app. Consecutive writes to the same app, with the glue that shapes data for them, are one job.
|
|
32
32
|
|
|
33
|
-
In a production the Steps are the workspaces a person opens: work feeding the next workspace is its part, else it joins the one before
|
|
33
|
+
In a production the Steps are the workspaces a person opens: work feeding the next workspace is its part, else it joins the one before. A Block with its own interface (a workspace, editor or picker) is always its own Step; two such Steps are never one. A video ends in the Editor (1.0), which has captions and export built in, so Export video is never a Step beside it.
|
|
34
34
|
|
|
35
35
|
A part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of the three reasons makes it separate; if none applies, make it a part of a neighbor. A Step nobody would ever open must not exist on its own. Prefer an app task's own filter (a Gmail query such as `has:attachment filename:png`) to an AI check, and use AI decide only for judgement.
|
|
36
36
|
|
|
@@ -44,7 +44,7 @@ Translate the request into an explicit capability contract before implementation
|
|
|
44
44
|
|
|
45
45
|
### Blocks compose
|
|
46
46
|
|
|
47
|
-
A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `
|
|
47
|
+
A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), `required`, a short `description`, and a `semantic` only where it narrows what fits. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
|
|
48
48
|
|
|
49
49
|
### Feature map
|
|
50
50
|
|
|
@@ -64,7 +64,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
64
64
|
|
|
65
65
|
### Interface
|
|
66
66
|
|
|
67
|
-
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
|
|
67
|
+
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 (Write, Generate; never "Run") 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. 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.
|
|
68
68
|
|
|
69
69
|
### Shared editing
|
|
70
70
|
|
|
@@ -158,7 +158,7 @@ Every finished Block must support automatic remixing through the standard SDK co
|
|
|
158
158
|
|
|
159
159
|
### Package format
|
|
160
160
|
|
|
161
|
-
A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task",runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}.
|
|
161
|
+
A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task",runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}. Port types: text, number, integer, boolean, json, object, image, video, audio, asset, script, transcript, brief, bible, shot-plan, panel-document, board-document, scene-document, timeline, timeline-edit, file-bundle, workspace-edit, timeline-range, research, character, location, style, pipeline, approval, scene, shot, mask, depth, pose, metadata, project-context, file, document, table, url, date, color. A value is exactly: text: a string; number: a number; integer: a whole number; boolean: true or false; json: any JSON value; object: a plain JSON object; image: {kind: 'image'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; width?: number; height?: number}; video: {kind: 'video'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; width?: number; height?: number; duration?: number}; audio: {kind: 'audio'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; duration?: number; sampleRate?: number; channels?: number}; asset: {kind: 'image' | 'video' | 'audio' | 'asset'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string}; script: {schemaVersion: 1; text: string; [key: string]: unknown}; transcript: {schemaVersion: 1; text: string; words: Array<{text: string; start: number; end: number}>}; file: {kind: 'file'; assetId: string; versionId: string; mimeType: 'application/pdf' | 'text/plain' | 'text/csv' | 'text/markdown' | 'application/json' | 'application/zip'; url?: string; name?: string; bytes?: number}; document: {schemaVersion: 1; format: 'markdown' | 'plain'; text: string; title?: string}; table: {schemaVersion?: 1; columns: string[]; rows: Array<Record<string, string | number | boolean | null>>}; url: an absolute http or https address string; date: an ISO 8601 date string ('2026-10-09' or '2026-10-09T14:30'); color: a hex color string ('#ff8800' or '#ff8800cc'). A file value is a saved file: one the person gives the Block (a module Block reads its bytes with stillmade.files.read) or what stillmade.files.write returns in a module Block; an image may also be decoded RGBA pixels {width, height, data} in recipe and JavaScript Blocks, which hold only 4 MiB of JSON (a picture of about 300 by 300 pixels), so a Block that joins photos into one picture (side by side, a grid, a contact sheet, a poster) is a module Block calling media.transform image.compose. Every other type is {schemaVersion: 1; [key: string]: unknown} with the fields its own section gives. A type ending in [] is a plain array of those values (audio[] is a list of audio references, never objects that wrap them). Include test {name,input,expected} where expected is output bindings.
|
|
162
162
|
|
|
163
163
|
### Feature map
|
|
164
164
|
|
|
@@ -166,7 +166,7 @@ Every Block lists its separate tools in manifest.features, one entry per tool or
|
|
|
166
166
|
|
|
167
167
|
### Port meanings
|
|
168
168
|
|
|
169
|
-
For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs
|
|
169
|
+
For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs required:boolean, a short plain-language description and a semantic (import refuses an input without one, and gate checks it). An input accepts values declared with its meaning or a narrower one: an input for any photo a Step hands on uses source_image, as StillMade's Image editor does (generated_image and processed_image are kinds of source_image), and a photo editor's result is processed_image. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters, and always on a json or object output (import refuses one without it): for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. A value of a structured type (transcript, script, brief, shot-plan and the like) is an object with schemaVersion:1, in code and fixtures; a transcript as the Transcription Block hands it on is {schemaVersion:1,text:"...",words:[{text:"Today",start:0,end:0.4}]}, times in seconds. Declare sources only when a requirement restricts where its answer may come from: upstream, project, adapter, chat, default, user or external. Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
|
|
170
170
|
Custom inheritance is persisted in manifest.semantics, an array of {name,description?,parents?}. Use namespaced custom names, at most 64 definitions per Block, eight parents per definition, and 500 description characters. Never redefine unnamespaced standard meanings, create cycles, or combine conflicting parent sets. Preserve effective inherited meanings of all existing base inputs, outputs and accepts declarations; adding or changing a parent is a contract change even when port labels stay identical. New independent ports may declare new meanings.
|
|
171
171
|
In isolated computation code, StillMade.emit(outputName,value) collects declared final outputs without a return object; emit each name once and never combine it with a returned output object or job.pending result. StillMade.resolveRequirement(inputName) reads the fixed host-validated declared input snapshot as {resolved:true,requirement,value}, or {resolved:false,requirement} for an absent optional input. StillMade.getProjectContext() returns only context fields explicitly bound in manifest.inputs with their matching context.FIELD.read permissions; getProjectContext(field) reads one declared field and rejects undeclared fields. These snapshot readers do not run the resolver again, request Chat/user answers, read extra project data, expose credentials, write projects or grant permissions. StillMade.validate(value,schema) checks JSON values against SDK semantic schemas and returns a boolean; it does not replace host output validation, prove semantic meaning or authorize execution. These methods are not available in view.javascript.
|
|
172
172
|
|
|
@@ -179,14 +179,14 @@ For hosted text generation use {manifest,capability,tests}, runtime:"capability"
|
|
|
179
179
|
For audio transcription, use runtime:"capability", operation:"audio.transcribe" and permissions.capabilities:["audio.transcribe"]. Declare one primary audio input with an asset control, one text language input defaulting to "auto", and one primary transcript output. Bind the two inputs through capability.audio and capability.language. Provider, model, payment and credentials belong to the host. Fixtures use transcript word-count bounds, never invented provider text. Shipped example: {"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":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"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"]}]},"capability":{"schemaVersion":1,"operation":"audio.transcribe","audio":{"$input":"audio"},"language":{"$input":"language"},"output":"transcript"},"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}}}]}.
|
|
180
180
|
For narration or speech synthesis, use runtime:"capability", operation:"audio.speech" and permissions.capabilities:["audio.speech"]. One required primary text or script input feeds capability.text via {$input:"portName"}; a script value contains schemaVersion:1 and text. One primary audio output is named by capability.output. Text must be at most 3800 characters. The host owns the voice, model, provider, billing and BYOK. Do not put those choices in package code. Tests use audio expectations (WAV, duration and byte bounds), never invented exact audio. Shipped example: {"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":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"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":[]},"capability":{"schemaVersion":1,"operation":"audio.speech","text":{"$input":"script"},"output":"audio"},"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}}}]}.
|
|
181
181
|
For image generation, use runtime:"capability", operation:"image.generate" and permissions.capabilities:["image.generate"]. One required primary text input feeds capability.prompt via {$input:"portName"}; one primary image output is named by capability.output. Prompt limit 32000 characters. The supported output is PNG 1024x1024, at most 12000000 bytes. Tests use image expectations, never fabricated generated URLs or pixels. Shipped example: {"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":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"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"}]},"capability":{"schemaVersion":1,"operation":"image.generate","prompt":{"$input":"prompt"},"output":"image"},"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}}}]}.
|
|
182
|
-
For image analysis, use runtime:"capability", operation:"
|
|
182
|
+
For image or video analysis (what a photo shows, which photos show different angles, the words on a menu or sign), use runtime:"capability", operation:"media.analyze" and permissions.capabilities:["media.analyze"]: StillMade credits pay for it, while image.describe runs only on the person's own OpenRouter key, so never use it. Declare one primary image input, one image[] input of one to four images, or one video input (with optional start and end number inputs, bound through capability.range:{start:{"$input":"start"},end:{"$input":"end"}}), and one primary text or json output. capability:{schemaVersion:1,operation:"media.analyze",media:{"$input":"photo"},question:"what to look for, at most 2,000 characters",output:"notes"}; a text output gets each picture's summary, a json output {schemaVersion:1,items}. For a quick rating of photos (sorting or picking), add detail:"check": images only, one short answer each, one credit per call of up to four photos instead of four credits per picture. The host loads only owned media and controls provider, model, payment and approval. Tests give saved references and expect json that includes summary, or text length bounds, never invented live answers. Shipped example: {"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":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"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)."}},"outputs":{"report":{"type":"json","primary":true,"semantic":"media_findings","description":"Summary, timeline, on-screen text and issues found."}},"permissions":{"project":[],"capabilities":["media.analyze"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"asset","port":"clip"},{"control":"number","port":"start"},{"control":"number","port":"end"}]},"capability":{"schemaVersion":1,"operation":"media.analyze","media":{"$input":"clip"},"question":"Is the product logo clearly visible, and when? List any on-screen text and visible defects.","range":{"start":{"$input":"start"},"end":{"$input":"end"}},"output":"report"},"tests":[{"name":"A saved clip","input":{"clip":{"kind":"video","assetId":"clip-1","versionId":"v1","url":"/api/media/owner/clip.mp4"}},"expectations":{"report":{"kind":"json","includes":["summary"]}}}]}.
|
|
183
183
|
For AI-directed editing, use runtime:"capability", operation:"timeline.propose" and permissions.capabilities:["timeline.propose"]. Declare one current timeline input, up to three additional typed context inputs, and one timeline-edit output. Bind every input exactly once through capability.context; declare context.timeline.read and timeline.propose. StillMade controls the provider, strict JSON validation, stale-before checks, preview and Apply. Tests declare timeline-edit command bounds, never fabricated provider results. Shipped example: {"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":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"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"}]},"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"},"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}}}]}.
|
|
184
184
|
For video generation use runtime:"capability", operation:"video.generate", permissions.capabilities:["video.generate"], one primary text input and one primary video output. capability.prompt references the input and capability.output names the output. Prompt limit 32000. Tests declare MP4 duration, dimensions and byte bounds; never fabricate video URLs. The enabled host selects its supported model, duration, resolution, payment and approved quote; packages cannot choose provider settings or dispatch jobs. Shipped example: {"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":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"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"}]},"capability":{"schemaVersion":1,"operation":"video.generate","prompt":{"$input":"prompt"},"output":"video"},"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}}}]}.
|
|
185
185
|
A capability package performs exactly one supported operation. A job that needs several (transcribe a recording, then find what is said in it; write words, then voice them), or code between two operations, is one module Block that calls each with stillmade.hosted.run (Module Blocks); never split one job into part Blocks, and never invent a combined capability runtime. Hosted image, speech and video source can be statically validated while authoring, but real fixture/sample generation requires the user's subsequent model/payment selection and quote confirmation. Do not claim those calls ran. Deterministic image transformations should remain sandboxed recipe or JavaScript code when supported; do not generate a new image as a substitute for processing an existing one.
|
|
186
186
|
|
|
187
187
|
### Module Blocks
|
|
188
188
|
|
|
189
|
-
A job that needs several hosted operations, or code around one (reading a transcript's words and times, shaping a list), is a module Block: {manifest,moduleSource,tests}, runtime:"module", moduleSource:{"main.js":"<the code>"}. StillMade names main.js by its digest and adds module; give no other files. main.js is plain JavaScript that imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or WebCodecs): export default async function run(input, stillmade) returns the declared outputs. It calls each hosted operation with await stillmade.hosted.run(operation, request), listed in manifest.permissions.capabilities: text.generate, image.generate, video.generate, audio.speech, audio.music, audio.sfx, audio.transcribe, image.describe, media.analyze, web.fetch, web.research. Requests, and what each call returns (a test's mocks record exactly that value): text.generate {prompt, system, maxTokens} returns the text as a string, and with schema (a JSON schema object) returns that object, so ask for JSON with schema; audio.transcribe {audio: the input's saved reference, language:"auto"} returns {schemaVersion:1,text,words:[{text,start,end}]}; image.generate {prompt} and audio.speech {text} return a saved image or audio reference; image.describe {image, instruction} and web.fetch {url, instruction} return a string; media.analyze {media, question} returns {items:[...]}; web.research {topic} returns {summary,sources}. A call that fails throws: let it fail the run rather than catching it and returning an empty result. Media work on the person's device (cut, join or resize video, decode or extract audio, thumbnails) is await stillmade.media.transform(request); exporting is too: {operation:"timeline.render",kind:"video" or "audio",aspect:"9:16",clips:[{source,start,duration,from,transition:{type:"Fade"},look:"Filmic"}],audio:[{source,start,duration}],captions:[{text,start,end,words:[{text,start,end}],highlight:"#ffd400"}]} renders the Block's own timeline with the Editor's transitions, looks and word-by-word captions under the person's export plan, so never ask for a separate export, captions or color Block; stillmade.log and stillmade.progress(fraction, label) report. One press of the Step's button runs every call at the price shown beside it. A module Block runs in the person's browser while StillMade is open, never on a schedule or event. Tests are {name,input,expected,mocks}: mocks records what each call answers, as {hosted:{"audio.transcribe":{schemaVersion:1,text:"...",words:[...]},"text.generate":{...}},transforms:{...}} (a list answers successive calls in order), and expected is the exact output main.js returns for those answers. StillMade runs every test before anyone sees the Block, without spending credits. With the SDK instead, the same code is module/main.js in a Block folder and the same tests are tests/fixtures.json; stillmade-block pack writes the file to import (MODULES.md).
|
|
189
|
+
A job that needs several hosted operations, or code around one (reading a transcript's words and times, shaping a list), or media work on the person's device even with no hosted operation (rendering clips and photos into one video, joining pictures into one), is a module Block: {manifest,moduleSource,tests}, runtime:"module", moduleSource:{"main.js":"<the code>"}. StillMade names main.js by its digest and adds module. StillMade's in-app builder takes one file: give no other files, and main.js is plain JavaScript that imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or WebCodecs). That single-file rule is the in-app builder's; a module Block built with the SDK may use timers, WebCodecs, bundled npm packages and .wasm files as MODULES.md describes. main.js: export default async function run(input, stillmade) returns the declared outputs. It calls each hosted operation with await stillmade.hosted.run(operation, request), listed in manifest.permissions.capabilities: text.generate, image.generate, video.generate, audio.speech, audio.music, audio.sfx, audio.transcribe, image.describe, media.analyze, web.fetch, web.research. Requests, and what each call returns (a test's mocks record exactly that value): text.generate {prompt, system, maxTokens} returns the text as a string, and with schema (a JSON schema object) returns that object, so ask for JSON with schema; one text.generate prompt holds at most 32,000 characters, so a longer file or transcript is read in parts (one call per part of about 25,000 characters, then one call that joins what they found), never cut off, and when the result is the whole text again (a cleaned transcript, a translation) each part's answer is kept and joined in order in main.js, since one answer holds at most 4,096 tokens; audio.transcribe {audio: the input's saved reference, language:"auto"} returns {schemaVersion:1,text,words:[{text,start,end}]}; image.generate {prompt, references?: one to eight saved images the new picture keeps, such as the product, person or place in the person's photos} and audio.speech {text} return a saved image or audio reference; web.fetch {url, instruction} returns a string; media.analyze {media: one to four saved images or one saved video, question} returns {items:[{kind,summary,observations,detectedText}]}, one item per image, and is how a Block looks at pictures (picking the photos that show different angles, reading a menu off a photo): StillMade credits pay for it; image.describe {image, instruction} returns a string but runs only on the person's own OpenRouter key, so never use it; web.research {topic} returns {summary,sources}. A file the person gives the Block (a text, CSV, Markdown or JSON file on a file input) is read with await stillmade.files.read(input.<port>), which returns its bytes; turn them into text in main.js as the worked file input below does, and give the test's input file an assetId whose text mocks.files records as {files:{"<assetId>":{text:"..."}}}. A Block that reads a file the person gives it is a module Block, never a text.generate capability package. A PDF (lesson slides, a lease, a contract) is read with await stillmade.media.transform({operation:"document.text",source:input.<port>}), which returns {text, pages}, the words of each page, on the person's device and without credits (a text file reads the same way); a scanned PDF has no words to read. A call that fails throws: let it fail the run rather than catching it and returning an empty result. Media work on the person's device (cut, join or resize video, decode or extract audio, thumbnails, joining images) is await stillmade.media.transform(request). Joining saved images into one picture (two photos side by side with Before and After labels, a grid or collage, a contact sheet, a storyboard sheet, a poster with a title) is {operation:"image.compose",width,height,background:"#ffffff",layers:[{source,x,y,width,height,fit:"cover" or "contain"}],text:[{text,x,y,size,color,background,align:"left" or "center" or "right",width}]}: a canvas up to 4096 pixels a side, positions in pixels worked out in main.js, layers drawn in order and text on top (y is the top of the line, background puts a box behind it, colours are #rrggbb or, see-through, #rrggbbaa such as #00000099 for a caption box, width wraps the words onto lines no wider than it: give a caption under a frame the frame's width so it never runs into the next one); it returns the saved image, and main.js never decodes pixels itself. Exporting is a transform too: {operation:"timeline.render",kind:"video" or "audio",aspect:"9:16",clips:[{source,start,duration,from,transition:{type:"Fade"},look:"Filmic"}],audio:[{source,start,duration}],captions:[{text,start,end,words:[{text,start,end}],highlight:"#ffd400"}]} renders the Block's own timeline with the Editor's transitions, looks and word-by-word captions under the person's export plan (start is a clip's place on that timeline, so each clip starts where the one before it ends, less the transition on the clip before it, which blends that clip into the next, and from is where it begins in its own file; overlapping clips are refused), so never ask for a separate export, captions or color Block; stillmade.log and stillmade.progress(fraction, label) report. One press of the Step's button approves each hosted operation once at the price shown beside it, and media.analyze for every picture or video the Step is given, across as many calls as that takes; any further call asks the person first. A module Block runs in the person's browser while StillMade is open, never on a schedule or event. Tests are {name,input,expected,mocks} and compare outputs, so every test is a case that runs to its outputs; an input the Block refuses (it throws a plain Error with a message for the person, such as a clip with no duration) is never a test. mocks records what each call answers, as {hosted:{"audio.transcribe":{schemaVersion:1,text:"...",words:[...]},"text.generate":{...}},transforms:{...}} (a list answers successive calls in order), and expected is the exact output main.js returns for those answers: exact values of each output's type, never capability bounds such as minDuration or maxBytes. In a module test, mock each media call with its complete saved reference (kind, assetId, versionId, url, mimeType and its measured fields: duration, bytes, sampleRate, channels for audio; width, height, bytes for images) and repeat those references unchanged in expected; names or labels go in their own output. Worked outputs, each a passing test. A document output is the document object on a document port (not text): {"outputs":{"sheet":{"type":"document"}},"main.js":"export default async function run(input, stillmade) { const text = await stillmade.hosted.run('text.generate', {prompt: 'A Markdown care sheet for ' + input.plant, maxTokens: 800}); return {sheet: {schemaVersion: 1, format: 'markdown', text, title: input.plant + ' care'}}; }","test":{"name":"Fern","input":{"plant":"Fern"},"mocks":{"hosted":{"text.generate":"## Light\nBright, indirect light."}},"expected":{"sheet":{"schemaVersion":1,"format":"markdown","text":"## Light\nBright, indirect light.","title":"Fern care"}}}}. A file output is the reference await stillmade.files.write(data, {mediaType, name}) returns, handed on unchanged; mediaType is one of application/pdf, text/plain, text/csv, text/markdown, application/json, application/zip: {"outputs":{"report":{"type":"file"}},"main.js":"export default async function run(input, stillmade) { const csv = 'Room\\n' + input.rooms.join('\\n'); return {report: await stillmade.files.write(csv, {mediaType: 'text/csv', name: 'rooms.csv'})}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"expected":{"report":{"kind":"file","mimeType":"text/csv","name":"rooms.csv"}}}} (in a test, files.write answers with fields the host makes: assetId local-written-file-N, versionId, url /api/media/stillmade-mock/written-file-N.csv with the extension of its mediaType, and bytes; expected gives that reference as {kind:"file", mimeType, name} and it matches when those three match). An audio[] output is the list of references audio.speech returned, unchanged; its mock is a list answering successive calls, and the labels go in a separate text[] output: {"outputs":{"tracks":{"type":"audio[]"},"names":{"type":"text[]"}},"main.js":"export default async function run(input, stillmade) { const tracks = []; for (const room of input.rooms) tracks.push(await stillmade.hosted.run('audio.speech', {text: 'Welcome to the ' + room + '.'})); return {tracks, names: input.rooms}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"mocks":{"hosted":{"audio.speech":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}]}},"expected":{"tracks":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}],"names":["Kitchen","Hall"]}}}. A file input read as text: {"outputs":{"guide":{"type":"text"}},"main.js":"function textOf(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192)); try { return decodeURIComponent(escape(binary)); } catch (error) { return binary; } }\nexport default async function run(input, stillmade) { const notes = textOf(await stillmade.files.read(input.notes)); const guide = await stillmade.hosted.run('text.generate', {prompt: 'Rewrite these notes as numbered steps, keeping every number as written:\\n' + notes, maxTokens: 1200}); return {guide}; }","test":{"name":"Two notes","input":{"notes":{"kind":"file","assetId":"notes-1","versionId":"v1","url":"/api/media/example/notes.txt","mimeType":"text/plain","name":"notes.txt","bytes":31}},"mocks":{"files":{"notes-1":{"text":"pull the probe\nwipe the tines"}},"hosted":{"text.generate":"1. Pull the probe.\n2. Wipe the tines."}},"expected":{"guide":"1. Pull the probe.\n2. Wipe the tines."}}}. Photos joined into one image, its transform mocked with the saved image it returns: {"outputs":{"sheet":{"type":"image"}},"main.js":"export default async function run(input, stillmade) { const photos = input.photos; if (!photos.length) throw new Error('Add at least one photo.'); const columns = Math.ceil(Math.sqrt(photos.length)), rows = Math.ceil(photos.length / columns), cell = 400, gap = 20, top = 160; const width = columns * cell + (columns + 1) * gap, height = top + rows * cell + (rows + 1) * gap; const layers = photos.map((source, i) => ({source, x: gap + (i % columns) * (cell + gap), y: top + gap + Math.floor(i / columns) * (cell + gap), width: cell, height: cell, fit: 'cover'})); const sheet = await stillmade.media.transform({operation: 'image.compose', width, height, background: '#ffffff', layers, text: [{text: input.title, x: width / 2, y: 50, size: 64, align: 'center', color: '#111111'}]}); return {sheet}; }","test":{"name":"Four photos","input":{"photos":[{"kind":"image","assetId":"photo-1","versionId":"v1","url":"/api/media/example/photo-1.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-2","versionId":"v1","url":"/api/media/example/photo-2.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-3","versionId":"v1","url":"/api/media/example/photo-3.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-4","versionId":"v1","url":"/api/media/example/photo-4.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000}],"title":"Saturday games"},"mocks":{"transforms":{"image.compose":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}},"expected":{"sheet":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}}}. StillMade runs every test before anyone sees the Block, without spending credits. With the SDK instead, the same code is module/main.js in a Block folder and the same tests are tests/fixtures.json; stillmade-block pack writes the file to import (MODULES.md).
|
|
190
190
|
|
|
191
191
|
### ComfyUI workflows
|
|
192
192
|
|
|
@@ -234,7 +234,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
|
|
|
234
234
|
|
|
235
235
|
*When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
|
|
236
236
|
|
|
237
|
-
A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles, in order: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`). Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action (`sm-primary`); peers are quiet buttons or named icons. Seven or fewer choices in view. The action button names what it does ("Write", "Generate image", "Make video"), never "Run", "Run step", "Submit" or "Review cost". Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is a + tile. Never the bare browser file picker, and no size limits in view until a file is too big. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. Status sits beside the action it describes (`sm-status`), in a few words. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks with the result first and the action within reach. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
|
|
237
|
+
A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles, in order: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`). Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action (`sm-primary`); peers are quiet buttons or named icons. Seven or fewer choices in view. The action button names what it does ("Write", "Generate image", "Make video"), never "Run", "Run step", "Submit" or "Review cost". Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is a + tile. Never the bare browser file picker, and no size limits in view until a file is too big. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. Status sits beside the action it describes (`sm-status`), in a few words. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks with the result first and the action within reach. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
|
|
238
238
|
Standard custom view code (no `view.config.json` frame runtime) is checked before it runs. These rules come straight from the validator; frame views run in their own sandbox and may build the page with ordinary DOM calls such as `createElement`:
|
|
239
239
|
|
|
240
240
|
- **HTML elements:** only `div`, `section`, `article`, `header`, `footer`, `main`, `aside`, `nav`, `h1`, `h2`, `h3`, `h4`, `p`, `span`, `strong`, `em`, `i`, `small`, `br`, `hr`, `label`, `input`, `textarea`, `button`, `select`, `option`, `optgroup`, `fieldset`, `legend`, `output`, `progress`, `meter`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `figure`, `figcaption`, `img`, `video`, `audio`, `source`, `canvas`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `pre`, `code`, `details`, `summary`. No scripts, iframes, SVG, forms, comments or inline event attributes; media URLs must be `data:` or `blob:`.
|
|
@@ -289,34 +289,69 @@ Example view for a package taking and returning text: {"html":"<main class=\"sm-
|
|
|
289
289
|
When the user supplies an idea (for example, a photo editor) and this guide, build
|
|
290
290
|
an original StillMade Block from scratch. A GitHub repository is not required.
|
|
291
291
|
Create the SDK folder as the working product from the first iteration:
|
|
292
|
-
`stillmade.block.json`, `src/run.js` (
|
|
293
|
-
`tests/fixtures.json`, and optional
|
|
292
|
+
`stillmade.block.json`, `src/run.js` (`module/main.js` for a module Block, or the
|
|
293
|
+
documented declarative runtime file), `tests/fixtures.json`, and optional
|
|
294
|
+
`src/view.html`, `src/view.css`, `src/view.js`.
|
|
294
295
|
Implement the task through typed SDK inputs/outputs and the `StillMade` UI bridge.
|
|
295
296
|
The exact finished folder must import with **Choose folder** without source edits
|
|
296
297
|
or adaptation. Keep development tools and extracted SDK examples outside it.
|
|
297
298
|
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
The SDK is one download, `/block-sdk/stillmade-sdk-0.1.0.zip` on the StillMade
|
|
300
|
+
site, with the same files as `npm install --save-dev stillmade-sdk`.
|
|
301
|
+
`stillmade-block` in every StillMade guide is its command line: `npx stillmade-block`
|
|
302
|
+
after the npm install, or `node <sdk>/packages/block-cli/cli.js` where `<sdk>` is
|
|
303
|
+
the folder you extracted the download into. `stillmade-block create my-block
|
|
304
|
+
javascript` scaffolds an isolated-JavaScript Block (`create --list` shows a
|
|
300
305
|
template for every runtime and hosted operation). Edit that source,
|
|
301
306
|
then run `validate my-block` and `test my-block` with the same CLI; add `--json`
|
|
302
307
|
for machine-readable results. Blocks that use hosted operations, files, network,
|
|
303
308
|
connections or storage test offline with `test my-block --mock` (recorded
|
|
304
309
|
stand-ins, or answers recorded in `tests/mocks.json`); a stand-in pass is never a
|
|
305
|
-
live verification.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
live verification. `stillmade-block dev my-block` shows the Block's real interface
|
|
311
|
+
in a browser (see "Start a local preview early, then iterate").
|
|
312
|
+
|
|
313
|
+
Before calling the Block ready, run `stillmade-block gate my-block` on the Block's
|
|
314
|
+
own folder: it runs StillMade's readiness check, including the admission
|
|
315
|
+
requirements (explicit semantic/required declarations) that `validate` does not
|
|
316
|
+
cover. When the Block sits outside the extracted SDK folder, always name its
|
|
317
|
+
folder: `gate .` in a folder that also holds the SDK checks the SDK's examples too.
|
|
318
|
+
Then `stillmade-block pack my-block` runs the tests again and writes the one file
|
|
319
|
+
to import, named by the Block's runtime:
|
|
320
|
+
|
|
321
|
+
| Runtime | `pack my-block` writes |
|
|
322
|
+
| --- | --- |
|
|
323
|
+
| `module`, or a frame view with files in `src/view-files/` | `<id>-<version>.stillmade-module.json` |
|
|
324
|
+
| `recipe`, `javascript`, `capability`, `comfyui` | `<id>-<version>.stillmade.json` |
|
|
325
|
+
|
|
326
|
+
Deliver that file (Create, then Create new, New Block, Import Block in StillMade) or the Block folder.
|
|
327
|
+
|
|
328
|
+
A test or example that needs a real photo, recording, clip or file can name one of
|
|
329
|
+
StillMade's example files instead of carrying its own: `/api/media/example/photo-1.jpg`
|
|
330
|
+
to `photo-4.jpg` (1600×1200, `photo-2.jpg` 1200×1600), `sheet.png` (1300×1440),
|
|
331
|
+
`room.png` (1200×900), `kitchen.wav`, `hall.wav` and `click-120.wav` (2 s, 24 kHz mono),
|
|
332
|
+
`audio.mp3` (2 s), `clip.mp4` (640×360, 2 s), `notes.txt` and `sales.csv`, for example
|
|
333
|
+
`{"kind":"image","assetId":"photo-1","versionId":"v1","url":"/api/media/example/photo-1.jpg","mimeType":"image/jpeg","width":1600,"height":1200}`.
|
|
334
|
+
StillMade serves them, its preview and phone and browser check open them, and `test`
|
|
335
|
+
reads them when the test gives no mock for them. A Block whose interface needs a photo
|
|
336
|
+
before its controls work needs such an example: the phone and browser check opens the
|
|
337
|
+
interface with the first test's input and fails when it shows no usable control.
|
|
338
|
+
Never add `--original` to a module Block: original `.stillmade-block` archives take
|
|
339
|
+
only recipe, JavaScript, capability and ComfyUI source (PORTABLE.md), and a new
|
|
340
|
+
Block does not need one. An already-portable folder (with `portable.json`) uses
|
|
341
|
+
ordinary `pack`, which writes its `.stillmade-block`; keep its existing evidence aligned with its source. Include normal and edge-case fixtures and complete applicable license notices.
|
|
313
342
|
The folder remains a supported direct import; packaging is an alternate delivery
|
|
314
343
|
format, not a conversion to another implementation. Portable folders may use the
|
|
315
344
|
same split interface files or `src/view.json`, never both.
|
|
316
345
|
|
|
317
|
-
For a photo editor
|
|
318
|
-
|
|
319
|
-
`
|
|
346
|
+
For a photo editor that keeps the photo's full size, build a `module` Block with a
|
|
347
|
+
frame view: `module/main.js` reads the photo with `stillmade.files.read`, edits its
|
|
348
|
+
pixels with `OffscreenCanvas` in the module's Worker and writes the result with
|
|
349
|
+
`stillmade.files.write`. The `javascript` runtime's `src/run.js` takes images only
|
|
350
|
+
as RGBA arrays of at most one megapixel, so it suits small images only. "Full size"
|
|
351
|
+
is the size of the photo the Block receives: with the account's media setting on (its
|
|
352
|
+
default), a photo over 2048 px or 4 MiB arrives as a 2048 px working copy. Either way,
|
|
353
|
+
return typed image outputs and show images through `StillMade.previewImage`. The UI
|
|
354
|
+
calls `StillMade.run` and shares edits using the documented shared-interface contract.
|
|
320
355
|
Observe saved image outputs through `onShared`; display them with
|
|
321
356
|
`StillMade.previewImage` just as you display run results. In saved Steps, shared
|
|
322
357
|
write promises wait for an acknowledged save before the run flush barrier proceeds.
|
|
@@ -583,7 +618,7 @@ contract, explain the specific gap early and work with them on a supported optio
|
|
|
583
618
|
Never silently replace their goal with an easier example.
|
|
584
619
|
|
|
585
620
|
When the user is happy with the result or asks to finalize, prepare the importable
|
|
586
|
-
|
|
621
|
+
file `stillmade-block pack` writes from the source they just reviewed. Perform the checks permitted
|
|
587
622
|
by the environment and the user's instructions; report any unfinished or unverified
|
|
588
623
|
behavior honestly. Deliver the Block for use in StillMade, not just a preview URL
|
|
589
624
|
or instructions to rebuild it themselves. The single-artifact final-delivery rule
|
|
@@ -600,7 +635,16 @@ Do not wait until packaging, or make the user start the servers manually when yo
|
|
|
600
635
|
can do it. Open the browser/IDE preview if supported and give the user the working
|
|
601
636
|
local URL. Keep the processes running while the user tries it and asks for edits.
|
|
602
637
|
|
|
603
|
-
|
|
638
|
+
Start the preview with `stillmade-block dev my-block`, the SDK's browser preview.
|
|
639
|
+
It serves the Block's real generated controls or custom view on loopback
|
|
640
|
+
(`http://127.0.0.1:4800`; `--port` picks another), reloads on every save
|
|
641
|
+
(`--build "npm run build"` reruns a bundler first), runs the Block with an
|
|
642
|
+
example's input or the user's, and answers hosted operations, files and
|
|
643
|
+
connections with stand-ins (from `tests/mocks.json` when present), never with
|
|
644
|
+
credits. Show its URL in the coding tool's preview where it has one. With `--live`
|
|
645
|
+
it also prints a link that offers each saved version to the user's signed-in
|
|
646
|
+
StillMade account for the usual import review, where runs are real and paid steps
|
|
647
|
+
ask first. Only when `dev` cannot show what the Block needs,
|
|
604
648
|
create a small development-only preview harness around the actual Block source,
|
|
605
649
|
its declared input controls, and its outputs. Use the supplied SDK sandbox and
|
|
606
650
|
view bridge for execution and custom interfaces; do not run Block code with eval,
|
|
@@ -610,9 +654,9 @@ A browser-only Block does not need a backend merely for appearance. If one is
|
|
|
610
654
|
needed to host the SDK or serve the preview, start it together with the frontend
|
|
611
655
|
using one documented development command. Keep that command reproducible.
|
|
612
656
|
|
|
613
|
-
|
|
614
|
-
runs a sample and returns JSON
|
|
615
|
-
|
|
657
|
+
`stillmade-block run <package> <input.json>` (`preview` is the same command)
|
|
658
|
+
runs a sample once and returns JSON; it is not a browser server. Do not invent
|
|
659
|
+
other SDK commands such as `serve`. A separate preview
|
|
616
660
|
harness is development tooling; keep it outside the final portable Block package.
|
|
617
661
|
For a custom view, render the actual authored view through the SDK's existing
|
|
618
662
|
sandboxed view contract, with controls connected to real validated inputs/outputs.
|
|
@@ -636,7 +680,8 @@ feedback to the same Block source and keep the preview available. If the user
|
|
|
636
680
|
already requested a final artifact without an interactive review, proceed after
|
|
637
681
|
available checks. Otherwise, package when they say it is ready to import. Then run
|
|
638
682
|
the permitted SDK validation, sandbox fixtures, and packaging checks against that
|
|
639
|
-
same final source and return the single
|
|
683
|
+
same final source and return the single file `pack` writes for its runtime
|
|
684
|
+
(the table under "Build new Blocks directly with the SDK"). The rule to
|
|
640
685
|
return one artifact applies to final delivery, not progress messages or preview URLs.
|
|
641
686
|
|
|
642
687
|
If the environment cannot run servers or open a browser, explain the precise
|
|
@@ -667,7 +712,10 @@ Do not implement a new application shell or require manual connection wiring.
|
|
|
667
712
|
|
|
668
713
|
## Exact package shape
|
|
669
714
|
|
|
670
|
-
|
|
715
|
+
For runtime `javascript` (a standalone JavaScript Block) the package is below. A
|
|
716
|
+
module Block is a folder instead: `module/main.js` exporting
|
|
717
|
+
`async function run(input, stillmade)`, with module fixtures (see "Module Blocks"
|
|
718
|
+
and MODULES.md).
|
|
671
719
|
|
|
672
720
|
```json
|
|
673
721
|
{
|
|
@@ -721,6 +769,8 @@ any restrictions on distributing source to a team.
|
|
|
721
769
|
|
|
722
770
|
## Runtime constraints
|
|
723
771
|
|
|
772
|
+
These constraints are for runtime `javascript`; a module Block runs as an ES module
|
|
773
|
+
in a browser Worker and can use what MODULES.md lists.
|
|
724
774
|
JavaScript runs in isolated QuickJS, not Node.js or the browser's JavaScript engine.
|
|
725
775
|
Ordinary synchronous JavaScript, arrays, objects, strings, and `Math` are available.
|
|
726
776
|
No network, DOM, files, modules, shell, timers, promises, or installed dependencies.
|
|
@@ -771,8 +821,8 @@ pretending those APIs exist or returning a successful-looking placeholder.
|
|
|
771
821
|
Each input/output is a named port. Names start with a lowercase letter and contain
|
|
772
822
|
only letters, numbers and underscores. Every input of a new Block needs `semantic`,
|
|
773
823
|
`required` and `description`; every output needs `semantic` and `description`.
|
|
774
|
-
`validate` and `test` check structure and fixtures only; `
|
|
775
|
-
|
|
824
|
+
`validate` and `test` check structure and fixtures only; `ready` and `gate` run
|
|
825
|
+
full admission, which rejects a port missing these fields.
|
|
776
826
|
|
|
777
827
|
<!-- port-fields:start (generated by npm run build:sdk; do not edit) -->
|
|
778
828
|
|
|
@@ -1093,13 +1143,18 @@ the workspace cancel unfinished display requests. Use a local revision counter
|
|
|
1093
1143
|
in asynchronous UI code so a late result cannot repaint an earlier selection.
|
|
1094
1144
|
|
|
1095
1145
|
StillMade's host image controls remain available with a custom interface.
|
|
1146
|
+
On a project Step the custom interface fills the Step: the host's file inputs and "Apply to"
|
|
1147
|
+
open from the Step's inputs button, and while a required file is missing a "Choose <input>"
|
|
1148
|
+
button sits above the interface. Show your own empty state
|
|
1149
|
+
in the view ("Add a photo") and never describe where the host's controls are.
|
|
1096
1150
|
A project lets the user select a compatible authorized image or upload one;
|
|
1097
1151
|
connected/context bindings remain read-only. A standalone preview keeps uploaded
|
|
1098
1152
|
files local to that preview. Host uploads accept PNG, JPEG, WebP or GIF up to
|
|
1099
|
-
12 MB and one megapixel
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1153
|
+
12 MB and one megapixel. Above one megapixel the host makes a one-megapixel
|
|
1154
|
+
working copy for the sandbox when the account's media setting is on (its
|
|
1155
|
+
default); with the setting off, larger uploads are rejected with a request to
|
|
1156
|
+
resize a copy. Existing authorized project references can be displayed up to 16
|
|
1157
|
+
megapixels, but the selected runtime still applies its processing limit. The host validates permissions and exact media
|
|
1103
1158
|
identity before resolving bytes; no account token, native file handle, or remote
|
|
1104
1159
|
fetch capability enters the iframe.
|
|
1105
1160
|
|
|
@@ -1222,9 +1277,16 @@ inside the iframe; larger media should use the existing native Editor workspace.
|
|
|
1222
1277
|
|
|
1223
1278
|
## Tests and interconnection
|
|
1224
1279
|
|
|
1225
|
-
Include 2–6 small deterministic fixtures covering normal behavior and edge cases
|
|
1226
|
-
|
|
1227
|
-
|
|
1280
|
+
Include 2–6 small deterministic fixtures covering normal behavior and edge cases
|
|
1281
|
+
(a capability Block takes 1–3). The fields of each fixture depend on the runtime:
|
|
1282
|
+
|
|
1283
|
+
| Runtime | Each fixture |
|
|
1284
|
+
| --- | --- |
|
|
1285
|
+
| `recipe`, `javascript` | `{ "name", "input", "expected" }`; a Block with `manifest.state` adds `expectedState` and may give a starting `state` |
|
|
1286
|
+
| `module` | `{ "name", "input", "expected", "mocks" }`; `mocks` is optional and records what each host call answers (MODULES.md) |
|
|
1287
|
+
| `capability`, `comfyui` | `{ "name", "input", "expectations" }`: bounded checks of each output, never `expected` |
|
|
1288
|
+
|
|
1289
|
+
`expected` is the **output bindings object**, not one bare value and not
|
|
1228
1290
|
`{"outputs": ...}`. Fixture comparison is exact JSON, including numeric values,
|
|
1229
1291
|
array lengths and output keys. Compute expected values carefully; do not weaken a
|
|
1230
1292
|
test merely to make code pass. Test defaults by omitting a defaulted input in at
|
|
@@ -1240,6 +1302,8 @@ should have defaults when practical.
|
|
|
1240
1302
|
|
|
1241
1303
|
## Deliver, test, and import
|
|
1242
1304
|
|
|
1305
|
+
For a new Block, deliver the one file `stillmade-block pack` writes for its runtime
|
|
1306
|
+
(the table under "Build new Blocks directly with the SDK") after `gate` passes.
|
|
1243
1307
|
For repository adaptations, deliver one `.stillmade-block` archive using the
|
|
1244
1308
|
[portable packaging commands](/block-sdk/PORTABLE.md). The agent runs validation,
|
|
1245
1309
|
fixtures and packaging. The recipient uploads the file; StillMade repeats source,
|
|
@@ -1252,8 +1316,6 @@ changed source. For a remix, use a new ID and preserve
|
|
|
1252
1316
|
`manifest.provenance.remixedFrom` with the source ID, version and SHA-256 digest
|
|
1253
1317
|
when available. Never guess a digest or erase attribution.
|
|
1254
1318
|
|
|
1255
|
-
Now inspect the supplied GitHub URL and produce the portable artifact using this contract.
|
|
1256
|
-
|
|
1257
1319
|
|
|
1258
1320
|
## Optional ComfyUI implementation
|
|
1259
1321
|
|
|
@@ -1481,7 +1543,11 @@ When a Block needs npm libraries, WebAssembly (including C/C++ libraries such as
|
|
|
1481
1543
|
OpenCV), async work, timers, OffscreenCanvas or WebCodecs, use
|
|
1482
1544
|
`"runtime": "module"`. The folder holds `stillmade.block.json`, `module/main.js`
|
|
1483
1545
|
(an ES module that bundles its npm dependencies), optional `.wasm` or data files
|
|
1484
|
-
in `module/`, and `tests/fixtures.json` with `{name,input,expected}
|
|
1546
|
+
in `module/`, and `tests/fixtures.json` with `{name,input,expected,mocks}` (`mocks`
|
|
1547
|
+
optional). The rule under "SDK authoring guide", "Module Blocks" above, that main.js
|
|
1548
|
+
imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or
|
|
1549
|
+
WebCodecs), is the single-file rule of StillMade's in-app builder; a module Block
|
|
1550
|
+
built with the SDK bundles what it imports and can use everything MODULES.md lists. The entry is
|
|
1485
1551
|
`export default async function run(input, stillmade)` and returns the declared
|
|
1486
1552
|
outputs. Use `stillmade.progress(value,label)`, `stillmade.log()`,
|
|
1487
1553
|
`stillmade.signal`, `stillmade.asset(path)` for the module's own files,
|
|
@@ -1491,7 +1557,9 @@ outputs. Use `stillmade.progress(value,label)`, `stillmade.log()`,
|
|
|
1491
1557
|
to mono PCM samples, `audio.extract`, `audio.normalize`, `video.cut` with
|
|
1492
1558
|
ordered `keep` ranges, `video.concat`, `video.resize`, `image.thumbnail`; the
|
|
1493
1559
|
results are saved references), with helpers such as `silentRanges`,
|
|
1494
|
-
`keepRanges` and `integratedLoudness` from `@stillmade/block-sdk/media
|
|
1560
|
+
`keepRanges` and `integratedLoudness` from `@stillmade/block-sdk/media` (bundle
|
|
1561
|
+
`stillmade-sdk/packages/block-sdk/media-transform.js` from the npm package, or
|
|
1562
|
+
`<sdk>/packages/block-sdk/media-transform.js` from the extracted download),
|
|
1495
1563
|
`stillmade.actions.run(action)` for declared actions such as `media.generate`,
|
|
1496
1564
|
and `stillmade.hosted.run(operation,request)` for hosted operations listed in
|
|
1497
1565
|
`permissions.capabilities`. There is no network, DOM or app storage. Never
|
|
@@ -1523,7 +1591,9 @@ Markdown, JSON or ZIP: `{kind:"file",assetId,versionId,mimeType,url,name,bytes}`
|
|
|
1523
1591
|
`document`, `chart` (`x`, `y`, `chart:"bar"|"line"`), `code`, `player` or
|
|
1524
1592
|
`download`. A module Block reads an uploaded file with
|
|
1525
1593
|
`await stillmade.files.read(input.file)` and builds values with `createTable`
|
|
1526
|
-
and `createDocument` from `@stillmade/block-sdk/data
|
|
1594
|
+
and `createDocument` from `@stillmade/block-sdk/data` (bundle
|
|
1595
|
+
`stillmade-sdk/packages/block-sdk/data-values.js` from the npm package, or
|
|
1596
|
+
`<sdk>/packages/block-sdk/data-values.js` from the extracted download). To hand shots to the
|
|
1527
1597
|
Shots workspace, output `{"type":"shot[]","semantic":"production_shot_plan"}`
|
|
1528
1598
|
where each shot has `schemaVersion:1` and a unique `id`.
|
|
1529
1599
|
|
|
@@ -1574,7 +1644,8 @@ output, and
|
|
|
1574
1644
|
A video Block may add `range:{start:{$input:"start"},end:{$input:"end"}}` bound
|
|
1575
1645
|
to two optional `number` inputs (at most 12 seconds apart). A json output
|
|
1576
1646
|
receives `{schemaVersion:1,items:[{kind,summary,timeline,observations,detectedText,issues,confidence}]}`.
|
|
1577
|
-
Each analyzed item is one StillMade-credit call.
|
|
1647
|
+
Each analyzed item is one StillMade-credit call. An `image` or `image[]` Block that only rates photos may add
|
|
1648
|
+
`detail:"check"`: each photo is read smaller and gets one short `summary`, and a call of up to four photos costs one photo check.
|
|
1578
1649
|
|
|
1579
1650
|
## Optional web reading
|
|
1580
1651
|
|
|
@@ -1829,7 +1900,7 @@ bytes in this state. External hosts pass `{state:{version:1,value:{count:0}}}` t
|
|
|
1829
1900
|
|
|
1830
1901
|
|
|
1831
1902
|
Offline Project Type packaging also supports ComfyUI-only and mixed ComfyUI,
|
|
1832
|
-
hosted-capability, and local packages. Use `
|
|
1903
|
+
hosted-capability, and local packages. Use `stillmade-block pack
|
|
1833
1904
|
workflow.json workflow.stillmade.json`. This statically checks every embedded
|
|
1834
1905
|
package but executes no fixtures or providers; its report must stay unverified
|
|
1835
1906
|
until StillMade runs the import review and the user confirms installation.
|
package/PORTABLE.md
CHANGED
|
@@ -416,8 +416,9 @@ control, context binding or default. Use one `primary:true` port per direction
|
|
|
416
416
|
when the capability has a primary value; declare semantic roles only when true.
|
|
417
417
|
The host presents typed outputs and supplies supported connections automatically.
|
|
418
418
|
Inputs must be ordinary user task data, never setup scripts or manifest edits.
|
|
419
|
-
|
|
420
|
-
stateful Blocks
|
|
419
|
+
Recipe and JavaScript fixtures have `{name,input,expected}` plus the existing state
|
|
420
|
+
fields for stateful Blocks; capability and ComfyUI fixtures have
|
|
421
|
+
`{name,input,expectations}`. Expected outputs must satisfy the declared types exactly.
|
|
421
422
|
|
|
422
423
|
Archive `portable.json` (replace the illustrative values with real evidence):
|
|
423
424
|
|
|
@@ -569,7 +570,10 @@ a dependency list, instructions, SDK archive or a promise to finish later.
|
|
|
569
570
|
## Original implementations in the same archive
|
|
570
571
|
|
|
571
572
|
For code you authored without reused repository or dependency code, use the same
|
|
572
|
-
canonical archive with an explicit authorship declaration
|
|
573
|
+
canonical archive with an explicit authorship declaration. Original archives take
|
|
574
|
+
recipe, JavaScript, declarative capability and ComfyUI source. A module Block is
|
|
575
|
+
packed with `pack <folder>` alone, which writes `<id>-<version>.stillmade-module.json`
|
|
576
|
+
(the file to import); `--original` refuses it with `RUNTIME_RESTRICTED`.
|
|
573
577
|
|
|
574
578
|
```sh
|
|
575
579
|
node packages/block-cli/cli.js pack working.stillmade.json creator.my-block-1.0.0.stillmade-block --original
|