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.
Files changed (60) hide show
  1. package/AGENTS.md +8 -8
  2. package/IMAGE_DESCRIPTION.md +2 -0
  3. package/MODULES.md +68 -10
  4. package/NATIVE_MCP.md +18 -7
  5. package/PASTE_TO_LLM.md +121 -50
  6. package/PORTABLE.md +7 -3
  7. package/README.md +19 -6
  8. package/REMIX.md +6 -0
  9. package/SDK.md +226 -103
  10. package/TRANSCRIPTION.md +4 -2
  11. package/UI_AESTHETIC.md +2 -0
  12. package/api-index.json +1 -1
  13. package/blocks/narration/package.js +1 -1
  14. package/blocks/narration/versions/1.0.1/package.js +221 -0
  15. package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +769 -0
  16. package/blocks/native/sm.board-editor/versions/2.2.11/package.js +551 -0
  17. package/blocks/native/sm.describe-media/versions/2.1.0/package.js +140 -0
  18. package/blocks/native/sm.panel-strip/versions/2.4.2/package.js +1316 -0
  19. package/blocks/native/sm.photo-notes/versions/1.1.0/package.js +170 -0
  20. package/blocks/native/sm.pipeline/versions/2.2.6/package.js +1095 -0
  21. package/blocks/screen-recorder/package.js +1 -1
  22. package/blocks/screen-recorder/versions/1.0.1/package.js +223 -0
  23. package/package.json +1 -1
  24. package/packages/block-cli/cli.js +91 -35
  25. package/packages/block-cli/dev-host/assets/host.js +35 -32
  26. package/packages/block-cli/dev-host/assets/index.css +1 -1
  27. package/packages/block-cli/dev-server.js +4 -0
  28. package/packages/block-cli/ready-gate.js +19 -4
  29. package/packages/block-cli/templates.js +33 -21
  30. package/packages/block-platform/block-readiness.js +10 -1
  31. package/packages/block-platform/catalog.js +14 -3
  32. package/packages/block-platform/chat-packages.js +6 -0
  33. package/packages/block-platform/flow-inspection.js +3 -1
  34. package/packages/block-platform/project-target.js +57 -0
  35. package/packages/block-platform/project-type-composer.js +4 -3
  36. package/packages/block-platform/project-types.js +5 -2
  37. package/packages/block-sdk/admission.js +4 -1
  38. package/packages/block-sdk/block-tier.js +23 -0
  39. package/packages/block-sdk/builder-contract.js +12 -10
  40. package/packages/block-sdk/capabilities.js +23 -10
  41. package/packages/block-sdk/collaboration-admission.js +3 -0
  42. package/packages/block-sdk/contracts.js +13 -1
  43. package/packages/block-sdk/example-media-data.js +16 -0
  44. package/packages/block-sdk/example-media.js +42 -0
  45. package/packages/block-sdk/first-party-identities.js +8 -0
  46. package/packages/block-sdk/first-party-packages.js +11 -1
  47. package/packages/block-sdk/index.d.ts +6 -2
  48. package/packages/block-sdk/local-admission.js +6 -5
  49. package/packages/block-sdk/media-transform.js +59 -3
  50. package/packages/block-sdk/mock-host.js +23 -2
  51. package/packages/block-sdk/mocks.js +4 -1
  52. package/packages/block-sdk/module-hosted.js +12 -2
  53. package/packages/block-sdk/port-types.d.ts +6 -0
  54. package/packages/block-sdk/port-types.js +32 -10
  55. package/packages/block-sdk/shared-agent-controls.js +25 -2
  56. package/packages/block-sdk/shared-controls.js +17 -3
  57. package/packages/block-sdk/testing.js +46 -3
  58. package/packages/block-sdk/view-design.js +67 -0
  59. package/packages/block-sdk/view-renderer.js +50 -8
  60. 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.10.0)
5
+ ## Block builder contract (stillmade.block-builder@1.11.0)
6
6
 
