stillmade-sdk 0.1.34 → 0.1.36

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 (48) hide show
  1. package/AGENTS.md +8 -4
  2. package/DESKTOP_CAPABILITIES.md +81 -6
  3. package/FRAME_VIEWS.md +5 -0
  4. package/MODULES.md +104 -11
  5. package/PASTE_TO_LLM.md +14 -6
  6. package/SDK.md +245 -50
  7. package/blocks/connected-app/versions/1.6.0/view.js +27 -0
  8. package/blocks/gmail/versions/1.1.0/workspace.js +15 -0
  9. package/blocks/native/sm.when/versions/1.1.0/package.js +216 -0
  10. package/package.json +1 -1
  11. package/packages/block-cli/cli.js +27 -32
  12. package/packages/block-cli/dev-host/assets/host.js +38 -37
  13. package/packages/block-cli/dev-host/assets/index.css +1 -1
  14. package/packages/block-cli/dev-host/index.html +1 -0
  15. package/packages/block-cli/format.js +8 -0
  16. package/packages/block-platform/catalog.js +17 -1
  17. package/packages/block-platform/segment-runner.js +10 -4
  18. package/packages/block-platform/steps.js +6 -0
  19. package/packages/block-sdk/admission.js +11 -4
  20. package/packages/block-sdk/builder-contract.js +10 -5
  21. package/packages/block-sdk/capabilities.js +3 -0
  22. package/packages/block-sdk/connected-app.js +1 -1
  23. package/packages/block-sdk/contracts.js +1 -0
  24. package/packages/block-sdk/desktop-permissions.js +1 -1
  25. package/packages/block-sdk/first-party-identities.js +1 -0
  26. package/packages/block-sdk/first-party-packages.js +2 -1
  27. package/packages/block-sdk/folder-package.js +75 -0
  28. package/packages/block-sdk/index.d.ts +32 -6
  29. package/packages/block-sdk/index.js +4 -0
  30. package/packages/block-sdk/local-admission.js +4 -2
  31. package/packages/block-sdk/media-transform.js +42 -2
  32. package/packages/block-sdk/mock-host.js +3 -16
  33. package/packages/block-sdk/mocks.js +20 -0
  34. package/packages/block-sdk/module-host-calls.js +23 -0
  35. package/packages/block-sdk/module-hosted.js +22 -1
  36. package/packages/block-sdk/module-node.js +15 -20
  37. package/packages/block-sdk/module-runtime.js +7 -4
  38. package/packages/block-sdk/module-sockets.js +66 -0
  39. package/packages/block-sdk/module-worker.js +38 -1
  40. package/packages/block-sdk/network.js +20 -0
  41. package/packages/block-sdk/permission-summary.js +97 -0
  42. package/packages/block-sdk/permission-usage.js +72 -0
  43. package/packages/block-sdk/port-types.js +1 -1
  44. package/packages/block-sdk/schema-fields.js +20 -5
  45. package/packages/block-sdk/testing.js +20 -7
  46. package/packages/block-sdk/view-bridge.js +2 -1
  47. package/blocks/connected-app/versions/1.5.0/view.js +0 -18
  48. package/blocks/gmail/versions/1.0.0/workspace.js +0 -6
package/SDK.md CHANGED
@@ -84,9 +84,13 @@ Author those files directly under the SDK contract; external framework packages,
84
84
  Node services and a separate standalone website are not this runtime's source.
85
85
  No build/install command executes during import.
86
86
 
87
- Select **my-block** with Choose folder to import that source unchanged. Development
88
- tools may live beside it; select the Block directory, not an SDK checkout containing
89
- several example builds. Use at least two fixtures (normal and edge cases) for the
87
+ Select **my-block** with Import folder (or drop the folder on the Import page) to
88
+ import that source unchanged. The app reads the folder with the same reader as
89
+ `pack`, so module Blocks (their `module/` files and `.wasm`) and frame views with
90
+ `src/view-files/` import as they pack; module files upload by SHA-256 before review.
91
+ `node_modules`, `.git` and other files outside the Block layout are ignored, so a
92
+ folder Codex or Claude Code built with its own tools imports as it is. Select the
93
+ Block directory, not an SDK checkout containing several example builds. Use at least two fixtures (normal and edge cases) for the
90
94
  canonical archive, and use `--original` only for your own original implementation
91
95
  without reused repository/dependency code. Direct folder import and archive import
92
96
  retain the same runtime and interface; packaging adds explicit retained-source
@@ -182,7 +186,7 @@ The archive needs Node.js 22 or later and nothing else; its runtime is bundled,
182
186
 
183
187
  ## Block builder contract
184
188
 
185
- Contract `stillmade.block-builder@1.7.1`. 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.
189
+ 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.
186
190
 
187
191
  ### Source of truth
188
192
 
@@ -196,6 +200,10 @@ Before building, check what already exists (`find_existing_blocks` over MCP, or
196
200
 
197
201
  Blocks come in three sizes and the manifest decides which (packages/block-sdk/block-tier.js): an app is a workspace a person works in (Editor, Canvas, Shots), placed in a Type and never wired automatically; a job is one deliverable a person would name (the narration, the captioned clip), built from the hosted operations it needs (`permissions.capabilities`) and reviewed in one place, the unit Chat picks for a Step; an ingredient is one hosted operation with nothing of its own (a declarative capability Block), what a job is made of. When two ingredients would stand in a row to make one deliverable (writing the words, then voicing them), build or pick the job that does both and keep the review point inside it. A job declares a second output when another Block consumes the intermediate (the script beside the voice); split into two jobs only when the intermediate has a life of its own or the halves have different owners or payment.
198
202
 
203
+ ### Choose the runtime
204
+
205
+ Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`, both able to run unattended. One text, script or brief prompt returning text, script or JSON: a `capability` Block. Hosted operations on other values (a transcript, media, structured data), several hosted operations in a row, or a result shaped in code: one `module` Block, `module/main.js` calling `stillmade.hosted.run(operation, request)` for each operation in `permissions.capabilities` (MODULES.md), never one Block per operation; it runs in the person's browser while StillMade is open. Build a new Block in a folder with `stillmade-block create`, `test`, `ready` and `pack`, and import the file `pack` writes (a module Block writes `<id>-<version>.stillmade-module.json`) through Create, then Import Block. A `.stillmade-block` archive is only for adapting a GitHub repository.
206
+
199
207
  ### One job per Step
200
208
 
201
209
  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.
@@ -208,7 +216,7 @@ In a production the Steps are the workspaces a person opens: work feeding the ne
208
216
 
209
217
  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.
210
218
 
211
- Inside one job, each capability stays its own part: a capability Block performs exactly one hosted operation and a connector one app task, so every part keeps its own permission, approval and credit rules, and StillMade groups the parts of one job into one Step (`groups` in the Project Type). Never invent a combined runtime. When one Block can do the whole job with documented primitives (a JavaScript Block doing several local transforms, a workspace owning its whole document), build it as one Block.
219
+ Build one Block for a job one Block can do: a module Block calls every hosted operation the job needs (it transcribes a sermon, then finds the verses said in it), a JavaScript Block does several local transforms, a workspace owns its whole document. A Block being built never takes an ingredient's result only so the ingredient can be its own part or Step (Transcribe in front of a verse finder): it calls that operation itself. Parts in one Step (`groups` in the Project Type) are for jobs made of Blocks that already exist and app tasks, each keeping its own permission, approval and credit rules. Never invent a combined runtime.
212
220
 
213
221
  Examples: "every new email with an image becomes a generated image" is two Steps, "Gmail · New emails with images" (When, fetch with an image filter, only new) and "Image from email" (AI write, Generate image); listing When, Fetch, Only new, AI decide, Continue only if, AI write and Generate image as seven Steps splits one job into its parts. "A new Meta lead goes to HubSpot" is three Steps: "Meta Ads · New leads", "Qualify lead" and "HubSpot · Add contact".
214
222
 
@@ -246,7 +254,7 @@ Projects are multiplayer. A custom view subscribes through `StillMade.onShared(c
246
254
 
247
255
  ### External services
248
256
 
249
- Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs) and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as typed outcomes the Block reports, not crashes. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
257
+ Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs), `stillmade.net.connect` for a live WebSocket to one of them, and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as typed outcomes the Block reports, not crashes. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly (threaded: `"threads": true` in module.json) or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
250
258
 
251
259
  ### Dependencies
252
260
 
@@ -254,7 +262,7 @@ A Block must declare everything it requires and must not assume anything is inst
254
262
 
255
263
  ### Security
256
264
 
257
- Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.
265
+ Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities; `stillmade-block permissions --fix` declares exactly what the source uses. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.
258
266
 
259
267
  ### Execution lifecycle
260
268
 
@@ -342,7 +350,7 @@ In a production the Steps are the workspaces a person opens: work feeding the ne
342
350
 
343
351
  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.
344
352
 
345
- Inside one job, each capability stays its own part: a capability Block performs exactly one hosted operation and a connector one app task, so every part keeps its own permission, approval and credit rules, and StillMade groups the parts of one job into one Step (`groups` in the Project Type). Never invent a combined runtime. When one Block can do the whole job with documented primitives (a JavaScript Block doing several local transforms, a workspace owning its whole document), build it as one Block.
353
+ Build one Block for a job one Block can do: a module Block calls every hosted operation the job needs (it transcribes a sermon, then finds the verses said in it), a JavaScript Block does several local transforms, a workspace owns its whole document. A Block being built never takes an ingredient's result only so the ingredient can be its own part or Step (Transcribe in front of a verse finder): it calls that operation itself. Parts in one Step (`groups` in the Project Type) are for jobs made of Blocks that already exist and app tasks, each keeping its own permission, approval and credit rules. Never invent a combined runtime.
346
354
 
