stillmade-sdk 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.stillmade/sdk-baseline.json +6 -8
- package/AGENTS.md +15 -10
- package/FRAME_VIEWS.md +113 -0
- package/INTERFACE_DESIGN.md +62 -22
- package/MOBILE_UI.md +4 -2
- package/MODULES.md +152 -6
- package/PASTE_TO_LLM.md +35 -25
- package/README.md +3 -2
- package/SDK.md +994 -550
- package/UI_AESTHETIC.md +22 -2
- package/blocks/generate/package.js +1 -0
- package/blocks/generate/versions/1.0.0/package.js +463 -0
- package/docs/build/audio-to-editor.md +36 -0
- package/docs/build/block-builder-contract.md +150 -0
- package/docs/build/comfyui.md +227 -0
- package/docs/build/concepts.md +40 -0
- package/docs/build/context.md +159 -0
- package/docs/build/cookbook.md +57 -0
- package/docs/build/custom-interface.md +617 -0
- package/docs/build/data-types.md +83 -0
- package/docs/build/frame-views.md +259 -0
- package/docs/build/generate-media.md +59 -0
- package/docs/build/github-applications.md +314 -0
- package/docs/build/hosted-images.md +214 -0
- package/docs/build/hosted-music.md +39 -0
- package/docs/build/hosted-speech.md +157 -0
- package/docs/build/hosted-text.md +246 -0
- package/docs/build/image-description.md +49 -0
- package/docs/build/interface-design.md +343 -0
- package/docs/build/javascript.md +63 -0
- package/docs/build/local-preview.md +61 -0
- package/docs/build/media-analysis.md +27 -0
- package/docs/build/media.md +400 -0
- package/docs/build/mobile-ui.md +106 -0
- package/docs/build/modules.md +487 -0
- package/docs/build/original-blocks.md +70 -0
- package/docs/build/outside-apis.md +128 -0
- package/docs/build/production-context-templates.md +12 -0
- package/docs/build/project-storage.md +94 -0
- package/docs/build/quickstart.md +48 -0
- package/docs/build/recipes.md +103 -0
- package/docs/build/script-handoff.md +45 -0
- package/docs/build/script-writer.md +34 -0
- package/docs/build/state.md +223 -0
- package/docs/build/testing.md +112 -0
- package/docs/build/timeline-edits.md +116 -0
- package/docs/build/timeline-proposals.md +76 -0
- package/docs/build/transcription.md +59 -0
- package/docs/build/triggers.md +59 -0
- package/docs/build/tutorial.md +52 -0
- package/docs/build/typescript.md +34 -0
- package/docs/build/video-generation.md +61 -0
- package/docs/build/web-reading.md +37 -0
- package/docs/build/with-your-ai.md +27 -0
- package/docs/changelog.md +9 -0
- package/docs/creators.md +21 -0
- package/docs/distribute/discovery.md +43 -0
- package/docs/distribute/imports.md +58 -0
- package/docs/distribute/publishing.md +29 -0
- package/docs/distribute/sdk-versions.md +36 -0
- package/docs/distribute/selling.md +14 -0
- package/docs/distribute/teams.md +60 -0
- package/docs/distribute/versioning.md +28 -0
- package/docs/examples.md +23 -0
- package/docs/gallery.md +26 -0
- package/docs/index.md +50 -0
- package/docs/native-mcp.md +307 -0
- package/docs/project-types/automation.md +43 -0
- package/docs/project-types/batches.md +50 -0
- package/docs/project-types/build.md +42 -0
- package/docs/project-types/completion.md +15 -0
- package/docs/project-types/conditions.md +21 -0
- package/docs/project-types/hosted-review.md +115 -0
- package/docs/project-types/onboarding.md +75 -0
- package/docs/project-types/running.md +309 -0
- package/docs/project-types/testing.md +72 -0
- package/docs/reference/application-artifacts.md +87 -0
- package/docs/reference/authoring-guide.md +134 -0
- package/docs/reference/browser-applications.md +267 -0
- package/docs/reference/capabilities.md +45 -0
- package/docs/reference/cli.md +71 -0
- package/docs/reference/connection-api.md +59 -0
- package/docs/reference/cooperative-jobs.md +13 -0
- package/docs/reference/desktop-capabilities.md +172 -0
- package/docs/reference/desktop-execution.md +77 -0
- package/docs/reference/errors.md +233 -0
- package/docs/reference/execution.md +800 -0
- package/docs/reference/first-party-blocks.md +40 -0
- package/docs/reference/generation-models.md +59 -0
- package/docs/reference/limits.md +306 -0
- package/docs/reference/manifest-kinds-controls.md +36 -0
- package/docs/reference/manifest.md +181 -0
- package/docs/reference/operations.md +36 -0
- package/docs/reference/package-jobs.md +35 -0
- package/docs/reference/parity.md +55 -0
- package/docs/reference/permissions.md +86 -0
- package/docs/reference/ports.md +26 -0
- package/docs/reference/types.md +268 -0
- package/docs/reference/universal-resolver.md +303 -0
- package/docs/reference/view-bridge.md +60 -0
- package/docs/reference/view-rules.md +15 -0
- package/docs/reference/workspace-sync.md +25 -0
- package/docs/sdk.md +34 -0
- package/docs/tools/block-remix.md +211 -0
- package/docs/tools/llm.md +2213 -0
- package/docs/tools/repository-adaptation.md +613 -0
- package/examples/check-clip.stillmade.json +4 -1
- package/examples/generate-video.stillmade.json +6 -2
- package/examples/research-topic.stillmade.json +5 -1
- package/examples/text-workflow.stillmade.json +3 -32
- package/examples/timeline-polish.stillmade.json +11 -3
- package/examples/transcribe-audio.stillmade.json +11 -3
- package/package.json +1 -1
- package/packages/block-cli/cli.js +13 -6
- package/packages/block-cli/dev-host/assets/host.js +76 -38
- package/packages/block-cli/dev-host/assets/index.css +1 -1
- package/packages/block-cli/format.js +1 -1
- package/packages/block-cli/templates.js +31 -13
- package/packages/block-cli/type-packages.js +119 -0
- package/packages/block-platform/block-readiness.js +5 -0
- package/packages/block-platform/canvas.js +2 -1
- package/packages/block-platform/catalog.js +27 -48
- package/packages/block-platform/native-host-inventory.js +0 -226
- package/packages/block-platform/plain-descriptions.js +1 -25
- package/packages/block-platform/project-type-starters.js +13 -53
- package/packages/block-platform/script-handoff.js +2 -4
- package/packages/block-platform/type-workspace.js +2 -0
- package/packages/block-sdk/appearance.js +3 -1
- package/packages/block-sdk/block-tier.js +1 -1
- package/packages/block-sdk/builder-contract.js +10 -8
- package/packages/block-sdk/capabilities.js +1 -1
- package/packages/block-sdk/contracts.js +6 -2
- package/packages/block-sdk/first-party-identities.js +1 -84
- package/packages/block-sdk/first-party-packages.js +3 -85
- package/packages/block-sdk/generation-catalog.js +15 -3
- package/packages/block-sdk/index.d.ts +2 -1
- package/packages/block-sdk/media-analysis-example.js +1 -1
- package/packages/block-sdk/mock-host.js +7 -1
- package/packages/block-sdk/module-host-calls.js +35 -10
- package/packages/block-sdk/module-hosted.js +15 -2
- package/packages/block-sdk/module-media-access.js +71 -0
- package/packages/block-sdk/module-node.js +1 -1
- package/packages/block-sdk/portable-media-actions.js +1 -1
- package/packages/block-sdk/runtime.js +1 -1
- package/packages/block-sdk/timeline-proposal-example.js +1 -1
- package/packages/block-sdk/transcription-example.js +1 -1
- package/packages/block-sdk/video-generation-example.js +1 -1
- package/packages/block-sdk/view-bridge.js +4 -1
- package/packages/block-sdk/view-design.js +17 -14
- package/packages/block-sdk/web-example.js +1 -1
- package/packages/design-system/block-kit.css +48 -12
- package/packages/design-system/block-kit.html +3 -2
- package/blocks/mcp-motion-planner/versions/1.1.0/package.js +0 -97
- package/blocks/motion-designer/versions/1.0.1/package.js +0 -173318
- package/blocks/motion-designer/versions/1.0.2/package.js +0 -173318
- package/blocks/motion-frame-review/versions/1.1.1/package.js +0 -465
- package/blocks/motion-frame-review/versions/1.1.2/package.snapshot.js +0 -572
- package/blocks/narration/package.js +0 -1
- package/blocks/narration/versions/1.0.0/package.js +0 -221
- package/blocks/narration/versions/1.0.1/package.js +0 -221
- package/blocks/native/sm.animated-scene/versions/2.0.0/package.js +0 -475
- package/blocks/native/sm.animated-scene/versions/2.1.0/package.js +0 -498
- package/blocks/native/sm.animated-scene/versions/2.2.0/package.js +0 -524
- package/blocks/native/sm.animated-scene/versions/2.3.0/package.js +0 -618
- package/blocks/native/sm.animated-scene/versions/2.3.1/package.js +0 -618
- package/blocks/native/sm.animated-scene/versions/2.3.2/package.js +0 -723
- package/blocks/native/sm.animated-scene/versions/2.3.3/package.js +0 -723
- package/blocks/native/sm.animated-scene/versions/2.3.4/package.js +0 -676
- package/blocks/native/sm.animated-scene/versions/2.3.5/package.js +0 -693
- package/blocks/native/sm.animated-scene/versions/2.3.6/package.js +0 -700
- package/blocks/native/sm.animated-scene/versions/2.3.7/package.js +0 -747
- package/blocks/native/sm.animated-scene/versions/2.3.8/package.js +0 -769
- package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +0 -769
- package/blocks/native/sm.animated-scene-assistant/versions/1.0.0/package.js +0 -183
- package/blocks/native/sm.blueprint/versions/2.0.0/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.0/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.1/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.2/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.3/package.js +0 -381
- package/blocks/native/sm.blueprint/versions/2.1.4/package.js +0 -381
- package/blocks/native/sm.blueprint/versions/2.1.5/package.js +0 -339
- package/blocks/native/sm.blueprint/versions/2.1.6/package.js +0 -356
- package/blocks/native/sm.blueprint/versions/2.1.7/package.js +0 -363
- package/blocks/native/sm.blueprint/versions/2.1.8/package.js +0 -410
- package/blocks/native/sm.blueprint/versions/2.1.9/package.js +0 -422
- package/blocks/native/sm.blueprint-assistant/versions/1.0.0/package.js +0 -172
- package/blocks/native/sm.chat/versions/2.0.0/package.js +0 -144
- package/blocks/native/sm.chat/versions/2.0.1/package.js +0 -97
- package/blocks/native/sm.chat/versions/2.0.2/package.js +0 -100
- package/blocks/native/sm.chat/versions/2.0.3/package.js +0 -100
- package/blocks/native/sm.continue-if/versions/1.0.0/package.js +0 -168
- package/blocks/native/sm.describe-media/versions/2.0.0/package.js +0 -153
- package/blocks/native/sm.describe-media/versions/2.0.1/package.js +0 -156
- package/blocks/native/sm.describe-media/versions/2.1.0/package.js +0 -140
- package/blocks/native/sm.image-editor/versions/2.0.0/package.js +0 -183
- package/blocks/native/sm.image-editor/versions/2.0.1/package.js +0 -190
- package/blocks/native/sm.new-items/versions/1.0.0/package.js +0 -191
- package/blocks/native/sm.pick-photos/versions/1.0.0/package.js +0 -334
- package/blocks/native/sm.quality-audit/versions/1.0.0/package.js +0 -192
- package/blocks/native/sm.quality-audit/versions/1.0.1/package.js +0 -192
- package/blocks/native/sm.quality-audit/versions/1.0.2/package.js +0 -195
- package/blocks/native/sm.research/versions/2.0.0/package.js +0 -175
- package/blocks/native/sm.research/versions/2.0.1/package.js +0 -184
- package/blocks/native/sm.research/versions/2.0.2/package.js +0 -187
- package/blocks/native/sm.script/versions/2.0.0/package.js +0 -144
- package/blocks/native/sm.script/versions/2.1.0/package.js +0 -144
- package/blocks/native/sm.script/versions/2.1.1/package.js +0 -144
- package/blocks/native/sm.script/versions/2.1.2/package.js +0 -105
- package/blocks/native/sm.script/versions/2.1.3/package.js +0 -152
- package/blocks/native/sm.script/versions/2.1.4/package.js +0 -156
- package/blocks/native/sm.script/versions/2.1.5/package.js +0 -156
- package/blocks/native/sm.shot-plan/versions/2.0.0/package.js +0 -301
- package/blocks/native/sm.shot-plan/versions/2.1.0/package.js +0 -528
- package/blocks/native/sm.shot-plan/versions/2.1.1/package.js +0 -528
- package/blocks/native/sm.shot-plan/versions/2.1.2/package.js +0 -528
- package/blocks/native/sm.shot-plan/versions/2.1.3/package.js +0 -536
- package/blocks/native/sm.shot-plan/versions/2.2.0/package.js +0 -800
- package/blocks/native/sm.shot-plan/versions/2.2.1/package.js +0 -920
- package/blocks/native/sm.shot-plan-assistant/versions/1.0.0/package.js +0 -173
- package/blocks/native/sm.stock-search/versions/2.0.0/package.js +0 -174
- package/blocks/native/sm.thumbnail/versions/2.0.0/package.js +0 -137
- package/blocks/native/sm.thumbnail/versions/2.0.1/package.js +0 -140
- package/blocks/native/sm.transcribe/versions/2.0.0/package.js +0 -163
- package/blocks/native/sm.transcribe/versions/2.0.1/package.js +0 -168
- package/blocks/native/sm.whiteboard-assistant/versions/1.0.0/package.js +0 -175
- package/blocks/native/sm.youtube/versions/2.0.0/package.js +0 -217
- package/blocks/occasion-story/package.js +0 -1
- package/blocks/occasion-story/versions/1.0.0/package.js +0 -1658
- package/blocks/occasion-story/versions/1.0.1/package.js +0 -1699
- package/blocks/occasion-story/versions/1.0.2/package.js +0 -1682
- package/blocks/photo-reel-composer/versions/1.0.0/package.js +0 -3043
- package/blocks/photo-reel-composer/versions/1.0.1/package.js +0 -3026
- package/blocks/photo-reel-composer/versions/1.0.2/package.js +0 -4149
- package/blocks/product-ad-variants/versions/1.0.0/package.js +0 -2911
- package/blocks/product-launch-director/versions/1.0.0/package.js +0 -103
- package/blocks/release-visuals/package.js +0 -1
- package/blocks/release-visuals/versions/1.0.0/package.js +0 -1492
- package/blocks/release-visuals/versions/1.0.1/package.js +0 -1517
- package/blocks/release-visuals/versions/1.0.2/package.js +0 -1500
- package/blocks/screen-recorder/package.js +0 -1
- package/blocks/screen-recorder/versions/1.0.0/package.js +0 -223
- package/blocks/screen-recorder/versions/1.0.1/package.js +0 -223
- package/blocks/short-form-finish/package.js +0 -1
- package/blocks/short-form-finish/versions/1.0.0/package.js +0 -1043
- package/blocks/short-form-finish/versions/1.0.1/package.js +0 -1043
- package/blocks/short-form-finish/versions/1.0.2/package.js +0 -1043
- package/blocks/short-form-finish/versions/1.0.3/package.js +0 -1026
- package/examples/saas-product-launch-reviewed-source.stillmade.json +0 -383
- package/examples/saas-product-launch.stillmade.json +0 -304
- package/packages/block-platform/dealer-inventory-video-project-type.js +0 -38
- package/packages/block-platform/first-revenue-project-types.js +0 -46
- package/packages/block-platform/fixture-starters.js +0 -13
- package/packages/block-platform/launch-project-source.js +0 -10
- package/packages/block-platform/launch-project.js +0 -30
- package/packages/block-platform/motion-project-types.js +0 -55
- package/packages/block-platform/motion-starters.js +0 -14
- package/packages/block-platform/product-ad-variants-project-type.js +0 -30
- package/packages/block-platform/product-launch-updated.js +0 -90
- package/packages/block-platform/revenue-review-project-types.js +0 -36
- package/packages/block-platform/revenue-review-starters.js +0 -13
- package/packages/block-platform/script-writer.js +0 -29
- package/packages/block-platform/website-source-block.js +0 -14
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# Module Blocks: JavaScript and WebAssembly
|
|
2
|
+
|
|
3
|
+
# Module Blocks: modern JavaScript and WebAssembly
|
|
4
|
+
|
|
5
|
+
A module Block (`"runtime": "module"`) is an ordinary ES module. It can use
|
|
6
|
+
everything a modern browser Worker offers: `async`/`await`, timers, typed
|
|
7
|
+
arrays, `WebAssembly`, `OffscreenCanvas`, `createImageBitmap`, WebCodecs, and
|
|
8
|
+
any npm package you bundle into it, including C and C++ libraries compiled to
|
|
9
|
+
WebAssembly (OpenCV, libvips builds, codecs, solvers), threaded WebAssembly
|
|
10
|
+
(see Threads) and live WebSocket connections to declared origins (see Live
|
|
11
|
+
connections). Use it when a recipe or the QuickJS `javascript` runtime is too
|
|
12
|
+
small for the job. (StillMade's in-app builder writes `main.js` as one file that
|
|
13
|
+
imports nothing and uses no browser APIs; that single-file rule is the builder's,
|
|
14
|
+
not the runtime's. A module Block you build with the SDK can use all of this.)
|
|
15
|
+
|
|
16
|
+
## Folder layout
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
my-block/
|
|
20
|
+
stillmade.block.json manifest with "runtime": "module"
|
|
21
|
+
module/ everything the Block runs
|
|
22
|
+
main.js the entry (bundle npm dependencies into it)
|
|
23
|
+
filters.wasm optional WebAssembly and data files
|
|
24
|
+
module.json optional: {"entry": "other.js", "threads": true}
|
|
25
|
+
tests/fixtures.json [{name, input, expected, mocks?}]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Start one with `stillmade-block create my-block module` (`module-hosted` for
|
|
29
|
+
one that calls hosted operations). `stillmade-block` is the SDK's command line:
|
|
30
|
+
`npx stillmade-block` with the npm package `stillmade-sdk`, or
|
|
31
|
+
`node <sdk>/packages/block-cli/cli.js` where `<sdk>` is the extracted
|
|
32
|
+
`/block-sdk/stillmade-sdk-0.1.0.zip`. Bundle npm
|
|
33
|
+
dependencies into `module/main.js` with any ES module bundler (esbuild,
|
|
34
|
+
rolldown, Vite). A module holds up to 64 files, 64 MiB each and 128 MiB in
|
|
35
|
+
total.
|
|
36
|
+
|
|
37
|
+
## The entry
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
export default async function run(input, stillmade) {
|
|
41
|
+
stillmade.progress(0.1, 'Loading');
|
|
42
|
+
const { instance } = await WebAssembly.instantiate(stillmade.asset('filters.wasm'));
|
|
43
|
+
const bytes = await stillmade.files.read(input.photo); // a saved image the Block received
|
|
44
|
+
// ... process ...
|
|
45
|
+
const saved = await stillmade.files.write(pngBlob, { mediaType: 'image/png', name: 'result.png' });
|
|
46
|
+
return { result: saved };
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Return an object whose keys are your declared outputs. Outputs are checked
|
|
51
|
+
against the manifest's port types.
|
|
52
|
+
|
|
53
|
+
## The `stillmade` host API
|
|
54
|
+
|
|
55
|
+
| Member | What it does |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `input` | The validated input values (the same object as the first argument). |
|
|
58
|
+
| `signal` | An `AbortSignal` that fires when the person cancels. |
|
|
59
|
+
| `progress(value, label)` | Reports progress from 0 to 1 with a short label. Each report extends the run limit. |
|
|
60
|
+
| `log(...values)` | Adds a line to the run log (up to 500 lines). |
|
|
61
|
+
| `asset(path)` | A copy of one of the module's own files, such as a `.wasm` build, as an `ArrayBuffer`. |
|
|
62
|
+
| `files.read(ref)` | The bytes, as an `ArrayBuffer`, of a saved image, video, audio, asset or file (a PDF, text, CSV, Markdown or JSON file on a `file` input) the Block received as input, or that StillMade made in this run: files it wrote, transform results and generated media. Media the person kept on their device (a `sm-media://device/...` address in a project Step) reads the same way, without leaving the device. Other files cannot be read, and transforms, hosted calls and actions accept only these references too; a hosted call or action gets a cloud copy of device media, made once per version. A reference is the file its `kind`, `assetId`, `versionId` and `url` name, so `{kind, assetId, versionId, url}` rebuilt from a `media.generate` result item (without its `key`) is the same file; any other file is refused with `MODULE_FILE_READ`, by `stillmade-block test --mock`, `ready` and `gate` as by StillMade. |
|
|
63
|
+
| `files.write(data, {mediaType, name})` | Saves PNG, JPEG, WebP, GIF, MP4, WebM, MP3, WAV, M4A or OGG media, or a PDF, TXT, CSV, Markdown, JSON or ZIP file (`application/pdf`, `text/plain`, `text/csv`, `text/markdown`, `application/json`, `application/zip`; a `{kind: 'file'}` reference), to the runner's library and returns a reference you can return as an output. At most 64 files per run. |
|
|
64
|
+
| `media.transform(request)` | On-device media work done by StillMade: decode audio to samples, extract or loudness-normalise audio, cut, join or re-encode video, and take thumbnails. Results are saved like `files.write`. See [Media processing](#media-processing). |
|
|
65
|
+
| `actions.run(action)` | Runs a declared portable action. `media.generate` opens one approval for its whole batch, naming the most it can cost in StillMade credits; `stock.search` runs directly. |
|
|
66
|
+
| `hosted.run(operation, request)` | Calls a hosted operation the manifest lists in `permissions.capabilities` (for example `text.generate`, `image.generate`, `audio.speech`, `web.research`). Each call is quoted and approved like a capability Block and paid with StillMade credits. |
|
|
67
|
+
| `net.fetch(url, {method, headers, body})` | Calls one of the origins declared in `permissions.network` through StillMade, which adds the person's own key or OAuth token. Returns a standard `Response`. See [Outside APIs](outside-apis.md). |
|
|
68
|
+
| `net.connect(url, {protocols})` | Opens a live WebSocket to the `wss://` form of a declared origin and resolves with the open socket. No key is added. See [Live connections](#live-connections). |
|
|
69
|
+
| `connections.call(appId, operationId, input)` | Calls an operation of a published adapter declared in `permissions.connections`, with the person's own account. Returns the checked JSON result. |
|
|
70
|
+
| `storage.get/set/remove/list/info(...)` | The Block's own cloud storage in the current project: JSON values up to 1 MiB under folder-like keys. Kept across runs, devices and the Block's versions. See [Project storage, reads and assets](project-storage.md). |
|
|
71
|
+
| `storage.putFile(key, data, {mediaType})` / `storage.getFile(key)` | Files up to 64 MiB in the same storage; `getFile` returns `{key, mediaType, bytes, data}` with `data` as an `ArrayBuffer`, or `null`. |
|
|
72
|
+
| `project.read(fields?)` | A copy of the current project limited to the fields the manifest may read (`project.read` plus `context.<field>.read`), such as `timeline`, `shots` or `assets`. |
|
|
73
|
+
| `assets.create(file, {name})` / `assets.appendVersion(asset, file)` | Adds a saved image, video or audio file to the project's media as a new asset (`asset.create`), or as the newest version of an asset the Block received, read or created (`asset.version.append`). |
|
|
74
|
+
|
|
75
|
+
Hosted calls take the operation's request fields, for example:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const plan = await stillmade.hosted.run('text.generate', {
|
|
79
|
+
prompt: `Write a 20-second podcast intro about ${input.topic}.`,
|
|
80
|
+
system: 'Warm, short sentences.', maxTokens: 400,
|
|
81
|
+
schema: { type: 'object', required: ['script'], properties: { script: { type: 'string' } } },
|
|
82
|
+
});
|
|
83
|
+
const voice = await stillmade.hosted.run('audio.speech', { text: plan.script, settings: { voice: 'adam' } });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`image.describe`, `media.analyze` and `audio.transcribe` take saved references
|
|
87
|
+
(`image`/`images`, `media`, `audio`). `audio.transcribe` takes a saved audio or a
|
|
88
|
+
saved video, whose own sound it transcribes, so there is no need to extract the
|
|
89
|
+
audio first. `media.analyze` returns `{schemaVersion: 1, items}`, one item per
|
|
90
|
+
picture or video, and for one video takes an optional `range: {start, end}` in
|
|
91
|
+
seconds, at most 12 apart, to look closely at that part. `image.generate` takes `prompt` and optional
|
|
92
|
+
`references`, one to eight saved images the new picture keeps. `video.generate` takes `prompt` and optional
|
|
93
|
+
`firstFrame` (a saved image the video starts on, for image-to-video), `lastFrame` (one it ends on; needs
|
|
94
|
+
`firstFrame`), `negativePrompt` (up to 2,000 characters) and `settings`; a model named in `settings` must take
|
|
95
|
+
those frames (Kling 3.0 Turbo and Wan 2.6 Flash take no last frame; Wan 2.6 Flash and Wan 2.7 need a first frame).
|
|
96
|
+
For a vertical or square clip, set `settings.aspectRatio` (`'9:16'`, `'1:1'`) and name no model: the default
|
|
97
|
+
model (Vidu Q3 Turbo) takes no aspect ratio, so StillMade uses the cheapest model that makes that shape with the
|
|
98
|
+
frames and `duration` given (Wan 2.6 Flash with a first frame, else Veo 3.1 Lite, else Seedance 2.0 Mini; see
|
|
99
|
+
[Generation models](../reference/generation-models.md)). A model you name is kept, and one that cannot make the shape
|
|
100
|
+
is refused. Look at pictures with `media.analyze`:
|
|
101
|
+
StillMade credits pay for it, while `image.describe` runs only on the person's
|
|
102
|
+
own OpenRouter key. `web.fetch` takes `url` and `instruction`;
|
|
103
|
+
`web.research` takes `topic`; music and sound effects take `prompt` and
|
|
104
|
+
`durationSec`.
|
|
105
|
+
|
|
106
|
+
### Actions (`permissions.actions`)
|
|
107
|
+
|
|
108
|
+
`actions.run` runs only the actions listed in `permissions.actions`, the same list a
|
|
109
|
+
JavaScript Block declares: `"actions": ["media.generate"]` (or `"stock.search"`). A
|
|
110
|
+
module that runs actions and calls no hosted operation leaves
|
|
111
|
+
`permissions.capabilities` out; an empty list means the same.
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const { value, failures } = await stillmade.actions.run({
|
|
115
|
+
schemaVersion: 1, kind: 'stillmade.media-action', operation: 'media.generate',
|
|
116
|
+
resultPort: null,
|
|
117
|
+
request: { items: [
|
|
118
|
+
{ key: 'front', operation: 'image', prompt: `The front of ${input.address} at dusk`,
|
|
119
|
+
settings: { model: 'nano-banana-pro', aspectRatio: '16:9' } },
|
|
120
|
+
{ key: 'tour', operation: 'speech', prompt: input.script },
|
|
121
|
+
] },
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The action is the object a JavaScript Block returns, with every key present; its
|
|
126
|
+
items, settings and limits are in [Generate media from
|
|
127
|
+
JavaScript](generate-media.md). Pass `resultPort: null`: no
|
|
128
|
+
output port receives the media, `actions.run` returns it to your code and `run()`
|
|
129
|
+
returns what the Block outputs. Before anything is generated StillMade opens one
|
|
130
|
+
approval for the whole batch with the most it can cost in StillMade credits; credits
|
|
131
|
+
the batch does not use come back when it closes. Each `actions.run` call is its own
|
|
132
|
+
approval, so put every item one run needs in one batch: one clip per photo, the
|
|
133
|
+
voice and the music are one `media.generate` call of up to 50 items (StillMade makes
|
|
134
|
+
four at a time), never a call per photo, and never `hosted.run('video.generate')` once
|
|
135
|
+
per photo, which asks again from the second call. Video items take the same
|
|
136
|
+
`settings` as `video.generate` above, `aspectRatio` included. `actions.run` resolves with:
|
|
137
|
+
|
|
138
|
+
- `value`: the items that were made, in request order, each
|
|
139
|
+
`{key, kind, assetId, versionId, url, duration?}` with `kind` `image`, `video` or `audio`
|
|
140
|
+
(speech, music and sound effects). `duration` is the length in seconds of a
|
|
141
|
+
video, music or sound effect item: the length it asked for (`settings.duration`,
|
|
142
|
+
`settings.durationSec`, or the model's default). A speech item has none, since
|
|
143
|
+
its length is what the words take: decode it with
|
|
144
|
+
`media.transform({operation: 'audio.decode', source})`, whose result has
|
|
145
|
+
`duration`, before timing a cut to it. Return them as outputs, or pass them to
|
|
146
|
+
`files.read`, `media.transform` or a hosted call.
|
|
147
|
+
- `failures`: one `{key, message}` for each item that was not made; those items are
|
|
148
|
+
not in `value`.
|
|
149
|
+
|
|
150
|
+
It throws when no item was made, when the person declines (`MODULE_ACTION_DECLINED`)
|
|
151
|
+
and when the action is not declared (`MODULE_PERMISSION`). `stock.search` resolves
|
|
152
|
+
with `{results}`. With `--mock` or a `tests/mocks.json`, `media.generate` answers
|
|
153
|
+
with stand-in media and no failures; record your own answer under
|
|
154
|
+
`actions["media.generate"]`.
|
|
155
|
+
|
|
156
|
+
## A check, then the expensive run, on one page
|
|
157
|
+
|
|
158
|
+
A job the person fills in and then gets a result from is one Block on one page
|
|
159
|
+
(builder contract, "One job per Step"): the inputs, a cheap check before the
|
|
160
|
+
expensive run, and the result. A listing video writes the script and voices a
|
|
161
|
+
few seconds of it first, and makes the video only once the person has read the
|
|
162
|
+
script and heard the voice. Build that as one module Block with a custom view,
|
|
163
|
+
never as two Blocks or two Steps.
|
|
164
|
+
|
|
165
|
+
The manifest takes the mode and the checked values as optional inputs, and the
|
|
166
|
+
outputs the check leaves out are optional (a run may leave out any output with
|
|
167
|
+
`"required": false`). `action` names the main button, the expensive run:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
"inputs": {
|
|
171
|
+
"listingPhotos": { "type": "image[]", "primary": true, "description": "Photos of the home, in the order of the tour." },
|
|
172
|
+
"homeFacts": { "type": "text", "description": "Address, rooms and what stands out." },
|
|
173
|
+
"make": { "type": "text", "required": false, "default": "video", "description": "check: the script and a voice sample; video: the tour." },
|
|
174
|
+
"script": { "type": "script", "required": false, "description": "The checked script, as the person left it." }
|
|
175
|
+
},
|
|
176
|
+
"outputs": {
|
|
177
|
+
"tour": { "type": "video", "primary": true, "required": false },
|
|
178
|
+
"script": { "type": "script", "required": false },
|
|
179
|
+
"voiceSample": { "type": "audio", "required": false }
|
|
180
|
+
},
|
|
181
|
+
"action": { "label": "Generate tour", "running": "Generating tour…" },
|
|
182
|
+
"permissions": { "capabilities": ["text.generate", "audio.speech"] }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The module does only the cheap work when asked for the check:
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
export default async function run(input, stillmade) {
|
|
189
|
+
const write = async () => (await stillmade.hosted.run('text.generate', {
|
|
190
|
+
prompt: `A 40-second listing tour script from these facts: ${input.homeFacts}`,
|
|
191
|
+
schema: { type: 'object', required: ['script'], properties: { script: { type: 'string' } } },
|
|
192
|
+
})).script;
|
|
193
|
+
if (input.make === 'check') {
|
|
194
|
+
const script = await write();
|
|
195
|
+
const voiceSample = await stillmade.hosted.run('audio.speech', { text: script.split(/(?<=[.!?])\s/)[0] });
|
|
196
|
+
return { script, voiceSample };
|
|
197
|
+
}
|
|
198
|
+
const script = input.script || await write();
|
|
199
|
+
const voice = await stillmade.hosted.run('audio.speech', { text: script });
|
|
200
|
+
const { duration } = await stillmade.media.transform({ operation: 'audio.decode', source: voice });
|
|
201
|
+
const each = duration / input.listingPhotos.length;
|
|
202
|
+
const tour = await stillmade.media.transform({
|
|
203
|
+
operation: 'timeline.render', aspect: '9:16',
|
|
204
|
+
clips: input.listingPhotos.map((photo, index) => ({ source: photo, start: index * each, duration: each, motion: 'zoom-in' })),
|
|
205
|
+
audio: [{ source: voice, start: 0, duration }],
|
|
206
|
+
});
|
|
207
|
+
return { tour, script };
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The view runs it twice with `StillMade.run`, which takes the inputs the view
|
|
212
|
+
sets (StillMade keeps them on the Step and fills the rest): `{make: 'check'}`
|
|
213
|
+
first ("Write the script"), then `{make: 'video', script}` with the script the
|
|
214
|
+
person checked or edited. One `sm-primary` at a time: the check until there is
|
|
215
|
+
a script, then the expensive run, labelled with `manifest.action`, while
|
|
216
|
+
"Write again" stays a quiet button. The result's place
|
|
217
|
+
(`sm-stage data-ratio="9:16"`) is drawn before either run; the script and the sample sit in it or beside the inputs
|
|
218
|
+
until the video replaces them. Each run starts empty: return the script again
|
|
219
|
+
with the video so it stays on the Step. StillMade saves the inputs a view sets,
|
|
220
|
+
so the mode last pressed stays on the Step; until the view first sets it, a run
|
|
221
|
+
started elsewhere (Chat, an automation) takes the default and makes the video.
|
|
222
|
+
|
|
223
|
+
Each run is priced and approved by StillMade on its own. Beside the Block's
|
|
224
|
+
page StillMade shows what each hosted call costs at the Step's saved or default
|
|
225
|
+
model ("Write · 1 credit · Make voiceover · 3 credits"), with an (i) saying that
|
|
226
|
+
pressing the Block's button is the approval. A run started by the person's own
|
|
227
|
+
click or key in the view then runs each hosted operation once without a dialog,
|
|
228
|
+
up to that price. StillMade checks the price with the provider first and opens
|
|
229
|
+
its approval dialog, with the quote, only when the quote is higher, when the run
|
|
230
|
+
calls the same operation a second time, or when the run did not come from the
|
|
231
|
+
person's press (in a preview outside a project every call shows the dialog;
|
|
232
|
+
`stillmade-block dev` answers with stand-ins and spends nothing). The view shows
|
|
233
|
+
no prices. `media.transform`, `timeline.render` included, runs on the device
|
|
234
|
+
and costs nothing; `actions.run` `media.generate` always asks first.
|
|
235
|
+
|
|
236
|
+
## Media processing
|
|
237
|
+
|
|
238
|
+
`stillmade.media.transform(request)` asks StillMade to do heavy media work on
|
|
239
|
+
the person's device: Web Audio for audio, and the same renderer as Final
|
|
240
|
+
Render for video (WebCodecs in the browser, the native renderer on desktop).
|
|
241
|
+
It never runs on StillMade's servers and costs no credits. A transform reads
|
|
242
|
+
only files the Block received, wrote or generated in this run (including media
|
|
243
|
+
from `actions.run` `media.generate`; see `files.read` above), and saves its
|
|
244
|
+
result to the runner's library, so you can return it as an output or pass it
|
|
245
|
+
to the next transform.
|
|
246
|
+
|
|
247
|
+
| Request | Result |
|
|
248
|
+
| --- | --- |
|
|
249
|
+
| `{operation: 'audio.decode', source, sampleRate?}` | `{sampleRate, duration, samples}`: mono `Float32Array` PCM for analysis (default 16 kHz; 8000 to 48000). |
|
|
250
|
+
| `{operation: 'audio.extract', source, format?}` | A saved WAV (16-bit, 48 kHz) or MP3 of the audio track. |
|
|
251
|
+
| `{operation: 'audio.normalize', source, targetLufs?, format?}` | A saved WAV or MP3 at the target integrated loudness (default -16 LUFS, peaks at most -1 dBFS). |
|
|
252
|
+
| `{operation: 'video.cut', source, keep, settings?}` | A saved MP4 with only the `keep` ranges (`[{start, end}]` seconds, ordered, up to 500). |
|
|
253
|
+
| `{operation: 'video.concat', sources, settings?}` | A saved MP4 of 2 to 50 videos in order. |
|
|
254
|
+
| `{operation: 'video.resize', source, settings}` | A saved MP4 re-encoded with new settings. |
|
|
255
|
+
| `{operation: 'image.thumbnail', source, at?, width?}` | A saved PNG of one frame (default the first frame, 1280 px wide). |
|
|
256
|
+
| `{operation: 'document.text', source}` | `{text, pages}`: all the words of a PDF or text file the person gave, and `pages`, a list holding each page's words as one string (up to 500 pages). A file with no words (a scanned PDF) is refused. |
|
|
257
|
+
| `{operation: 'image.compose', width, height, layers, background?, format?, text?}` | A saved PNG (or JPEG) of saved images joined into one picture: side by side, a grid, a contact sheet, a poster with a title. |
|
|
258
|
+
| `{operation: 'timeline.render', clips, kind?, aspect?, audio?, captions?, settings?}` | A saved MP4 (or, with `kind: 'audio'`, an MP3) of your own timeline, rendered the way the Editor exports. |
|
|
259
|
+
|
|
260
|
+
`timeline.render` is how a Block exports: there is no separate Export, Captions,
|
|
261
|
+
Transition or Color grade Block to call. Each clip is `{source, start, duration,
|
|
262
|
+
from?, transition?: {type, duration?}, look?, mute?, motion?, fit?, focus?}` (a saved
|
|
263
|
+
image or video; a video keeps its own sound unless `mute`; `motion` is a slow
|
|
264
|
+
camera move over the clip: `zoom-in`, `zoom-out`, `pan-left`, `pan-right`,
|
|
265
|
+
`pan-up` or `pan-down`, which always keeps the picture filling the frame), each
|
|
266
|
+
audio item `{source, start, duration, from?, volume?}` (`volume` is a percentage of the
|
|
267
|
+
recording's own level, 0 to 200: 100, the default, plays it as recorded, 50 at half
|
|
268
|
+
the level, 0 silent; put music under a voice at about 20 to 30), and each caption
|
|
269
|
+
`{text, start, end, words?, position?, highlight?, animation?, size?}`, where
|
|
270
|
+
`words` (`[{text, start, end}]`) highlights each word as it is spoken and `size`
|
|
271
|
+
is the text height in pixels of the rendered frame (16 to 200; default 44, so
|
|
272
|
+
give a 1080 by 1920 vertical video about 80). Times are seconds on the timeline: each
|
|
273
|
+
clip starts where the one before it ends, less the transition on that earlier
|
|
274
|
+
clip, which blends it into the next one; clips that overlap more are refused.
|
|
275
|
+
`aspect` is `16:9`, `9:16`, `1:1` or `4:5`. A clip of another shape than the
|
|
276
|
+
frame (a wide sermon in a `9:16` clip) is cropped to fill it, around its middle
|
|
277
|
+
or around `focus: {x, y}`, the point to keep in view as fractions 0 to 1 across
|
|
278
|
+
the picture (`{x: 0.3}` keeps a speaker standing left of centre), and
|
|
279
|
+
`focus.scale` (1 to 2.5, instead of a motion) holds the clip that much closer on
|
|
280
|
+
that point; `fit: 'contain'` fits it whole with bars instead. Looks, transitions and caption
|
|
281
|
+
animations use the Editor's names (`TIMELINE_LOOKS`, `TIMELINE_TRANSITIONS`,
|
|
282
|
+
`CAPTION_ANIMATIONS`). The render follows the person's export plan, like any
|
|
283
|
+
export from StillMade.
|
|
284
|
+
|
|
285
|
+
`image.compose` draws on a canvas `width` by `height` pixels (16 to 4096),
|
|
286
|
+
filled with `background` (`#rrggbb`, default white). Each layer is `{source, x,
|
|
287
|
+
y, width, height, fit?}`: a saved image placed in that box, in order, cropped
|
|
288
|
+
to fill it (`fit: 'cover'`, the default) or fitted whole inside it (`'contain'`).
|
|
289
|
+
Each piece of text is `{text, x, y, size?, color?, background?, align?,
|
|
290
|
+
weight?, width?}`, drawn on top: `y` is the top of the line, `size` is 8 to 400
|
|
291
|
+
pixels (default 48), `color` defaults to black, `background` puts a box behind it
|
|
292
|
+
(for a Before label over a photo; `color` and `background` are `#rrggbb`, or `#rrggbbaa`
|
|
293
|
+
or `rgba()` for a see-through box such as `#00000099`), `align` is `left`, `center` or `right` around
|
|
294
|
+
`x`, and `weight` is `bold` (default) or `normal`. `width` wraps the words onto
|
|
295
|
+
lines no wider than it, 1.2 times `size` apart, breaking a word wider than a line
|
|
296
|
+
(or a caption with no spaces) between its characters: give a caption under a
|
|
297
|
+
frame the frame's width so it never runs into the next one. Up to 64 layers and 64 pieces of
|
|
298
|
+
text; `format` is `png` (default) or `jpeg`. A grid is arithmetic in your code:
|
|
299
|
+
for 9 photos on a 1800 by 1800 sheet, three columns of 600 pixels.
|
|
300
|
+
|
|
301
|
+
`source`/`sources` are saved references (`{kind, assetId, versionId, url}`).
|
|
302
|
+
Render `settings` are `{fps: 24|30|60, resolution: 720|1080|2160, quality:
|
|
303
|
+
'low'|'medium'|'high'}`; the default is 30 fps, 1080, high. `video.cut`,
|
|
304
|
+
`video.concat` and `video.resize` keep the source's aspect ratio;
|
|
305
|
+
`timeline.render` makes the `aspect` it is given. Rendering follows the person's export plan, like any
|
|
306
|
+
export.
|
|
307
|
+
|
|
308
|
+
Pure helpers ship as `@stillmade/block-sdk/media` for you to bundle (the file is
|
|
309
|
+
`stillmade-sdk/packages/block-sdk/media-transform.js` in the npm package and
|
|
310
|
+
`<sdk>/packages/block-sdk/media-transform.js` in the extracted download; alias
|
|
311
|
+
the name in your bundler or import that path):
|
|
312
|
+
`silentRanges(samples, sampleRate, {thresholdDb, minSilence})`,
|
|
313
|
+
`keepRanges(silent, duration, {padding})`, `integratedLoudness(channels,
|
|
314
|
+
sampleRate)` (ITU-R BS.1770), `loudnessGain`, `encodeWav` and `decodeWav`.
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
import { silentRanges, keepRanges } from '@stillmade/block-sdk/media';
|
|
318
|
+
|
|
319
|
+
export default async function run(input, stillmade) {
|
|
320
|
+
const audio = await stillmade.media.transform({ operation: 'audio.decode', source: input.video });
|
|
321
|
+
const keep = keepRanges(silentRanges(audio.samples, audio.sampleRate), audio.duration);
|
|
322
|
+
const trimmed = await stillmade.media.transform({ operation: 'video.cut', source: input.video, keep });
|
|
323
|
+
return { trimmed };
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
For anything else, decode and encode in the Block itself: the Worker has
|
|
328
|
+
WebCodecs (`VideoDecoder`, `VideoEncoder`, `AudioDecoder`), `OffscreenCanvas`
|
|
329
|
+
and `createImageBitmap`, and you can bundle `mp4-muxer`, `mp4box` or a
|
|
330
|
+
WebAssembly codec. Images arrive as saved references: read the bytes with
|
|
331
|
+
`files.read` and decode them with `createImageBitmap`, at full resolution.
|
|
332
|
+
|
|
333
|
+
## Sandbox and limits
|
|
334
|
+
|
|
335
|
+
- The module runs in one dedicated Worker inside a response-sandboxed frame
|
|
336
|
+
with an opaque origin. It has no network of its own (`fetch`, WebSocket and
|
|
337
|
+
remote imports fail; `net.fetch` and `net.connect` go through StillMade), no
|
|
338
|
+
storage of the app, no page or DOM access, and cannot read another Block's
|
|
339
|
+
files.
|
|
340
|
+
- Every file is fetched from StillMade's store and checked against its SHA-256
|
|
341
|
+
before anything runs.
|
|
342
|
+
- A run may take 2 minutes; each progress report allows 60 more seconds. Time
|
|
343
|
+
StillMade spends on a host call (rendering a video, waiting for the person to
|
|
344
|
+
approve credits) does not count. Every run stops after 30 minutes in total.
|
|
345
|
+
When a limit passes, or the person cancels, the frame is removed and the
|
|
346
|
+
Worker stops immediately, even inside an endless loop.
|
|
347
|
+
- Generating code at run time (`new Function`, used by Emscripten bindings) is
|
|
348
|
+
allowed inside the sandbox.
|
|
349
|
+
|
|
350
|
+
Module Blocks run in StillMade on the person's device: in the browser while
|
|
351
|
+
StillMade is open, or in StillMade Desktop. StillMade has no server sandbox for
|
|
352
|
+
them. A schedule or event that hands browser-only Steps to the owner's StillMade
|
|
353
|
+
Desktop ("Run ... on my StillMade Desktop") pauses at a module Step until the
|
|
354
|
+
owner's desktop app, open with "Run my automations on this computer" on, runs it
|
|
355
|
+
with the Step's exact inputs; the workflow then continues from its result. Module
|
|
356
|
+
Steps that call paid hosted operations, generate media or need a device grant
|
|
357
|
+
still wait for the owner. Without that choice a background run stops before a
|
|
358
|
+
module Step and the person runs it in the project. Marketplace listings show
|
|
359
|
+
module Blocks as **Browser only**.
|
|
360
|
+
|
|
361
|
+
## Live connections
|
|
362
|
+
|
|
363
|
+
`stillmade.net.connect(url, {protocols})` opens a WebSocket to the `wss://` form
|
|
364
|
+
of an origin in `permissions.network` (declare `https://stream.example.org` and
|
|
365
|
+
connect to `wss://stream.example.org/...`) and resolves once it is open:
|
|
366
|
+
|
|
367
|
+
```js
|
|
368
|
+
const socket = await stillmade.net.connect('wss://stream.example.org/v1/live', { protocols: ['json.v1'] });
|
|
369
|
+
await socket.send(JSON.stringify({ subscribe: input.channel }));
|
|
370
|
+
for await (const data of socket) { // text arrives as strings, binary as ArrayBuffers
|
|
371
|
+
stillmade.progress(0.5, 'Listening'); // each progress report extends the run limit
|
|
372
|
+
if (JSON.parse(data).done) break; // leaving the loop closes the connection (code 1000)
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The socket also has `onmessage`, `onclose`, `addEventListener`, `readyState`,
|
|
377
|
+
`protocol`, `close(code, reason)` (1000 or 3000 to 4999) and `closed`, a promise of
|
|
378
|
+
`{code, reason, wasClean}`. `for await` yields every message not yet read, also ones
|
|
379
|
+
that arrived before the loop began (up to 1,024 messages or 16 MiB; older ones are
|
|
380
|
+
dropped). The module's own frame still has no network: StillMade
|
|
381
|
+
checks each URL (`wss://`, the standard port, a declared origin, valid subprotocol
|
|
382
|
+
names) and opens the connection in a separate relay frame that runs no Block code,
|
|
383
|
+
then relays the messages. A run keeps at most 4 connections, sends or receives at
|
|
384
|
+
most 1 MiB a message and 256 MiB in all, and every connection closes when the run
|
|
385
|
+
ends. The service sees an opaque `Origin` (`null`). StillMade adds no key to a live
|
|
386
|
+
connection: for a service that needs one, get a short-lived token with `net.fetch`
|
|
387
|
+
first and pass it as the service documents.
|
|
388
|
+
|
|
389
|
+
## Threads (SharedArrayBuffer)
|
|
390
|
+
|
|
391
|
+
A module that runs its own Workers with shared memory (Emscripten pthreads,
|
|
392
|
+
threaded WebAssembly builds) declares it in `module.json`:
|
|
393
|
+
|
|
394
|
+
```json
|
|
395
|
+
{ "entry": "main.js", "threads": true }
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
It then runs in a Worker that can share `SharedArrayBuffer` and
|
|
399
|
+
`WebAssembly.Memory({shared: true})` with the Workers it starts from `blob:` URLs
|
|
400
|
+
(module Workers and remote scripts stay unavailable, so bundle the thread code).
|
|
401
|
+
That Worker is not a secure context, so it has no WebCodecs, WebGPU,
|
|
402
|
+
`crypto.subtle` or `crypto.randomUUID`; a module without `threads` keeps those and
|
|
403
|
+
cannot share memory between Workers. Threads work in Chrome and Edge (version 137
|
|
404
|
+
or later) and in StillMade Desktop; in other browsers `self.crossOriginIsolated` is
|
|
405
|
+
false and `SharedArrayBuffer` is missing, so check it and fall back to one thread.
|
|
406
|
+
`files.write` and `storage.putFile` accept bytes in shared memory (they are copied).
|
|
407
|
+
|
|
408
|
+
## Test, pack and import
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
stillmade-block test my-block # runs fixtures in a local Node worker
|
|
412
|
+
stillmade-block preview my-block input.json
|
|
413
|
+
stillmade-block dev my-block # the interface in your browser, reloaded on save
|
|
414
|
+
stillmade-block gate my-block # StillMade's readiness check
|
|
415
|
+
stillmade-block pack my-block # writes <id>-<version>.stillmade-module.json
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
`dev` serves the Block's real controls or view at `http://127.0.0.1:4800`
|
|
419
|
+
(`--port` picks another) and answers hosted calls with stand-ins, never credits.
|
|
420
|
+
The Block is not ready until `gate` passes. Name the Block's own folder: `gate .`
|
|
421
|
+
in a folder that also holds the extracted SDK checks the SDK's examples too.
|
|
422
|
+
`pack my-block` is the only packing step for a module Block, and it never takes
|
|
423
|
+
`--original` (original `.stillmade-block` archives hold recipe, JavaScript,
|
|
424
|
+
capability and ComfyUI source only and refuse a module Block).
|
|
425
|
+
|
|
426
|
+
`test` and `preview` run your code on your machine in a disposable Node worker
|
|
427
|
+
with the same host API and run limits (not a security sandbox; it is your own
|
|
428
|
+
code). `files.read`, `files.write`, `media.transform`, actions and hosted calls need StillMade, or
|
|
429
|
+
a host you pass to `createNodeModuleExecutor` in your own tests. The Node worker has no
|
|
430
|
+
`OffscreenCanvas` or `createImageBitmap`: a Block that decodes or draws images there
|
|
431
|
+
needs its own fallback for tests, or tests only what it does without drawing.
|
|
432
|
+
|
|
433
|
+
A fixture's `files` mock is a path inside the Block folder (local runs only: it does not
|
|
434
|
+
travel in the pack), `{"text": "..."}` for a text file, or `{"base64": "..."}` for a small
|
|
435
|
+
binary file (at most 256 KB), which travels in the pack and the import review. Text can
|
|
436
|
+
never hold a NUL character (`\u0000`): StillMade cannot store it, and import refuses it.
|
|
437
|
+
|
|
438
|
+
A fixture's photo, recording, clip or file can be one of StillMade's example files,
|
|
439
|
+
named by address: `/api/media/example/photo-1.jpg` to `photo-4.jpg`, `sheet.png`,
|
|
440
|
+
`room.png`, `kitchen.wav`, `hall.wav`, `click-120.wav`, `audio.mp3`, `clip.mp4`,
|
|
441
|
+
`notes.txt`, `sales.csv` and `lesson.pdf`, a two-page PDF of words (sizes in
|
|
442
|
+
PASTE_TO_LLM.md). `test` reads their real bytes
|
|
443
|
+
when the fixture gives no mock for them, and StillMade, its preview and its phone and
|
|
444
|
+
browser check serve the same files, so the example a person sees is the one you tested.
|
|
445
|
+
Point what a fixture's mocks answer at them too (`/api/media/example/clip.mp4` for a
|
|
446
|
+
rendered clip, `kitchen.wav` for speech): `dev` then shows and plays the result, where a
|
|
447
|
+
made-up address shows an empty player.
|
|
448
|
+
|
|
449
|
+
A fixture can record what the host answers, so a test of a Block that calls
|
|
450
|
+
hosted operations costs nothing and gives the same result every run:
|
|
451
|
+
|
|
452
|
+
```json
|
|
453
|
+
{
|
|
454
|
+
"name": "Finds John 3:16",
|
|
455
|
+
"input": { "sermon": { "kind": "audio", "assetId": "a1", "versionId": "v1", "url": "https://example.com/sermon.wav" } },
|
|
456
|
+
"mocks": {
|
|
457
|
+
"hosted": {
|
|
458
|
+
"audio.transcribe": { "schemaVersion": 1, "text": "Turn to John 3:16", "words": [{ "text": "John", "start": 0.6, "end": 0.9 }] },
|
|
459
|
+
"text.generate": { "verses": [{ "reference": "John 3:16", "word": 0 }] }
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
"expected": { "verses": [{ "reference": "John 3:16", "start": 0.6 }] }
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
`mocks` has the shape of `tests/mocks.json` (`hosted`, `transforms`, `actions`,
|
|
467
|
+
`files`, `storage`, `project`, `connections`, `network`); a list answers
|
|
468
|
+
successive calls in order. `files` maps an input's asset ID to a file in the Block
|
|
469
|
+
folder, or to `{"text": "..."}` for a text file, so a Block that reads a notes or
|
|
470
|
+
CSV file the person gives it is tested without a folder (StillMade's builder has none). A fixture's own `mocks` take the place of
|
|
471
|
+
`tests/mocks.json` for that fixture. StillMade's own builder writes module
|
|
472
|
+
Blocks the same way and runs their tests before anyone sees them.
|
|
473
|
+
|
|
474
|
+
Import the `.stillmade-module.json` in StillMade (Create, then Import).
|
|
475
|
+
StillMade verifies every file digest, uploads each file to its
|
|
476
|
+
content-addressed store, and saves the Block. Fixtures then run in your
|
|
477
|
+
browser's module sandbox.
|
|
478
|
+
|
|
479
|
+
Uploaded module files count against your cloud storage. A digest alone never
|
|
480
|
+
grants access: a person can read a module file only when they uploaded those
|
|
481
|
+
exact bytes or can open a saved Block that names it (yours, an installed or
|
|
482
|
+
community version, or one shared with their team).
|
|
483
|
+
|
|
484
|
+
See `examples/module-opencv/` for a Block that runs OpenCV on a 12-megapixel
|
|
485
|
+
photo, `examples/module-silence-trimmer/` for one that cuts the pauses out of a
|
|
486
|
+
talking video, and `examples/module-audio-extract/` for one that saves a
|
|
487
|
+
video's audio as WAV.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Build an original SDK folder
|
|
2
|
+
|
|
3
|
+
Download `/block-sdk/stillmade-sdk-0.1.0.zip` (the same files as `npm install --save-dev stillmade-sdk`) and extract it into a development directory. `stillmade-block` in every StillMade guide is its command line: `node <sdk>/packages/block-cli/cli.js`, where `<sdk>` is the extracted folder (run from inside it, as below), or `npx stillmade-block` after the npm install. Then run:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
node packages/block-cli/cli.js create my-block javascript
|
|
7
|
+
node packages/block-cli/cli.js validate my-block
|
|
8
|
+
node packages/block-cli/cli.js test my-block
|
|
9
|
+
node packages/block-cli/cli.js dev my-block
|
|
10
|
+
node packages/block-cli/cli.js gate my-block
|
|
11
|
+
node packages/block-cli/cli.js pack my-block
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`dev` shows the Block's interface in your browser and reloads it on every save.
|
|
15
|
+
`gate` is the readiness check StillMade runs; the Block is not ready until it
|
|
16
|
+
passes. Name the Block's own folder: `gate .` in a folder that also holds the
|
|
17
|
+
extracted SDK checks the SDK's examples too. `pack` runs the tests again and
|
|
18
|
+
writes the file to import, named by the Block's runtime:
|
|
19
|
+
|
|
20
|
+
| Runtime | `pack my-block` writes |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `module`, or a frame view with files in `src/view-files/` | `<id>-<version>.stillmade-module.json` |
|
|
23
|
+
| `recipe`, `javascript`, `capability`, `comfyui` | `<id>-<version>.stillmade.json` |
|
|
24
|
+
|
|
25
|
+
`create` supplies an editable starter. Replace its identity, task behavior, controls
|
|
26
|
+
and fixtures with your Block's requirements, retaining applicable SDK license
|
|
27
|
+
notices. The directory layout is:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
my-block/
|
|
31
|
+
stillmade.block.json
|
|
32
|
+
src/run.js
|
|
33
|
+
src/view.html optional custom interface
|
|
34
|
+
src/view.css optional styles
|
|
35
|
+
src/view.js optional interface behavior
|
|
36
|
+
tests/fixtures.json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`src/run.js` is an isolated function body operating on `input`; it returns declared
|
|
40
|
+
outputs. The custom interface uses the supplied `StillMade` bridge. A small image
|
|
41
|
+
tool can read typed image pixels (at most one megapixel, as RGBA arrays), apply its
|
|
42
|
+
edits in `run.js`, show before/after images through `StillMade.previewImage`, and call
|
|
43
|
+
`StillMade.run` from its controls; a photo editor that keeps full-size photos is a
|
|
44
|
+
`module` Block with a frame view (MODULES.md).
|
|
45
|
+
Author those files directly under the SDK contract; external framework packages,
|
|
46
|
+
Node services and a separate standalone website are not this runtime's source.
|
|
47
|
+
No build/install command executes during import.
|
|
48
|
+
|
|
49
|
+
Select **my-block** with Import folder (or drop the folder on the Import page) to
|
|
50
|
+
import that source unchanged. The app reads the folder with the same reader as
|
|
51
|
+
`pack`, so module Blocks (their `module/` files and `.wasm`) and frame views with
|
|
52
|
+
`src/view-files/` import as they pack; module files upload by SHA-256 before review.
|
|
53
|
+
`node_modules`, `.git` and other files outside the Block layout are ignored, so a
|
|
54
|
+
folder Codex or Claude Code built with its own tools imports as it is. Select the
|
|
55
|
+
Block directory, not an SDK checkout containing several example builds. Use at least two fixtures (normal and edge cases).
|
|
56
|
+
A new Block needs no `.stillmade-block` archive: `pack <folder> <file>.stillmade-block --original`
|
|
57
|
+
writes one only for your own recipe, JavaScript, capability or ComfyUI code
|
|
58
|
+
([PORTABLE.md](https://stillmade.shop/block-sdk/PORTABLE.md)) and refuses a module Block. Direct folder
|
|
59
|
+
import and packed-file import retain the same runtime and interface. Security, source-rights confirmation and hosted execution approvals still
|
|
60
|
+
use the ordinary review path. They do not adapt the Block.
|
|
61
|
+
|
|
62
|
+
This is the implemented SDK, not the full future runtime catalog. It supports
|
|
63
|
+
data-only recipes, isolated JavaScript, reviewed hosted text and speech generation, and a reviewed ComfyUI core-image adapter,
|
|
64
|
+
typed inputs/outputs, schema-driven controls,
|
|
65
|
+
optional sandboxed HTML interfaces,
|
|
66
|
+
behavior fixtures, package validation, standalone execution, and app import.
|
|
67
|
+
Python source, external modules, native dependencies, custom ComfyUI nodes and
|
|
68
|
+
per-frame editor renderers are not executable through this SDK version. ComfyUI
|
|
69
|
+
requires an explicitly selected account connection and a real remote review;
|
|
70
|
+
recipe and JavaScript examples continue to run offline.
|