7
- Contract `stillmade.block-builder@1.10.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.
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 already exists (`find_existing_blocks` over MCP, or the Marketplace and your library) by the inputs, results and operations the goal needs. Use an installed or reviewed Block that already does it; remix a close one so its license, notices and creator credit stay attached; chain existing Blocks and build only the missing step. Never republish another creator's source without remix lineage: public copies are refused. Build a new Block when nothing fits or the person asks for their own version, and say which similar Blocks you considered.
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). That is why Chat, Bible, Canvas and Editor are four Steps.
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; neighbouring workspaces with one main result type are one Step unless another part feeds the second. A video ends in the Editor (1.0), which has captions and export built in, so Export video is never a Step beside it.
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 `semantic` meaning on every port, `required` and a short `description`. 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()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
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 genuinely 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 (a video editor, a 3D configurator, a 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`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their 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.
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"]}}. Types include text,number,integer,boolean,json,image,audio,video,script,text[]. Include test {name,input,expected} where expected is output bindings.
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 semantic, required:boolean and a short plain-language description. 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.
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:"image.describe" and permissions.capabilities:["image.describe"]. Declare one primary image input (or one image[] input of one to four images to compare together) and one primary text output. capability.image references the input; capability.instruction contains bounded package-owned analysis instructions; capability.maxTokens is 1–4096; capability.output names the text output. The host loads only owned media and controls provider, model, payment and approval. Tests use bounded RGBA pixels and nondeterministic text expectations, never invented live descriptions. Shipped example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.describe-image","version":"1.0.0","name":"Describe an image","description":"Turn one owned image into concise production-ready visual notes.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"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":{"image":{"type":"image","primary":true,"semantic":"source_image","required":true,"description":"Owned image to inspect for visible production details."}},"outputs":{"description":{"type":"text","primary":true,"role":"visual_description","semantic":"visual_description","description":"Concise production notes based only on visible image details."}},"permissions":{"project":[],"capabilities":["image.describe"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"asset","port":"image"}]},"capability":{"schemaVersion":1,"operation":"image.describe","image":{"$input":"image"},"instruction":"Describe only visibly supported subject matter, composition, lighting, color, texture, camera feel and visual style. Return concise production-ready plain text.","maxTokens":700,"output":"description"},"tests":[{"name":"Blue image","input":{"image":{"width":1,"height":1,"data":[20,80,200,255]}},"expectations":{"description":{"kind":"text","minLength":20,"maxLength":32768,"includes":["blue"]}}}]}.
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` (or the documented declarative runtime file),
293
- `tests/fixtures.json`, and optional `src/view.html`, `src/view.css`, `src/view.js`.
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
- After extracting `/block-sdk/sdk-docs.zip`, `node packages/block-cli/cli.js create
299
- my-block javascript` scaffolds an isolated-JavaScript Block (`create --list` shows a
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. For an original
306
- downloadable artifact run `pack my-block my-block.stillmade-block --original`.
307
- Before delivering a plain original source folder, run `pack` with `--original`
308
- into a temporary path outside that folder: original packing checks admission requirements
309
- (including explicit semantic/required declarations) that ordinary `validate`
310
- does not fully cover. Packaging for this check does not require changing the
311
- chosen folder delivery format. An already-portable folder uses ordinary `pack`
312
- without `--original`; keep its existing evidence aligned with its source. Include normal and edge-case fixtures and complete applicable license notices.
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, implement pixel transformations in `src/run.js`, return typed
318
- image outputs, and show images through `StillMade.previewImage`. The UI calls
319
- `StillMade.run` and shares edits using the documented shared-interface contract.
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
- `.stillmade-block` from the source they just reviewed. Perform the checks permitted
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
- Use the coding tool's existing preview environment where available. Otherwise,
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
- The existing CLI command `node packages/block-cli/cli.js preview <package> <input.json>`
614
- runs a sample and returns JSON. It is not a browser server and does not launch a
615
- frontend or backend. Do not invent an SDK `serve` command. A separate preview
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 `.stillmade-block` artifact. The rule to
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
- Use a standalone JavaScript Block:
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; `pack --original` and
775
- `ready` run full admission, which rejects a port missing these fields.
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; larger uploads are rejected with a request to resize
1100
- a copy. Existing authorized project references can be displayed up to 16
1101
- megapixels, but the selected runtime still applies its processing limit. No
1102
- automatic processing resize occurs. The host validates permissions and exact media
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
- Each fixture is exactly `{ "name": "...", "input": {...}, "expected": {...} }`.
1227
- Expected is the **output bindings object**, not one bare value and not
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}`. The entry is
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`. To hand shots to the
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 `node packages/block-cli/cli.js pack
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
- All fixtures have `{name,input,expected}` plus the existing state fields for
420
- stateful Blocks. Expected outputs must satisfy the declared types exactly.
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