347
355
  Examples: "every new email with an image becomes a generated image" is two Steps, "Gmail · New emails with images" (When, fetch with an image filter, only new) and "Image from email" (AI write, Generate image); listing When, Fetch, Only new, AI decide, Continue only if, AI write and Generate image as seven Steps splits one job into its parts. "A new Meta lead goes to HubSpot" is three Steps: "Meta Ads · New leads", "Qualify lead" and "HubSpot · Add contact".
348
356
 
@@ -1850,6 +1858,8 @@ the Block.
1850
1858
  measured after decompression), 30 seconds per request including redirects, and
1851
1859
  120 requests a minute per person and Block.
1852
1860
  - A response that contains the key (raw, URL-encoded or base64) is withheld.
1861
+ - Live connections (`net.connect`) go to the `wss://` form of a declared origin,
1862
+ carry no key, and are relayed outside the module's sandbox (see Live connections).
1853
1863
 
1854
1864
  ### OAuth apps
1855
1865
 
@@ -1995,8 +2005,9 @@ event through an input with `"semantic": "workflow_event"` and
1995
2005
  `"sources": ["project"]`.
1996
2006
 
1997
2007
  Triggers work for recipe, JavaScript and hosted (`runtime: "capability"`)
1998
- Blocks. Module Blocks run only in the person's browser, so they cannot declare
1999
- triggers or run unattended. Sandboxed code has 25 seconds per run.
2008
+ Blocks. Module Blocks run only on the person's device, so they cannot declare
2009
+ triggers of their own; a schedule or event can hand their Steps to the owner's
2010
+ StillMade Desktop (see Module Blocks). Sandboxed code has 25 seconds per run.
2000
2011
 
2001
2012
  ### Hosted Blocks run inside an approved budget
2002
2013
 
@@ -2116,8 +2127,10 @@ A module Block (`"runtime": "module"`) is an ordinary ES module. It can use
2116
2127
  everything a modern browser Worker offers: `async`/`await`, timers, typed
2117
2128
  arrays, `WebAssembly`, `OffscreenCanvas`, `createImageBitmap`, WebCodecs, and
2118
2129
  any npm package you bundle into it, including C and C++ libraries compiled to
2119
- WebAssembly (OpenCV, libvips builds, codecs, solvers). Use it when a recipe or
2120
- the QuickJS `javascript` runtime is too small for the job.
2130
+ WebAssembly (OpenCV, libvips builds, codecs, solvers), threaded WebAssembly
2131
+ (see Threads) and live WebSocket connections to declared origins (see Live
2132
+ connections). Use it when a recipe or the QuickJS `javascript` runtime is too
2133
+ small for the job.
2121
2134
 
2122
2135
  ### Folder layout
2123
2136
 
@@ -2127,7 +2140,7 @@ my-block/
2127
2140
  module/ everything the Block runs
2128
2141
  main.js the entry (bundle npm dependencies into it)
2129
2142
  filters.wasm optional WebAssembly and data files
2130
- module.json optional: {"entry": "other.js"}
2143
+ module.json optional: {"entry": "other.js", "threads": true}
2131
2144
  tests/fixtures.json [{name, input, expected}]
2132
2145
  ```
2133
2146
 
@@ -2167,6 +2180,7 @@ against the manifest's port types.
2167
2180
  | `actions.run(action)` | Runs a declared portable action. `media.generate` asks the person to approve one StillMade credit maximum for the batch; `stock.search` runs directly. |
2168
2181
  | `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. |
2169
2182
  | `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 on its servers. Returns a standard `Response`. See [Outside APIs](/docs/build/outside-apis). |
2183
+ | `net.connect(url, {protocols})` | Opens a live WebSocket to the `wss://` form of a declared origin and resolves with the open socket (`send`, messages, `for await`, `close`). No key is added. See [Live connections](#live-connections). |
2170
2184
  | `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. |
2171
2185
  | `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](/docs/build/project-storage). |
2172
2186
  | `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`. |
@@ -2240,9 +2254,10 @@ WebAssembly codec. Images arrive as saved references: read the bytes with
2240
2254
  ### Sandbox and limits
2241
2255
 
2242
2256
  - The module runs in one dedicated Worker inside a response-sandboxed frame
2243
- with an opaque origin. It has no network (`fetch`, WebSocket and remote
2244
- imports fail), no storage of the app, no page or DOM access, and cannot read
2245
- another Block's files.
2257
+ with an opaque origin. It has no network of its own (`fetch`, WebSocket and
2258
+ remote imports fail; `net.fetch` and `net.connect` go through StillMade), no
2259
+ storage of the app, no page or DOM access, and cannot read another Block's
2260
+ files.
2246
2261
  - Every file is fetched from StillMade's store and checked against its SHA-256
2247
2262
  before anything runs.
2248
2263
  - A run may take 2 minutes; each progress report allows 60 more seconds. Time
@@ -2253,11 +2268,63 @@ WebAssembly codec. Images arrive as saved references: read the bytes with
2253
2268
  - Generating code at run time (`new Function`, used by Emscripten bindings) is
2254
2269
  allowed inside the sandbox.
2255
2270
 
2256
- Module Blocks run in StillMade in the person's browser, only while StillMade is
2257
- open. They cannot run on a schedule, from an event or on new media: StillMade has
2258
- no server sandbox for them. A workflow's background run stops before a module Step
2259
- and the person runs it in the project. Marketplace listings show module Blocks as
2260
- **Browser only**. For work that must run unattended, use a JavaScript Block.
2271
+ Module Blocks run in StillMade on the person's device: in the browser while
2272
+ StillMade is open, or in StillMade Desktop. StillMade has no server sandbox for
2273
+ them. A schedule or event that hands browser-only Steps to the owner's StillMade
2274
+ Desktop ("Run ... on my StillMade Desktop") pauses at a module Step until the
2275
+ owner's desktop app, open with "Run my automations on this computer" on, runs it
2276
+ with the Step's exact inputs; the workflow then continues from its result. Module
2277
+ Steps that call paid hosted operations, generate media or need a device grant
2278
+ still wait for the owner. Without that choice a background run stops before a
2279
+ module Step and the person runs it in the project. Marketplace listings show
2280
+ module Blocks as **Browser only**.
2281
+
2282
+ ### Live connections
2283
+
2284
+ `stillmade.net.connect(url, {protocols})` opens a WebSocket to the `wss://` form
2285
+ of an origin in `permissions.network` (declare `https://stream.example.org` and
2286
+ connect to `wss://stream.example.org/...`) and resolves once it is open:
2287
+
2288
+ ```js
2289
+ const socket = await stillmade.net.connect('wss://stream.example.org/v1/live', { protocols: ['json.v1'] });
2290
+ await socket.send(JSON.stringify({ subscribe: input.channel }));
2291
+ for await (const data of socket) { // text arrives as strings, binary as ArrayBuffers
2292
+ stillmade.progress(0.5, 'Listening'); // each progress report extends the run limit
2293
+ if (JSON.parse(data).done) break; // leaving the loop closes the connection (code 1000)
2294
+ }
2295
+ ```
2296
+
2297
+ The socket also has `onmessage`, `onclose`, `addEventListener`, `readyState`,
2298
+ `protocol`, `close(code, reason)` (1000 or 3000 to 4999) and `closed`, a promise of
2299
+ `{code, reason, wasClean}`. `for await` yields every message not yet read, also ones
2300
+ that arrived before the loop began (up to 1,024 messages or 16 MiB; older ones are
2301
+ dropped). The module's own frame still has no network: StillMade
2302
+ checks each URL (`wss://`, the standard port, a declared origin, valid subprotocol
2303
+ names) and opens the connection in a separate relay frame that runs no Block code,
2304
+ then relays the messages. A run keeps at most 4 connections, sends or receives at
2305
+ most 1 MiB a message and 256 MiB in all, and every connection closes when the run
2306
+ ends. The service sees an opaque `Origin` (`null`). StillMade adds no key to a live
2307
+ connection: for a service that needs one, get a short-lived token with `net.fetch`
2308
+ first and pass it as the service documents.
2309
+
2310
+ ### Threads (SharedArrayBuffer)
2311
+
2312
+ A module that runs its own Workers with shared memory (Emscripten pthreads,
2313
+ threaded WebAssembly builds) declares it in `module.json`:
2314
+
2315
+ ```json
2316
+ { "entry": "main.js", "threads": true }
2317
+ ```
2318
+
2319
+ It then runs in a Worker that can share `SharedArrayBuffer` and
2320
+ `WebAssembly.Memory({shared: true})` with the Workers it starts from `blob:` URLs
2321
+ (module Workers and remote scripts stay unavailable, so bundle the thread code).
2322
+ That Worker is not a secure context, so it has no WebCodecs, WebGPU,
2323
+ `crypto.subtle` or `crypto.randomUUID`; a module without `threads` keeps those and
2324
+ cannot share memory between Workers. Threads work in Chrome and Edge (version 137
2325
+ or later) and in StillMade Desktop; in other browsers `self.crossOriginIsolated` is
2326
+ false and `SharedArrayBuffer` is missing, so check it and fall back to one thread.
2327
+ `files.write` and `storage.putFile` accept bytes in shared memory (they are copied).
2261
2328
 
2262
2329
  ### Test, pack and import
2263
2330
 
@@ -4000,6 +4067,7 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
4000
4067
  | `previewMedia(value: StillMadeVideo \| StillMadeAudio): Promise<{kind: "video" \| "audio"; url: string}>` | Every view | A playable blob: URL for audio or video the view may play. Call releaseMedia when done. |
4001
4068
  | `releaseMedia(url: string): void` | Every view | Frees a URL previewMedia returned. |
4002
4069
  | `asset(path: string): string` | Frame views | A blob: URL for one of the view's own asset files, for <img>, new FontFace() or <video>. |
4070
+ | `readonly live: {start(options: {camera?: boolean; microphone?: boolean; fps?: number; width?: number; sampleRate?: number}): Promise<{live: true; camera: boolean; microphone: boolean; fps: number; width: number; height: number; sampleRate: number}>; stop(): Promise<{live: false}>; status(): Promise<{live: boolean; [key: string]: unknown}>; onFrame(callback: (frame: ImageBitmap, info: {index: number; timestamp: number; width: number; height: number}) => void): () => void; onAudio(callback: (samples: Float32Array, info: {index: number; timestamp: number; sampleRate: number}) => void): () => void; onEnd(callback: (event: {reason: string}) => void): () => void}` | Every view | The camera, the microphone or both as they happen (declare camera.capture and microphone.capture). Nothing reaches the view until the person presses Start in StillMade, which then shows that the device is on and a Turn off button. Frames arrive as ImageBitmaps, closed when your callback returns, so draw or copy them there; a slow view gets fewer frames, never a backlog. Audio arrives as mono Float32Array chunks. Nothing is saved. In browsers and StillMade Desktop. |
4003
4071
  | `assetBytes(path: string): ArrayBuffer` | Frame views | A copy of one of the view's own asset files. |
4004
4072
 
4005
4073
  ### Generated interface
@@ -4018,7 +4086,7 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
4018
4086
  | `onSharedWorkRefresh(callback: () => void): () => void` | A saved project Step | Called when shared work changes elsewhere; read it again. |
4019
4087
  | `previewSharedWorkMedia(contributionId: string): Promise<{url: string; kind: "image" \| "video" \| "audio"}>` | A saved project Step | A display copy of media attached to a shared work contribution. |
4020
4088
  | `releaseSharedWorkMedia(url: string): void` | A saved project Step | Frees a URL previewSharedWorkMedia returned. |
4021
- | `desktop(operation: string, args?: Record<string, unknown>): Promise<Record<string, unknown>>` | Every view for recording; StillMade Desktop for the rest | Runs a declared desktop permission after the person grants it. Recording (record.start, record.stop, record.cancel, record.status) and cutting a recording (record.cut) work in browsers and in StillMade Desktop; clipboard, media.pick, notifications, keep-awake and native tools need StillMade Desktop. |
4089
+ | `desktop(operation: string, args?: Record<string, unknown>): Promise<Record<string, unknown>>` | Every view for recording and folders; StillMade Desktop for the rest | Runs a declared desktop permission after the person grants it. Recording (record.start, record.stop, record.cancel, record.status), cutting a recording (record.cut) and folders the person picks (folder.*; Chrome and Edge in a browser) work in browsers and in StillMade Desktop; clipboard, media.pick, notifications, keep-awake and native tools need StillMade Desktop. Picking or pressing Start is the consent; StillMade asks once only about the clipboard, the cursor and desktop programs. |
4022
4090
 
4023
4091
  ## StillMade Blocks and the SDK
4024
4092
 
@@ -4304,6 +4372,17 @@ Every number below is read from the SDK constant named under its heading, the sa
4304
4372
  | Requests per minute to one origin (everyone) | 3,000 |
4305
4373
  | Saved key length | 4,096 |
4306
4374
 
4375
+ ### Live connections (net.connect)
4376
+
4377
+ `SOCKET_LIMITS`
4378
+
4379
+ | Limit | Value |
4380
+ | --- | --- |
4381
+ | Open connections per run | 4 |
4382
+ | One message, either way | 1 MiB |
4383
+ | All messages in one run | 256 MiB |
4384
+ | Subprotocol names | 8 |
4385
+
4307
4386
  ### Cloud storage
4308
4387
 
4309
4388
  `CLOUD_STORAGE_LIMITS`
@@ -6045,7 +6124,7 @@ export const VIEW_LIMITS: Readonly<{htmlBytes:65536;cssBytes:32768;javascriptByt
6045
6124
  export interface BlockImagePreview { url: `data:image/png;base64,${string}`; width: number; height: number; }
6046
6125
  export interface BlockImagePixels { width: number; height: number; data: number[]; role?: string; }
6047
6126
  export interface BlockImageReference { kind: 'image'; assetId: string; versionId: string; url: string; role?: string; }
6048
- export type DesktopPermission = "screen.capture"|"cursor.track"|"camera.capture"|"microphone.capture"|"clipboard.read"|"clipboard.write"|"media.pick"|"notifications.show"|"power.keep-awake"|"native.tools";
6127
+ export type DesktopPermission = "screen.capture"|"cursor.track"|"camera.capture"|"microphone.capture"|"clipboard.read"|"clipboard.write"|"media.pick"|"notifications.show"|"power.keep-awake"|"native.tools"|"folder.read"|"folder.write";
6049
6128
  export type DesktopToolDeclaration = {id:'org.natron.Natron';adapterVersion:1;actions:('launch'|'project.import'|'project.open'|'project.render')[]} | {id:'org.stillmade.PythonAdapter';adapterVersion:1;actions:('adapter.import'|'adapter.inspect'|'adapter.execute'|'adapter.describe'|'adapter.call')[]};
6050
6129
  export type DesktopExecution = {schemaVersion:1;tool:'org.natron.Natron';action:'project.render';arguments:Record<'projectId'|'input'|'reader'|'writer'|'firstFrame'|'lastFrame',{input:string}>;outputs:Record<string,'asset'>} | {schemaVersion:1;tool:'org.stillmade.PythonAdapter';action:'adapter.execute';arguments:Record<'adapterId'|'sourceDigest'|'functionId'|'input',{input:string}>;outputs:Record<string,'result'>} | {schemaVersion:2;tool:'org.stillmade.PythonAdapter';action:'adapter.call';source:{code:string;digest:string;license:string};functionId:string;arguments:Record<string,{input:string}>;outputs:{result:'result'}};
6051
6130
  export interface DesktopExecutionRequest {schemaVersion:1;requestId:string;packageDigest:string;inputDigest:string;tools:DesktopToolDeclaration[]}
@@ -6070,7 +6149,7 @@ export interface BlockViewNode { tag: BlockViewTag; text?: string; children?: Bl
6070
6149
  export interface BlockViewAction { action: string; value: string; checked: boolean; event: 'click'|'input'|'change'; }
6071
6150
  export interface BlockMediaReference { kind: 'video'|'audio'; assetId: string; versionId: string; url: string; role?: string; }
6072
6151
  export interface BlockMediaPreview { kind: 'video'|'audio'; url: string; mimeType: string; bytes: number; }
6073
- export interface BlockViewBridge { useOutput(outputs: Record<string, BlockMediaReference|BlockMediaReference[]>): Promise<Record<string, unknown>>; previewMedia(value: BlockMediaReference): Promise<BlockMediaPreview>; releaseMedia(url: string): void; render(targetId: string, tree: BlockViewNode[]): void; onAction(callback: (event: BlockViewAction) => void|Promise<void>): () => void; desktop(operation: DesktopPermission|DesktopToolOperation|import('./block-storage.js').BlockStorageOperation|"record.start"|"record.stop"|"record.cancel"|"session.close", args?: Record<string,unknown>): Promise<Record<string,unknown>>; readonly input: Record<string, unknown>; onInput(callback: (input: Record<string, unknown>) => void): () => void; run(input: Record<string, unknown>): Promise<Record<string, unknown>>; previewImage(value: BlockImageReference|BlockImagePixels): Promise<BlockImagePreview>; }
6152
+ export interface BlockViewBridge { useOutput(outputs: Record<string, BlockMediaReference|BlockMediaReference[]>): Promise<Record<string, unknown>>; previewMedia(value: BlockMediaReference): Promise<BlockMediaPreview>; releaseMedia(url: string): void; render(targetId: string, tree: BlockViewNode[]): void; onAction(callback: (event: BlockViewAction) => void|Promise<void>): () => void; desktop(operation: DesktopPermission|DesktopToolOperation|import('./block-storage.js').BlockStorageOperation|"record.start"|"record.stop"|"record.cancel"|"session.close"|"live.start"|"live.stop"|"live.status"|"folder.pick"|"folder.shared"|"folder.forget"|"folder.list", args?: Record<string,unknown>): Promise<Record<string,unknown>>; readonly input: Record<string, unknown>; onInput(callback: (input: Record<string, unknown>) => void): () => void; run(input: Record<string, unknown>): Promise<Record<string, unknown>>; previewImage(value: BlockImageReference|BlockImagePixels): Promise<BlockImagePreview>; }
6074
6153
 
6075
6154
  /** Validate hosted UI colors independently of the preserved original stylesheet. */
6076
6155
  export function validateViewAppearance(view?: BlockView, options?: {appearance?:'host'|'original'}): true;
@@ -6476,9 +6555,14 @@ export interface ConnectionConfigurationResult {schemaVersion:1;operation:'reloa
6476
6555
  export function configuredConnectionBlock(app:ConnectedAppMetadata,route:ConnectedTaskMetadata,basePackage:BlockPackage,configuration:ConnectionConfigurationResult):Promise<BlockPackage>;
6477
6556
  export function validateConnectedCapability(capability:ConnectedCapability,manifest:Manifest):true;
6478
6557
  export function prepareConnectedInvocation(pkg:BlockPackage,raw:Record<string,unknown>):{operation:'connection.execute';appId:string;capabilityId:string;pin:ConnectionPin;input:Record<string,unknown>};
6479
- export interface SchemaFields {currencyDigits(currency:string):number;minorUnits(decimal:string,currency:string):number;formatMoney(minor:number,currency:string):string;preview(value:unknown,options?:{maximumFields?:number}):BlockViewNode[];read(root:unknown,path:Array<string|number>):unknown;set(root:unknown,path:Array<string|number>,value:unknown):Record<string,unknown>;controls(schema:Record<string,unknown>,bindings:Record<string,string>,values:Record<string,unknown>,options?:{canEdit?:boolean;busy?:boolean}):{tree:unknown[];actions:unknown[]};change(values:Record<string,unknown>,actions:unknown[],event:unknown):Record<string,unknown>;fromState(input:Record<string,unknown>,state:Record<string,unknown>,bindings:Record<string,string>):Record<string,unknown>;edits(values:Record<string,unknown>,state:Record<string,unknown>,bindings:Record<string,string>):{patch:Record<string,unknown>;before:Record<string,unknown>;changed:boolean}}
6558
+ export interface SchemaFields {currencyDigits(currency:string):number;minorUnits(decimal:string,currency:string):number;formatMoney(minor:number,currency:string):string;preview(value:unknown,options?:{maximumFields?:number}):BlockViewNode[];read(root:unknown,path:Array<string|number>):unknown;set(root:unknown,path:Array<string|number>,value:unknown):Record<string,unknown>;controls(schema:Record<string,unknown>,bindings:Record<string,string>,values:Record<string,unknown>,options?:{canEdit?:boolean;busy?:boolean;more?:string[];hide?:string[]}):{tree:unknown[];actions:unknown[]};change(values:Record<string,unknown>,actions:unknown[],event:unknown):Record<string,unknown>;fromState(input:Record<string,unknown>,state:Record<string,unknown>,bindings:Record<string,string>):Record<string,unknown>;edits(values:Record<string,unknown>,state:Record<string,unknown>,bindings:Record<string,string>):{patch:Record<string,unknown>;before:Record<string,unknown>;changed:boolean}}
6480
6559
  export function createSchemaFields(lockedInputKeys?:string[]):Readonly<SchemaFields>;
6481
6560
  export interface BlockViewBridge {schemaFields:Readonly<SchemaFields>}
6561
+ export interface BlockLiveFrameInfo {index:number;timestamp:number;width:number;height:number}
6562
+ export interface BlockLiveAudioInfo {index:number;timestamp:number;sampleRate:number}
6563
+ /** The camera and microphone as they happen (`camera.capture`, `microphone.capture`); nothing reaches the view before the person presses Start. */
6564
+ export interface BlockLiveFeed {start(options:{camera?:boolean;microphone?:boolean;fps?:number;width?:number;sampleRate?:number}):Promise<{live:true;camera:boolean;microphone:boolean;fps:number;width:number;height:number;sampleRate:number}>;stop():Promise<{live:false}>;status():Promise<{live:boolean;[key:string]:unknown}>;onFrame(callback:(frame:ImageBitmap,info:BlockLiveFrameInfo)=>void):()=>void;onAudio(callback:(samples:Float32Array,info:BlockLiveAudioInfo)=>void):()=>void;onEnd(callback:(event:{reason:string})=>void):()=>void}
6565
+ export interface BlockViewBridge {readonly live:BlockLiveFeed}
6482
6566
 
6483
6567
  export interface ConnectionReferencePointer {id:string;version:string}
6484
6568
  export interface ConnectionReferenceField {path:Array<string|number>;value:string|number|boolean|null}
@@ -6518,7 +6602,9 @@ export function parseJsonReply(text: string): unknown;
6518
6602
 
6519
6603
  /** Runtime `module`: an ES module (with optional WebAssembly and data files) named by SHA-256. */
6520
6604
  export interface ModuleFile { sha256: string; bytes: number; mediaType: 'text/javascript'|'application/wasm'|'application/json'|'text/plain'|'image/png'|'image/jpeg'|'image/webp'|'font/woff2'|'application/octet-stream'; }
6521
- export interface ModuleDescriptor { schemaVersion: 1; entry: string; files: Record<string, ModuleFile>; }
6605
+ export interface ModuleDescriptor { schemaVersion: 1; entry: string; files: Record<string, ModuleFile>; /** Run in a Worker that shares memory with the Workers it starts (no WebCodecs, WebGPU or crypto.subtle). */ threads?: true; }
6606
+ /** A live WebSocket a module opened with stillmade.net.connect. */
6607
+ export interface ModuleLiveSocket { url: string; protocol: string; readonly readyState: number; onmessage: ((event: {data: string | ArrayBuffer}) => void) | null; onclose: ((event: {code: number; reason: string; wasClean: boolean}) => void) | null; send(data: string | ArrayBuffer | ArrayBufferView | Blob): Promise<unknown>; close(code?: number, reason?: string): Promise<{code: number; reason: string; wasClean: boolean}>; closed: Promise<{code: number; reason: string; wasClean: boolean}>; addEventListener(type: 'message' | 'close', listener: (event: Event & Record<string, unknown>) => void): void; removeEventListener(type: 'message' | 'close', listener: (event: Event & Record<string, unknown>) => void): void; [Symbol.asyncIterator](): AsyncIterator<string | ArrayBuffer>}
6522
6608
  export const MODULE_RUNTIME_VERSION: 1;
6523
6609
  export const MODULE_LIMITS: Readonly<{files:64;fileBytes:67108864;totalBytes:134217728;path:160;wallClockMs:120000;maxWallClockMs:1800000;progressGraceMs:60000;messageBytes:16777216;logEntries:500}>;
6524
6610
  export const MODULE_MEDIA_TYPES: readonly ModuleFile['mediaType'][];
@@ -6544,8 +6630,8 @@ export interface ModuleHost {
6544
6630
  media: { transform(request: MediaTransformRequest): Promise<SavedMediaRef|DecodedAudio>; };
6545
6631
  actions: { run(action: Record<string, unknown>): Promise<unknown>; };
6546
6632
  hosted: { run(operation: string, request: Record<string, unknown>): Promise<unknown>; };
6547
- /** Declared-origin network calls; StillMade adds the person's own key. */
6548
- net: { fetch(url: string|URL, init?: {method?: string; headers?: Record<string,string>|[string,string][]; body?: ArrayBuffer|ArrayBufferView|Blob|string}): Promise<Response>; };
6633
+ /** Declared-origin network calls; StillMade adds the person's own key. `connect` opens a live WebSocket to a declared origin's wss:// form (no key). */
6634
+ net: { fetch(url: string|URL, init?: {method?: string; headers?: Record<string,string>|[string,string][]; body?: ArrayBuffer|ArrayBufferView|Blob|string}): Promise<Response>; connect(url: string|URL, options?: {protocols?: string|string[]}): Promise<ModuleLiveSocket>; };
6549
6635
  /** Published adapter operations, with the person's own account. */
6550
6636
  connections: { call(appId: string, operation: string, input?: Record<string, unknown>): Promise<unknown>; };
6551
6637
  /** The Block's cloud storage in this project (manifest `storage`). */
@@ -6602,6 +6688,25 @@ export const NATIVE_APPLICATION_RUNTIME_REQUIREMENT:string;
6602
6688
  /** Validates a native application package's strict source syntax and returns its detached, frozen copy. Throws NATIVE_APPLICATION_PACKAGE errors. Not runtime admission. */
6603
6689
  export function defineNativeApplicationPackage<T=unknown>(value:T):T;
6604
6690
  export function validateNativeApplicationPackage(value:unknown):true;
6691
+ export const PERMISSION_LEVELS:readonly ["none","low","medium","high"];
6692
+ export type PermissionLevel="none"|"low"|"medium"|"high";
6693
+ export interface PermissionLine { id:string; text:string; level:PermissionLevel; family:"project"|"service"|"network"|"device" }
6694
+ /** What a Block can do, in plain words, with the level of care it needs. The host refuses anything a Block did not declare, so this is all it can reach. */
6695
+ export function summarizePermissions(manifest:Manifest):{ level:PermissionLevel; lines:PermissionLine[]; dataLeaves:boolean; staysInStillMade:boolean; headline:string };
6696
+ /** What an update adds or removes, by line, and whether its level rose. */
6697
+ export function permissionChanges(previous:Manifest,next:Manifest):{ added:PermissionLine[]; removed:PermissionLine[]; levelRaised:boolean; before:ReturnType<typeof summarizePermissions>; after:ReturnType<typeof summarizePermissions> };
6698
+ /** What a Block's source uses compared with its manifest: desktop operations and network origins used but not declared, or declared and never used. Advice from literal names; never a gate. */
6699
+ export function permissionUsage(pkg:BlockPackage,files?:Record<string,Uint8Array|string>):{ desktop:string[]; origins:string[]; undeclared:{family:"desktop"|"network";scope:string}[]; unused:{family:"desktop"|"network";scope:string}[] };
6700
+ /** The manifest declaring what the source uses and nothing else. */
6701
+ export function fixPermissions(manifest:Manifest,usage:ReturnType<typeof permissionUsage>):Manifest;
6702
+ /** The package for a Block folder (the layout stillmade-block create writes), from its files. Shared by the CLI and StillMade's Import folder. */
6703
+ export function packageFromFolder(source:{ read(path:string):Promise<Uint8Array|undefined>; list(prefix:string):Promise<Record<string,Uint8Array>> }):Promise<{ pkg:BlockPackage; files:Record<string,Uint8Array>|null }>;
6704
+ /** Whether a path inside a Block folder is part of the Block (node_modules, build output and notes are not). */
6705
+ export function blockFolderPath(path:string):boolean;
6706
+ /** The manifest file at the root of a Block folder. */
6707
+ export const BLOCK_FOLDER_MANIFEST:"stillmade.block.json";
6708
+ /** Size limits for Block folder source files, in bytes. */
6709
+ export const BLOCK_FOLDER_LIMITS:Readonly<{jsonBytes:4000000;javascriptBytes:262144;viewScriptBytes:4194304;viewSourceBytes:262144}>;
6605
6710
 
6606
6711
  ```
6607
6712
 
@@ -6753,7 +6858,7 @@ A portable image reference is not decoded pixel data. The host must resolve it b
6753
6858
  5. Test actual supported media in the host workspace.
6754
6859
 
6755
6860
  ### Every SDK error code
6756
- Generated from the SDK source (197 codes). The message is one example; the actual message names the exact field or limit.
6861
+ Generated from the SDK source (198 codes). The message is one example; the actual message names the exact field or limit.
6757
6862
 
6758
6863
  | Code | Example message | Raised in |
6759
6864
  | --- | --- | --- |
@@ -6788,12 +6893,11 @@ Generated from the SDK source (197 codes). The message is one example; the actua
6788
6893
  | ARCHIVE | Expected a complete single ZIP archive | `portable-archive` |
6789
6894
  | ARCHIVE_PATH | Use safe relative regular-file paths | `portable` |
6790
6895
  | AUDIO_SCORE_HOST | Choose a supported Audio Score operation | `audio-score-host` |
6791
- | BEHAVIOR | Output differs from expected fixture. Expected …; received … | `testing` |
6792
6896
  | BROWSER_APPLICATION_UNAVAILABLE | No trusted desktop browser application executor is installed. A bundle or mounted interface is not execution. | `browser-application-execution` |
6793
6897
  | BROWSER_BUILD_DEPENDENCY | Expected one exact registry tarball identity. | `repository-build-dependencies` |
6794
6898
  | BUSY | JavaScript runners are busy. Try again shortly. | `sandbox-node` |
6795
6899
  | BYOK_PAYMENT_HOST | Choose a supported payment capability | `byok-payment-host` |
6796
- | CANCELLED | Conversion stopped. | `adapter-registry`, `browser-application-execution`, `browser-application-guest` and 7 more |
6900
+ | CANCELLED | Conversion stopped. | `adapter-registry`, `browser-application-execution`, `browser-application-guest` and 8 more |
6797
6901
  | CANVAS_GENERATION_HOST | Canvas media Blocks require version … of their trusted generation host | `canvas-generation-host` |
6798
6902
  | CANVAS_NODE_HOST | Invalid versioned Canvas node registration | `canvas-node-host` |
6799
6903
  | CANVAS_WORKSPACE | Canvas needs its scoped snapshot and navigation binding | `canvas-workspace-host` |
@@ -6865,13 +6969,15 @@ Generated from the SDK source (197 codes). The message is one example; the actua
6865
6969
  | MEDIA_RENDER_LIMIT | Final render exceeds the approved timeline limits. | `portable-media-contracts` |
6866
6970
  | MEDIA_RENDER_RECEIPT | The final render receipt is invalid. | `portable-media-contracts` |
6867
6971
  | MEDIA_UPLOAD_HOST | Import Media requires its version 1 host interface | `media-upload-host` |
6868
- | MOCK_HOST | … has the sections … | `mock-host` |
6972
+ | MOCK_HOST | The stand-in host needs the Block manifest. | `mock-host`, `mocks` |
6869
6973
  | MODULE_BUNDLE | Only module Blocks and frame views with assets are bundled this way | `module-bundle` |
6870
- | MODULE_FILE | … is missing from the local files | `module-node`, `module-runtime` |
6974
+ | MODULE_ERROR | The module stopped without returning: it waits on something that never finishes. | `module-quickjs` |
6975
+ | MODULE_FILE | … is missing from the local files | `module-node`, `module-quickjs`, `module-runtime` |
6871
6976
  | MODULE_HOST_UNAVAILABLE | Local files need a file reader. | `mock-host` |
6872
6977
  | MODULE_HOSTED | A module Block identity is required | `module-hosted` |
6873
6978
  | MODULE_PERMISSION | This Block did not declare … in permissions.capabilities | `module-hosted` |
6874
6979
  | MODULE_RESULT | The module host returned an unexpected result | `runtime` |
6980
+ | MODULE_TIMEOUT | The module's tests ran past … ms of its own time. | `module-quickjs` |
6875
6981
  | NATIVE_APPLICATION_AUTHORITY | Use the scoped project execution checkpoint. | `runtime` |
6876
6982
  | NATIVE_APPLICATION_UNAVAILABLE | No trusted native application executor is installed. A bundle or mounted interface is not execution. | `native-application-execution` |
6877
6983
  | NATIVE_BINDING | Declare scoped trusted bindings | `native-workspace-contract` |
@@ -7241,9 +7347,9 @@ Ask the assistant to return source files or one `.stillmade.json` package. Run t
7241
7347
 
7242
7348
  <!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
7243
7349
 
7244
- ### Block builder contract (stillmade.block-builder@1.7.1)
7350
+ ### Block builder contract (stillmade.block-builder@1.10.0)
7245
7351
 
7246
- Contract `stillmade.block-builder@1.7.1`. 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.
7352
+ 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.
7247
7353
 
7248
7354
  ### Source of truth
7249
7355
 
@@ -7257,6 +7363,10 @@ Before building, check what already exists (`find_existing_blocks` over MCP, or
7257
7363
 
7258
7364
  Blocks come in three sizes and the manifest decides which (packages/block-sdk/block-tier.js): an app is a workspace a person works in (Editor, Canvas, Shots), placed in a Type and never wired automatically; a job is one deliverable a person would name (the narration, the captioned clip), built from the hosted operations it needs (`permissions.capabilities`) and reviewed in one place, the unit Chat picks for a Step; an ingredient is one hosted operation with nothing of its own (a declarative capability Block), what a job is made of. When two ingredients would stand in a row to make one deliverable (writing the words, then voicing them), build or pick the job that does both and keep the review point inside it. A job declares a second output when another Block consumes the intermediate (the script beside the voice); split into two jobs only when the intermediate has a life of its own or the halves have different owners or payment.
7259
7365
 
7366
+ ### Choose the runtime
7367
+
7368
+ Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`, both able to run unattended. One text, script or brief prompt returning text, script or JSON: a `capability` Block. Hosted operations on other values (a transcript, media, structured data), several hosted operations in a row, or a result shaped in code: one `module` Block, `module/main.js` calling `stillmade.hosted.run(operation, request)` for each operation in `permissions.capabilities` (MODULES.md), never one Block per operation; it runs in the person's browser while StillMade is open. Build a new Block in a folder with `stillmade-block create`, `test`, `ready` and `pack`, and import the file `pack` writes (a module Block writes `<id>-<version>.stillmade-module.json`) through Create, then Import Block. A `.stillmade-block` archive is only for adapting a GitHub repository.
7369
+
7260
7370
  ### One job per Step
7261
7371
 
7262
7372
  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.
@@ -7269,7 +7379,7 @@ In a production the Steps are the workspaces a person opens: work feeding the ne
7269
7379
 
7270
7380
  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.
7271
7381
 
7272
- Inside one job, each capability stays its own part: a capability Block performs exactly one hosted operation and a connector one app task, so every part keeps its own permission, approval and credit rules, and StillMade groups the parts of one job into one Step (`groups` in the Project Type). Never invent a combined runtime. When one Block can do the whole job with documented primitives (a JavaScript Block doing several local transforms, a workspace owning its whole document), build it as one Block.
7382
+ Build one Block for a job one Block can do: a module Block calls every hosted operation the job needs (it transcribes a sermon, then finds the verses said in it), a JavaScript Block does several local transforms, a workspace owns its whole document. A Block being built never takes an ingredient's result only so the ingredient can be its own part or Step (Transcribe in front of a verse finder): it calls that operation itself. Parts in one Step (`groups` in the Project Type) are for jobs made of Blocks that already exist and app tasks, each keeping its own permission, approval and credit rules. Never invent a combined runtime.
7273
7383
 
7274
7384
  Examples: "every new email with an image becomes a generated image" is two Steps, "Gmail · New emails with images" (When, fetch with an image filter, only new) and "Image from email" (AI write, Generate image); listing When, Fetch, Only new, AI decide, Continue only if, AI write and Generate image as seven Steps splits one job into its parts. "A new Meta lead goes to HubSpot" is three Steps: "Meta Ads · New leads", "Qualify lead" and "HubSpot · Add contact".
7275
7385
 
@@ -7307,7 +7417,7 @@ Projects are multiplayer. A custom view subscribes through `StillMade.onShared(c
7307
7417
 
7308
7418
  ### External services
7309
7419
 
7310
- Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs) and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as typed outcomes the Block reports, not crashes. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
7420
+ Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs), `stillmade.net.connect` for a live WebSocket to one of them, and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as typed outcomes the Block reports, not crashes. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly (threaded: `"threads": true` in module.json) or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
7311
7421
 
7312
7422
  ### Dependencies
7313
7423
 
@@ -7315,7 +7425,7 @@ A Block must declare everything it requires and must not assume anything is inst
7315
7425
 
7316
7426
  ### Security
7317
7427
 
7318
- Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.
7428
+ Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities; `stillmade-block permissions --fix` declares exactly what the source uses. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.
7319
7429
 
7320
7430
  ### Execution lifecycle
7321
7431
 
@@ -7417,7 +7527,11 @@ For image generation, use runtime:"capability", operation:"image.generate" and p
7417
7527
  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"]}}}]}.
7418
7528
  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}}}]}.
7419
7529
  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}}}]}.
7420
- Each capability package performs one supported operation. A job that needs several operations is built from parts: return each operation as its own part Block, and StillMade groups the parts of one job into one Step (One job per Step); never invent a combined 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.
7530
+ 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.
7531
+
7532
+ ### Module Blocks
7533
+
7534
+ 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).
7421
7535
 
7422
7536
  ### ComfyUI workflows
7423
7537
 
@@ -10682,9 +10796,16 @@ Offline `validate`, `test`, `pack`, `admitLocalPackage` and `admitPackage` witho
10682
10796
 
10683
10797
  # Desktop capabilities for Blocks
10684
10798
 
10685
- Recording runs on both platforms: in a browser the trusted web host records with the browser's own screen, camera or microphone picker and keeps the finished file on the device (a `sm-media://device/...` reference); in StillMade Desktop the native host gives consent and picks the source. Every other scope below needs StillMade Desktop.
10799
+ Recording and folders run on both platforms: in a browser the trusted web host records with the browser's own screen, camera or microphone picker and keeps the finished file on the device (a `sm-media://device/...` reference), and shares folders through the browser's folder picker (Chrome and Edge); in StillMade Desktop the native host does both. Every other scope below needs StillMade Desktop.
10800
+
10801
+ **Consent is the person's own action.** A Block reaches only what it declares, and StillMade refuses everything else, so people are never asked to judge a Block. Picking a folder or files in the system dialog, or pressing StillMade's Start button for a recording or live feed, is the consent: there is no second "Allow?" question. Notifications and keeping the computer awake need no question. StillMade asks once only about what happens without such an action: reading the clipboard or the cursor, replacing the clipboard, and running a desktop program. An OS prompt may follow the first use of the camera or microphone. Importing a Block, installing StillMade or opening a preview asks nothing. Imported UI receives neither native IPC nor grant tokens.
10802
+
10803
+ **What a person allows is remembered for the exact Block where it is used**: their account, the project Step, and the exact package (its digest). Picked folders and answered questions carry over to the next session and the next launch, in StillMade Desktop and in the browser. Any other package starts with nothing, including a new version of the Block, another Block in the same Step, or a Block from another author that reuses the same id. Remembered access ends when:
10804
+ - the Step's Block changes: an update, or another Block put in its place (it picks or answers again, once);
10805
+ - StillMade quarantines the Block;
10806
+ - the person forgets it: **Stop sharing** under the Block, or **Forget** in Desktop access.
10686
10807
 
10687
- The desktop host implements these capabilities on demand. Importing a Block, installing StillMade, or opening a preview does not request device permissions. The first operation displays StillMade's native consent dialog identifying the Block ID and version. An OS prompt may follow. Grants last for the mounted Block workspace, up to one hour; closing/reloading it or revoking access ends that grant. Imported UI receives neither native IPC nor grant tokens.
10808
+ Under every Block that uses device features, **What this Block did** lists the folders it uses and what it read, wrote, copied or ran on this device. The Block cannot read or hide it.
10688
10809
 
10689
10810
  Declare the exact required scopes in `manifest.permissions.desktop`. Other permission families remain unchanged. Pure recipe/JavaScript execution stays sandboxed; the **Block UI bridge** requests desktop operations through the trusted host.
10690
10811
 
@@ -10692,14 +10813,16 @@ Declare the exact required scopes in `manifest.permissions.desktop`. Other permi
10692
10813
  | --- | --- | --- |
10693
10814
  | `screen.capture` | `record.start`, `{kind:"screen"}` | Choose a screen or window, then record it. Browser and desktop. |
10694
10815
  | `cursor.track` | `cursor.track`, or `{cursor:true}` on `record.start` | Global cursor coordinates and display geometry; recordings sample at 10 Hz. Does not monitor clicks or keyboard input. |
10695
- | `camera.capture` | `record.start`, `{kind:"camera"}` | Camera recording after device/OS approval. Browser and desktop. |
10696
- | `microphone.capture` | `record.start`, `{kind:"microphone"}`, or `{kind:"screen", microphone:true}` | Microphone recording after device/OS approval, or the microphone mixed into a screen recording. Browser and desktop. |
10816
+ | `camera.capture` | `record.start`, `{kind:"camera"}`; `live.start`, `{camera:true}` | Camera recording, or the live camera feed, after device/OS approval. Browser and desktop. |
10817
+ | `microphone.capture` | `record.start`, `{kind:"microphone"}`, or `{kind:"screen", microphone:true}`; `live.start`, `{microphone:true}` | Microphone recording after device/OS approval, the microphone mixed into a screen recording, or the live microphone feed. Browser and desktop. |
10697
10818
  | `clipboard.read` | `clipboard.read` | Returns `{text}`; bounded to 100,000 characters. |
10698
10819
  | `clipboard.write` | `clipboard.write`, `{text}` | Explicit permission to replace clipboard text. |
10699
10820
  | `media.pick` | `media.pick` | Native picker returns `{assets}` for up to 20 selected media files; no arbitrary path access. |
10700
10821
  | `notifications.show` | `notifications.show`, `{body}` | StillMade/Block-branded desktop notification; maximum one every ten seconds. |
10701
10822
  | `power.keep-awake` | `power.keep-awake`, `{enabled:true}` | Prevent app suspension until disabled, revoked, closed, or expired. |
10702
10823
  | `native.tools` | `tool.status` and declared `tool.*` operations | Use one exact versioned adapter for an approved installed desktop application. |
10824
+ | `folder.read` | `folder.pick`, `folder.shared`, `folder.list`, `folder.read`, `folder.forget` | List and read files in a folder the person picks. The Block gets an opaque folder id and names files by relative paths. Desktop, and Chrome and Edge. |
10825
+ | `folder.write` | `folder.pick`, `{write:true}`; `folder.write` | Create and replace files in a folder the person shared for changing. Declare `folder.read` too. Desktop, and Chrome and Edge. |
10703
10826
 
10704
10827
  The host always checks declarations. Declaring a scope does not grant it. For anything but recording, a browser receives a clear desktop-required error; there is no silent paid/cloud fallback. A Block whose only desktop scopes are recording scopes is not marked as requiring StillMade Desktop.
10705
10828
 
@@ -10719,6 +10842,72 @@ Natron adapter version 1 currently exposes `launch`, `project.import`, `project.
10719
10842
 
10720
10843
  The mapped Natron Block uses `video` as both its primary typed input and output, with semantic `video`. This allows an explicit Project Type chain such as video producer → Natron → review/export Block. Its custom interface submits the connected original input and render settings to `StillMade.run`; the approved desktop executor invokes the renderer and returns the newly registered artifact with a bound native receipt. Source fixtures and JavaScript passthrough do not prove native execution. See [Native desktop execution](/docs/reference/desktop-execution) for `src/desktop.json`, offline admission, host callbacks and retained-result requirements.
10721
10844
 
10845
+ ### Folders you choose
10846
+
10847
+ A Block that works on a person's files (a photo culler, a subtitle batch, a project exporter) asks for one folder at a time. The person picks it in the system folder dialog (StillMade Desktop) or the browser's folder picker (Chrome and Edge); the Block never sees a path and reaches nothing outside that folder. The folder is remembered for the Block where it is used, so start with the folders it already has:
10848
+
10849
+ ```js
10850
+ let [folder] = (await StillMade.desktop('folder.shared')).folders;
10851
+ if (!folder) ({folder} = await StillMade.desktop('folder.pick', {write: true}));
10852
+ const {entries} = await StillMade.desktop('folder.list', {folder: folder.id, path: 'raw'});
10853
+ const {text} = await StillMade.desktop('folder.read', {folder: folder.id, path: 'notes.txt'});
10854
+ const {asset} = await StillMade.desktop('folder.read', {folder: folder.id, path: 'raw/a.png', as: 'media'});
10855
+ await StillMade.desktop('folder.write', {folder: folder.id, path: 'edits/notes.txt', text: 'Take two'});
10856
+ await StillMade.desktop('folder.write', {folder: folder.id, path: 'cover.png', asset, replace: true});
10857
+ ```
10858
+
10859
+ - `folder.pick` returns `{folder: {id, name, write}}`. `{write:true}` asks to share it for changing, and needs `folder.write`; the picker says which. Picking a folder already shared keeps its id. In a browser, call it from a click in the Block: Chrome opens its picker only after one, and otherwise StillMade shows its own Choose folder button first.
10860
+ - `folder.shared` returns `{folders: [{id, name, write}]}`: the folders this Block already has here, kept from earlier sessions. `folder.forget` with `{folder}` stops using one.
10861
+ - `folder.list` returns `{entries: [{name, kind, size, modified}], truncated}`, folders first, at most 1,000 entries. Links are left out.
10862
+ - `folder.read` returns `{text, size}` for a text file up to 1 MB, or `{asset, size}` with `as: "media"` for an image, video or audio file: on Desktop the file becomes a saved desktop asset; in a browser it stays on the device, linked where it sits (`sm-media://device/...`). Either way the Block can show it, pass it to `StillMade.run` or return it.
10863
+ - `folder.write` takes `text` (up to 1 MB) or `asset`, a media value this Block saved, read or received in this session. Missing folders on the path are created. An existing file is replaced only with `replace: true`. The file is written beside its destination first and moved into place, so a failed write leaves no partial file.
10864
+ - Paths are relative, separated by `/`, at most 32 levels and 1,024 characters. `..`, `.`, absolute paths, drive letters, backslashes, names with leading or trailing spaces or a trailing dot, reserved Windows names (`CON`, `NUL`) and Windows short names (`GIT~1`) are refused. On Desktop, hidden-file checks also run on the resolved path, a symbolic link anywhere on the path is refused rather than followed, and every resolved path is checked to be inside the folder. A browser does not show links or the folders above the picked one; Chrome itself refuses system folders and the home folder.
10865
+ - Hidden files and folders (names starting with `.`, such as `.git`, `.env`, `.vscode` or a shell profile) are left out of lists and cannot be read or written.
10866
+ - `folder.write` creates and replaces only media, subtitle, plain-text and table files: images (`png`, `jpg`, `webp`, `tiff`, `exr`, `psd` and similar), video (`mp4`, `mov`, `webm`, `mkv`, `mxf` and similar), audio (`mp3`, `wav`, `m4a`, `flac` and similar), `txt`, `srt`, `vtt`, `ass`, `csv`, `tsv`, `edl`, `otio`, `cube` and `pdf`. Structured data goes in a file named `<name>.stillmade.json`. Other `.json` and `.md` files are not written, because tools and coding agents read names like `package.json` or `CLAUDE.md` as settings or instructions; nor are programs, scripts, shortcuts, disk images, macro documents, build files such as `CMakeLists.txt`, or anything inside a macOS bundle such as `Name.app`. This is an allowlist: a type it does not name, including a lookalike written with other characters, is refused. Written files are never executable.
10867
+ - The person cannot pick a whole drive, their home folder or a folder above it. A folder shared for changing also cannot be hidden, inside application settings (`AppData`, `Library`) or inside a code repository (a folder with `.git` at or above it on Desktop, inside it in a browser); those can still be shared for reading. Every write checks again, so a repository created inside the folder later stops writes into it.
10868
+ - A Block holds at most four folders. They are remembered for the exact Block where it is used until the person stops sharing them (under the Block, or in Desktop access), the Step's Block changes, or StillMade quarantines it. On Desktop each remembered folder is checked again before use: one that was moved, replaced by a link or turned into a repository is dropped. In a browser, Chrome may ask once to allow a remembered folder again after it restarts, or keep it for good with "Allow on every visit".
10869
+ - Nothing deletes or renames files. In a read-only workspace a folder can be picked for reading, listed and read; picking one for changing and `folder.write` need edit access.
10870
+
10871
+ ### Live camera and microphone
10872
+
10873
+ A view can use the camera, the microphone or both as they happen instead of a
10874
+ finished recording: a live filter, a tuner, a level meter, a pose or gesture
10875
+ check, live captions. It declares the same scopes as recording and uses
10876
+ `StillMade.live` (standard and frame views, browsers and StillMade Desktop):
10877
+
10878
+ ```js
10879
+ const canvas = document.getElementById('picture'), context = canvas.getContext('2d');
10880
+ StillMade.live.onFrame((frame, info) => { // an ImageBitmap, info.width x info.height
10881
+ canvas.width = info.width; canvas.height = info.height;
10882
+ context.drawImage(frame, 0, 0); // draw or copy it now: it is closed afterwards
10883
+ });
10884
+ StillMade.live.onAudio((samples, info) => { // mono Float32Array, info.sampleRate
10885
+ meter.value = Math.max(...samples.map(Math.abs));
10886
+ });
10887
+ StillMade.live.onEnd(event => { status.textContent = 'Camera off'; }); // event.reason
10888
+ document.getElementById('start').addEventListener('click', () =>
10889
+ StillMade.live.start({ camera: true, microphone: true, fps: 15, width: 640, sampleRate: 16000 }));
10890
+ ```
10891
+
10892
+ - `live.start` resolves with `{live, camera, microphone, fps, width, height, sampleRate}`
10893
+ once the person presses Start in StillMade, in a browser and in StillMade
10894
+ Desktop. `fps` is 1 to 30 (default 15), `width` 160 to 1280
10895
+ pixels (default 640, never wider than the camera), `sampleRate` 8000 to 48000
10896
+ (default 16000; a browser that cannot resample reports its own rate in each chunk).
10897
+ - The view never holds the camera or microphone. StillMade keeps the stream,
10898
+ shows that the device is on with a Turn off button, and posts transferred
10899
+ copies: one `ImageBitmap` per frame and one 4,096-sample `Float32Array` per
10900
+ audio chunk. At most two frames are unanswered at a time, so a slow view
10901
+ receives fewer frames, never a backlog.
10902
+ - `live.stop()` (or Turn off, closing the Block, or revoking access)
10903
+ ends the feed; `onEnd` receives the reason (`stopped`, `person`, `closed`,
10904
+ `ended` when the device stops, `time`). Nothing is recorded or saved.
10905
+ - A recording and a live feed do not run at the same time. `live.stop` and
10906
+ `live.status` work in a read-only workspace; `live.start` needs edit access.
10907
+ - For heavy processing of frames (a WebAssembly model), copy what you need
10908
+ (`getImageData`, `createImageBitmap(frame)`) and hand it to a Worker in a frame view,
10909
+ or to the Block's runtime with `StillMade.run`.
10910
+
10722
10911
  ### Ready-to-import recording Blocks
10723
10912
 
10724
10913
  Download the complete [Screen recorder](/block-sdk/examples/screen-recorder.stillmade.json), [Camera recorder](/block-sdk/examples/camera-recorder.stillmade.json), or [Microphone recorder](/block-sdk/examples/microphone-recorder.stillmade.json). These packages also ship in the SDK archive. Import one through StillMade’s normal review flow, then use it in a browser or in the desktop app. Each includes a themed interface and pure runtime fixtures that preserve the media reference. The fixtures use synthetic references; they do not exercise your device.
@@ -10758,7 +10947,7 @@ stop.addEventListener('click', async () => {
10758
10947
  });
10759
10948
  ```
10760
10949
 
10761
- Every recording in a browser, and a screen recording on desktop, begins from StillMade's own Start button outside the Block interface (labelled for the screen, camera or microphone): a view never reaches a device by itself, and the site's standing camera or microphone permission is not consent for a Block. On desktop, camera and microphone recordings begin after StillMade's consent dialog naming the Block.
10950
+ Every recording, in a browser and in StillMade Desktop, begins from StillMade's own Start button outside the Block interface (labelled for the screen, camera or microphone): pressing it is the consent. A view never reaches a device by itself, and the site's standing camera or microphone permission is not consent for a Block.
10762
10951
 
10763
10952
  `record.stop` returns `{asset, duration, width, height, bytes, mimeType, stoppedBy, message, cursor, displays, coordinates}`. `asset` is a canonical media reference with `assetId`, `versionId`, `kind` and a device-local `sm-media:` URL (`sm-media://asset/...` from the desktop media library, `sm-media://device/...` for a file a browser keeps on the device, which `previewMedia`, module `files.read` and the next Step read without uploading). `duration` is seconds measured by the host and written into the WebM header, so the recording seeks and cuts correctly. `stoppedBy` is present when the host ended the recording before `record.stop`: `size`, `time` or `sharing` (the person ended the share); the recording up to that point is kept and `message` says so. Cursor samples contain `{x,y,time}`: global device-independent screen coordinates and milliseconds relative to recording start. Multiple monitors can have negative coordinates. These are not automatically converted into cropped-window video coordinates. Share/upload media through the existing host media flow before teammates on other devices can use it. By default that flow uploads a 1080p working copy of large video (the account's media setting); the original stays in the desktop media library.
10764
10953
 
@@ -10768,7 +10957,7 @@ Every recording in a browser, and a screen recording on desktop, begins from Sti
10768
10957
 
10769
10958
  `record.cancel` discards an active capture. `session.close` revokes all access and cancels capture. StillMade also renders a host-owned cancel/revoke control outside the Block iframe. Screen source selection happens for every recording. No preview can bypass consent or access the native bridge directly.
10770
10959
 
10771
- Current limits: one recording per Block workspace. In StillMade Desktop the recording streams to disk as it is captured (each chunk crosses to the desktop process as it arrives; the header is completed with the measured duration at the end), so the caps are 4 hours and free disk space; a desktop app without streamed recordings keeps 30 minutes and 128 MiB. In a browser the caps are 30 minutes, 2 GB and the browser's storage quota (`device.info` says how many minutes fit). Hitting a limit, the disk running low, or the person ending the share stops the recording and keeps what was captured; `record.stop` then saves it and reports `stoppedBy` (`size`, `time`, `disk` or `sharing`). `record.status` reports `limits` with `bytes: null` where only disk space applies. Output is WebM where the browser records it (H.264 or VP9 inside WebM in Chromium) and MP4 where only that is available (Safari). Camera recording is video-only; microphone recording is audio-only. Optional `{systemAudio:true}` is implemented through Windows loopback; other platforms reject it explicitly. Microphone mixing, global click/keyboard hooks, remote-control automation, arbitrary subprocesses, and folder watching are not provided by these scopes.
10960
+ Current limits: one recording per Block workspace. In StillMade Desktop the recording streams to disk as it is captured (each chunk crosses to the desktop process as it arrives; the header is completed with the measured duration at the end), so the caps are 4 hours and free disk space; a desktop app without streamed recordings keeps 30 minutes and 128 MiB. In a browser the caps are 30 minutes, 2 GB and the browser's storage quota (`device.info` says how many minutes fit). Hitting a limit, the disk running low, or the person ending the share stops the recording and keeps what was captured; `record.stop` then saves it and reports `stoppedBy` (`size`, `time`, `disk` or `sharing`). `record.status` reports `limits` with `bytes: null` where only disk space applies. Output is WebM where the browser records it (H.264 or VP9 inside WebM in Chromium) and MP4 where only that is available (Safari). Camera recording is video-only; microphone recording is audio-only. Optional `{systemAudio:true}` is implemented through Windows loopback; other platforms reject it explicitly. Microphone mixing, global click/keyboard hooks, remote-control automation, arbitrary subprocesses, and folder watching are not provided by these scopes (folder access is `folder.read` and `folder.write`, above).
10772
10961
 
10773
10962
  ### Testing and installation
10774
10963
 
@@ -11990,7 +12179,7 @@ Every manifest declares `permissions` with these families. Anything not declared
11990
12179
  | capabilities | Exactly one hosted operation for `runtime: "capability"` (`text.generate`, `audio.speech`, `audio.transcribe`, `audio.music`, `audio.sfx`, `image.generate`, `video.generate`, `image.describe`, `media.analyze`, `web.fetch`, `web.research`, `timeline.propose`, `workspace.propose`, `connection.execute`), or `comfyui.execute` for `runtime: "comfyui"`. See [runtime capabilities](/docs/reference/capabilities). |
11991
12180
  | actions | Portable media actions below, for `runtime: "javascript"` or `"module"` |
11992
12181
  | desktop | Desktop permissions below, used through the host desktop bridge |
11993
- | network | For `runtime: "module"`: up to 8 exact https origins the Block calls with `stillmade.net.fetch`, each optionally with the key it needs (`{origin, auth: {scheme: "bearer" \| "header" \| "query" \| "oauth2", name or app, label, help}}`). People add their own key or sign in once per Block; the host adds it on its servers. Other runtimes: empty. See [Outside APIs](/docs/build/outside-apis). |
12182
+ | network | For `runtime: "module"`: up to 8 exact https origins the Block calls with `stillmade.net.fetch` (and opens live WebSockets to with `stillmade.net.connect`, at their `wss://` form), each optionally with the key it needs (`{origin, auth: {scheme: "bearer" \| "header" \| "query" \| "oauth2", name or app, label, help}}`). People add their own key or sign in once per Block; the host adds it on its servers. Other runtimes: empty. See [Outside APIs](/docs/build/outside-apis). |
11994
12183
  | connections | For `runtime: "module"`: up to 8 published adapters by app ID (`app:openapi:notion`), called with `stillmade.connections.call`. Other runtimes: empty. |
11995
12184
  | filesystem, secrets | Must be empty arrays. Blocks have no ambient file or secret access; keys come from the people running the Block, through declared network access. |
11996
12185
 
@@ -12053,14 +12242,16 @@ A JavaScript Block returns a `{schemaVersion: 1, kind: "stillmade.media-action",
12053
12242
  | --- | --- |
12054
12243
  | `screen.capture` | Choose a screen or window, then record it. |
12055
12244
  | `cursor.track` | Global cursor coordinates and display geometry; no click or keyboard monitoring. |
12056
- | `camera.capture` | Camera recording after device and OS approval. |
12057
- | `microphone.capture` | Microphone recording after device and OS approval. |
12245
+ | `camera.capture` | Camera recording, or the live camera feed (`StillMade.live`), after device and OS approval. |
12246
+ | `microphone.capture` | Microphone recording, or the live microphone feed (`StillMade.live`), after device and OS approval. |
12058
12247
  | `clipboard.read` | Read clipboard text, bounded to 100,000 characters. |
12059
12248
  | `clipboard.write` | Replace clipboard text. |
12060
12249
  | `media.pick` | Native picker for up to 20 media files; no arbitrary path access. |
12061
12250
  | `notifications.show` | A Block-branded desktop notification, at most one every ten seconds. |
12062
12251
  | `power.keep-awake` | Keep the app from suspending until disabled, revoked, closed or expired. |
12063
12252
  | `native.tools` | Use one exact versioned adapter for an approved installed desktop application. |
12253
+ | `folder.read` | List and read files in a folder the person picks (StillMade Desktop, Chrome and Edge); the Block sees an opaque folder id and relative paths only, and the folder is remembered for the Block where it is used. |
12254
+ | `folder.write` | Create and replace files in a folder the person shared for changing (declare folder.read too). |
12064
12255
 
12065
12256
  Details and request shapes: [desktop capabilities](/docs/reference/desktop-capabilities).
12066
12257
 
@@ -12276,7 +12467,7 @@ The latest version of each Block bundled with the SDK, generated from the packag
12276
12467
  | `sm.transition` 2.0.2 | Transition | editor-extension | `timeline` timeline (editor_timeline) · project context only · primary; `clipIds` text[] (timeline_clip_ids); `transition` text (timeline_transition_type); `duration` number (timeline_transition_duration); `soundOn` boolean (timeline_transition_sound_enabled); `soundId` text (timeline_transition_sound_id) | `edit` timeline-edit (timeline_edit) · primary |
12277
12468
  | `sm.video-assets` 2.1.4 | Video Assets | workspace | `assets` asset[] (media_collection) · project context only · primary; `versions` metadata[] (media_version_history) · project context only; `incoming` asset[] (media_collection); `kind` text (media_filter); `selectedIds` text (media_selection); `selectedVersions` text (media_version_selection) | `output` asset[] (production_media) · primary |
12278
12469
  | `sm.voiceover` 2.1.6 | Voiceover | workspace | `input` script (narration_script) · primary; `take` audio (narration_audio); `library` asset[] (project_media) · project context only; `timing` transcript (timed_transcript) · project context only | `output` audio (narration_audio) · primary |
12279
- | `sm.when` 1.0.0 | When | task | `event` object (workflow_event) | `trigger` object (automation_trigger) · primary |
12470
+ | `sm.when` 1.1.0 | Automation | task | `event` object (workflow_event); `start` object (automation_start); `only_if` text (automation_condition) | `item` object (external_data) · primary; `trigger` object (automation_trigger); `check` object (ai_decision) |
12280
12471
  | `sm.whiteboard-assistant` 1.0.0 | Build Whiteboard | task | `board` board-document · project context only; `request` text · primary | `edit` workspace-edit · primary |
12281
12472
  | `sm.write-summary` 1.0.0 | Write summary | task | `data` text (external_data) · primary | `summary` text (summary_report) · primary |
12282
12473
  | `sm.youtube` 2.0.0 | YouTube publication | task | `video` video (finished_video) · primary; `title` text; `description` text; `tags` text; `visibility` text; `publishAt` text; `madeForKids` boolean; `aiGenerated` boolean | `uploadAction` approval (portable_media_action); `publication` json (youtube_publication) |
@@ -12339,7 +12530,11 @@ For image generation, use runtime:"capability", operation:"image.generate" and p
12339
12530
  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"]}}}]}.
12340
12531
  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}}}]}.
12341
12532
  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}}}]}.
12342
- Each capability package performs one supported operation. A job that needs several operations is built from parts: return each operation as its own part Block, and StillMade groups the parts of one job into one Step (One job per Step); never invent a combined 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.
12533
+ 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.
12534
+
12535
+ ### Module Blocks
12536
+
12537
+ 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).
12343
12538
 
12344
12539
  ### ComfyUI workflows
12345
12540