stillmade-sdk 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -1
- package/PASTE_TO_LLM.md +10 -2
- package/SDK.md +28 -3
- package/package.json +1 -1
- package/packages/block-cli/cli.js +4 -1
- package/packages/block-cli/ready-gate.js +7 -1
- package/packages/block-sdk/builder-contract.js +5 -2
- package/packages/block-sdk/contracts.js +3 -1
- package/packages/block-sdk/feature-map.js +58 -0
- package/packages/block-sdk/index.d.ts +9 -0
- package/packages/block-sdk/index.js +1 -0
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ This folder is the StillMade Block SDK. Read this file before writing code; it i
|
|
|
6
6
|
|
|
7
7
|
Do not tell the user a Block is ready, done or working until `node packages/block-cli/cli.js gate .` passes after your last edit, and report the exact status it prints. In this folder Claude Code (`.claude/settings.json`) and Cursor (`.cursor/hooks.json`) run the gate when you finish a turn and send any failures back to you, but only in a session started in this folder: if you downloaded the SDK during the current session, run the gate yourself. Other agents must also run it themselves. If you cannot fix a failure, say plainly that the Block is not ready and list the failing checks.
|
|
8
8
|
|
|
9
|
-
Contract `stillmade.block-builder@1.
|
|
9
|
+
Contract `stillmade.block-builder@1.6.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.
|
|
10
10
|
|
|
11
11
|
## Source of truth
|
|
12
12
|
|
|
@@ -40,6 +40,10 @@ Translate the request into an explicit capability contract before implementation
|
|
|
40
40
|
|
|
41
41
|
A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
|
|
42
42
|
|
|
43
|
+
## Feature map
|
|
44
|
+
|
|
45
|
+
Every Block carries a feature map in `manifest.features`, written by whoever builds it while they build, so the person never writes it. Add one entry for each separate thing a person could ask for: a tool, a mode, a preset family or a kind of result (an editor's reaction GIFs, its motion presets, a recorder's screen recording), not one entry covering several tools. Each entry has a lowercase `id`, a `label` of 40 characters or fewer, `does` (what it does, 160 characters or fewer) and `asks`: 1–8 short requests in a customer's own words that this feature answers ("add a fire gif over my car"). `operation` names an id in the Block's own `manifest.operations` (omit it when there are none); `inputs`/`outputs` name declared ports. The gate fails a Block without one. Add or update the entry in the same change that adds or alters a tool, and remove it when the tool goes. List only what the code really does; listings are generated from this map, nobody can edit them, and StillMade matches requests to features. Features are not tested at publish: each shows as new until real use proves it.
|
|
46
|
+
|
|
43
47
|
## Use platform primitives
|
|
44
48
|
|
|
45
49
|
Prefer, in order: an SDK capability; a documented host capability (the hosted operations are 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; connected third-party apps go through `connection.execute`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.
|
package/PASTE_TO_LLM.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
|
|
4
4
|
|
|
5
|
-
## Block builder contract (stillmade.block-builder@1.
|
|
5
|
+
## Block builder contract (stillmade.block-builder@1.6.1)
|
|
6
6
|
|
|
7
|
-
Contract `stillmade.block-builder@1.
|
|
7
|
+
Contract `stillmade.block-builder@1.6.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.
|
|
8
8
|
|
|
9
9
|
### Source of truth
|
|
10
10
|
|
|
@@ -38,6 +38,10 @@ Translate the request into an explicit capability contract before implementation
|
|
|
38
38
|
|
|
39
39
|
A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
|
|
40
40
|
|
|
41
|
+
### Feature map
|
|
42
|
+
|
|
43
|
+
Every Block carries a feature map in `manifest.features`, written by whoever builds it while they build, so the person never writes it. Add one entry for each separate thing a person could ask for: a tool, a mode, a preset family or a kind of result (an editor's reaction GIFs, its motion presets, a recorder's screen recording), not one entry covering several tools. Each entry has a lowercase `id`, a `label` of 40 characters or fewer, `does` (what it does, 160 characters or fewer) and `asks`: 1–8 short requests in a customer's own words that this feature answers ("add a fire gif over my car"). `operation` names an id in the Block's own `manifest.operations` (omit it when there are none); `inputs`/`outputs` name declared ports. The gate fails a Block without one. Add or update the entry in the same change that adds or alters a tool, and remove it when the tool goes. List only what the code really does; listings are generated from this map, nobody can edit them, and StillMade matches requests to features. Features are not tested at publish: each shows as new until real use proves it.
|
|
44
|
+
|
|
41
45
|
### Use platform primitives
|
|
42
46
|
|
|
43
47
|
Prefer, in order: an SDK capability; a documented host capability (the hosted operations are 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; connected third-party apps go through `connection.execute`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.
|
|
@@ -148,6 +152,10 @@ Every finished Block must support automatic remixing through the standard SDK co
|
|
|
148
152
|
|
|
149
153
|
A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task",runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}. Types include text,number,integer,boolean,json,image,audio,video,script,text[]. Include test {name,input,expected} where expected is output bindings.
|
|
150
154
|
|
|
155
|
+
### Feature map
|
|
156
|
+
|
|
157
|
+
Every Block lists its separate tools in manifest.features, one entry per tool or mode a person could ask for: {"id":"reaction-gifs","label":"Reaction GIFs","does":"Search GIFs and drop one over a clip.","asks":["add a fire gif over my car","reaction gif when he laughs"]}. Use 1–24 entries; id is lowercase with hyphens, label at most 40 characters, does at most 160, asks 1–8 short requests in a customer's words. A Block that really does one job has one entry. Rewrite the starting entry create writes (it copies the description), or the gate fails. When refining or remixing, add, change or remove entries in the same change as the tool, and keep the ids of tools that stay.
|
|
158
|
+
|
|
151
159
|
### Port meanings
|
|
152
160
|
|
|
153
161
|
For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs semantic, required:boolean and a short plain-language description. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters: for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. Declare sources only when a requirement restricts where its answer may come from: upstream, project, adapter, chat, default, user or external. Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
|
package/SDK.md
CHANGED
|
@@ -182,7 +182,7 @@ The archive needs Node.js 22 or later and nothing else; its runtime is bundled,
|
|
|
182
182
|
|
|
183
183
|
## Block builder contract
|
|
184
184
|
|
|
185
|
-
Contract `stillmade.block-builder@1.
|
|
185
|
+
Contract `stillmade.block-builder@1.6.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.
|
|
186
186
|
|
|
187
187
|
### Source of truth
|
|
188
188
|
|
|
@@ -216,6 +216,10 @@ Translate the request into an explicit capability contract before implementation
|
|
|
216
216
|
|
|
217
217
|
A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
|
|
218
218
|
|
|
219
|
+
### Feature map
|
|
220
|
+
|
|
221
|
+
Every Block carries a feature map in `manifest.features`, written by whoever builds it while they build, so the person never writes it. Add one entry for each separate thing a person could ask for: a tool, a mode, a preset family or a kind of result (an editor's reaction GIFs, its motion presets, a recorder's screen recording), not one entry covering several tools. Each entry has a lowercase `id`, a `label` of 40 characters or fewer, `does` (what it does, 160 characters or fewer) and `asks`: 1–8 short requests in a customer's own words that this feature answers ("add a fire gif over my car"). `operation` names an id in the Block's own `manifest.operations` (omit it when there are none); `inputs`/`outputs` name declared ports. The gate fails a Block without one. Add or update the entry in the same change that adds or alters a tool, and remove it when the tool goes. List only what the code really does; listings are generated from this map, nobody can edit them, and StillMade matches requests to features. Features are not tested at publish: each shows as new until real use proves it.
|
|
222
|
+
|
|
219
223
|
### Use platform primitives
|
|
220
224
|
|
|
221
225
|
Prefer, in order: an SDK capability; a documented host capability (the hosted operations are 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; connected third-party apps go through `connection.execute`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.
|
|
@@ -5914,8 +5918,17 @@ export interface FrameView { runtime: 'frame'; html: string; css?: string; theme
|
|
|
5914
5918
|
export interface BlockPackage { portable?: PortableMetadata; manifest: Manifest; view?: BlockView|FrameView; code?: string; module?: ModuleDescriptor; comfyui?: ComfyWorkflow; capability?: ConnectedCapability|TextGenerationCapability|SpeechCapability|TranscriptionCapability|ImageGenerationCapability|VideoGenerationCapability|ImageDescriptionCapability|TimelineProposalCapability; recipe?: { schemaVersion: 1; steps: { id: string; op: string; args: Record<string, unknown> }[]; outputs: Record<string, unknown> }; tests: { name: string; input: Record<string, unknown>; expected?: Record<string, unknown>; state?: BlockState; expectedState?: BlockState; expectations?: Record<string, ConnectedResultExpectation|ComfyImageExpectation|CapabilityTextExpectation|CapabilityAudioExpectation|CapabilityTranscriptExpectation|CapabilityImageExpectation|CapabilityVideoExpectation|CapabilityTimelineExpectation> }[]; }
|
|
5915
5919
|
/** Named sandbox action with fixed declared input settings and produced ports. */
|
|
5916
5920
|
export interface BlockOperation {id:string;label:string;description:string;inputs:Record<string,string|number|boolean>;outputs:string[]}
|
|
5921
|
+
/** One entry of a Block's feature map: a separate thing the Block can do and the requests it answers. */
|
|
5922
|
+
export interface BlockFeature {id:string;label:string;does:string;asks:string[];operation?:string;inputs?:string[];outputs?:string[]}
|
|
5923
|
+
export const FEATURE_MAP_LIMITS: Readonly<{features:24;id:48;label:40;does:160;asks:8;ask:80}>;
|
|
5924
|
+
/** Validation messages for `manifest.features`; [] when valid or absent. */
|
|
5925
|
+
export function featureMapErrors(manifest:unknown):string[];
|
|
5926
|
+
/** The declared feature map, or one derived from operations or the Block itself. */
|
|
5927
|
+
export function blockFeatures(manifest:unknown):(BlockFeature&{source:'declared'|'derived'})[];
|
|
5917
5928
|
export interface Manifest {
|
|
5918
5929
|
operations?: BlockOperation[];
|
|
5930
|
+
/** Feature map: what the Block can do, tool by tool. Written by the builder; listings are generated from it. */
|
|
5931
|
+
features?: BlockFeature[];
|
|
5919
5932
|
storage?: import('./block-storage.js').BlockStorageDeclaration;
|
|
5920
5933
|
/** Events the Block can run on by itself, once the project owner turns them on. */
|
|
5921
5934
|
triggers?: import('./triggers.js').BlockTrigger[];
|
|
@@ -7224,9 +7237,9 @@ Ask the assistant to return source files or one `.stillmade.json` package. Run t
|
|
|
7224
7237
|
|
|
7225
7238
|
<!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
|
|
7226
7239
|
|
|
7227
|
-
### Block builder contract (stillmade.block-builder@1.
|
|
7240
|
+
### Block builder contract (stillmade.block-builder@1.6.1)
|
|
7228
7241
|
|
|
7229
|
-
Contract `stillmade.block-builder@1.
|
|
7242
|
+
Contract `stillmade.block-builder@1.6.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.
|
|
7230
7243
|
|
|
7231
7244
|
### Source of truth
|
|
7232
7245
|
|
|
@@ -7260,6 +7273,10 @@ Translate the request into an explicit capability contract before implementation
|
|
|
7260
7273
|
|
|
7261
7274
|
A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
|
|
7262
7275
|
|
|
7276
|
+
### Feature map
|
|
7277
|
+
|
|
7278
|
+
Every Block carries a feature map in `manifest.features`, written by whoever builds it while they build, so the person never writes it. Add one entry for each separate thing a person could ask for: a tool, a mode, a preset family or a kind of result (an editor's reaction GIFs, its motion presets, a recorder's screen recording), not one entry covering several tools. Each entry has a lowercase `id`, a `label` of 40 characters or fewer, `does` (what it does, 160 characters or fewer) and `asks`: 1–8 short requests in a customer's own words that this feature answers ("add a fire gif over my car"). `operation` names an id in the Block's own `manifest.operations` (omit it when there are none); `inputs`/`outputs` name declared ports. The gate fails a Block without one. Add or update the entry in the same change that adds or alters a tool, and remove it when the tool goes. List only what the code really does; listings are generated from this map, nobody can edit them, and StillMade matches requests to features. Features are not tested at publish: each shows as new until real use proves it.
|
|
7279
|
+
|
|
7263
7280
|
### Use platform primitives
|
|
7264
7281
|
|
|
7265
7282
|
Prefer, in order: an SDK capability; a documented host capability (the hosted operations are 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; connected third-party apps go through `connection.execute`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.
|
|
@@ -7370,6 +7387,10 @@ Every finished Block must support automatic remixing through the standard SDK co
|
|
|
7370
7387
|
|
|
7371
7388
|
A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task",runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}. Types include text,number,integer,boolean,json,image,audio,video,script,text[]. Include test {name,input,expected} where expected is output bindings.
|
|
7372
7389
|
|
|
7390
|
+
### Feature map
|
|
7391
|
+
|
|
7392
|
+
Every Block lists its separate tools in manifest.features, one entry per tool or mode a person could ask for: {"id":"reaction-gifs","label":"Reaction GIFs","does":"Search GIFs and drop one over a clip.","asks":["add a fire gif over my car","reaction gif when he laughs"]}. Use 1–24 entries; id is lowercase with hyphens, label at most 40 characters, does at most 160, asks 1–8 short requests in a customer's words. A Block that really does one job has one entry. Rewrite the starting entry create writes (it copies the description), or the gate fails. When refining or remixing, add, change or remove entries in the same change as the tool, and keep the ids of tools that stay.
|
|
7393
|
+
|
|
7373
7394
|
### Port meanings
|
|
7374
7395
|
|
|
7375
7396
|
For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs semantic, required:boolean and a short plain-language description. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters: for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. Declare sources only when a requirement restricts where its answer may come from: upstream, project, adapter, chat, default, user or external. Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
|
|
@@ -12288,6 +12309,10 @@ Every finished Block must support automatic remixing through the standard SDK co
|
|
|
12288
12309
|
|
|
12289
12310
|
A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task",runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}. Types include text,number,integer,boolean,json,image,audio,video,script,text[]. Include test {name,input,expected} where expected is output bindings.
|
|
12290
12311
|
|
|
12312
|
+
### Feature map
|
|
12313
|
+
|
|
12314
|
+
Every Block lists its separate tools in manifest.features, one entry per tool or mode a person could ask for: {"id":"reaction-gifs","label":"Reaction GIFs","does":"Search GIFs and drop one over a clip.","asks":["add a fire gif over my car","reaction gif when he laughs"]}. Use 1–24 entries; id is lowercase with hyphens, label at most 40 characters, does at most 160, asks 1–8 short requests in a customer's words. A Block that really does one job has one entry. Rewrite the starting entry create writes (it copies the description), or the gate fails. When refining or remixing, add, change or remove entries in the same change as the tool, and keep the ids of tools that stay.
|
|
12315
|
+
|
|
12291
12316
|
### Port meanings
|
|
12292
12317
|
|
|
12293
12318
|
For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs semantic, required:boolean and a short plain-language description. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters: for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. Declare sources only when a requirement restricts where its answer may come from: upstream, project, adapter, chat, default, user or external. Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {blockFeatures} from '../block-sdk/feature-map.js';
|
|
2
3
|
import {assertRemixReady} from '../block-sdk/remix-policy.js';
|
|
3
4
|
import {remixBlock,bundledRemixSource,assertRemixAttribution} from '../block-platform/block-remix.js';
|
|
4
5
|
import {verifyPortableSources} from '../block-sdk/portable-github.js';
|
|
@@ -200,6 +201,8 @@ export async function main(rawArgs) {
|
|
|
200
201
|
if (command === 'create') {
|
|
201
202
|
if(args.length<2)throw Object.assign(new Error('Name the folder to create: stillmade-block create my-block [template]'),{code:'USAGE'});
|
|
202
203
|
const template=templateNamed(extra),files=await template.files();
|
|
204
|
+
// Every new Block starts with a feature map (one entry from its name and description) for the builder to rewrite as it grows.
|
|
205
|
+
{const created=JSON.parse(files['stillmade.block.json']);if(!created.features&&!created.stages){created.features=blockFeatures(created).map(({source,...feature})=>feature);files['stillmade.block.json']=`${JSON.stringify(created,null,2)}\n`;}}
|
|
203
206
|
// Editors pick up the generated types for the view, src/run.js and module code.
|
|
204
207
|
files['stillmade-env.d.ts']=blockTypeDeclarations(JSON.parse(files['stillmade.block.json']));
|
|
205
208
|
files['jsconfig.json']=`${JSON.stringify({compilerOptions:{target:'ES2022',module:'ESNext',moduleResolution:'Bundler',lib:['ES2022','DOM'],jsx:'react-jsx',checkJs:false},include:['src','module','stillmade-env.d.ts']},null,2)}\n`;
|
|
@@ -208,7 +211,7 @@ export async function main(rawArgs) {
|
|
|
208
211
|
const names=['stillmade.block.json',...Object.keys(files).filter(name=>name!=='stillmade.block.json')];
|
|
209
212
|
for(const name of names){await mkdir(dirname(join(path,name)),{recursive:true});await writeFile(join(path,name),files[name],{flag:'wx'});}
|
|
210
213
|
// Coding agents read command output: restate the readiness rule where they will see it.
|
|
211
|
-
return { created: path, sdkVersion: '0.1.0', template:template.name, runtime:template.runtime, files:names, next: 'Build the Block, then run `stillmade-block gate .`. Do not call the Block ready until the gate passes; report the status it prints.' };
|
|
214
|
+
return { created: path, sdkVersion: '0.1.0', template:template.name, runtime:template.runtime, files:names, next: 'Build the Block and write its feature map (`manifest.features`: one entry per thing it can do), then run `stillmade-block gate .`. Do not call the Block ready until the gate passes; report the status it prints.' };
|
|
212
215
|
}
|
|
213
216
|
if (command === 'gate') {
|
|
214
217
|
// Every new or edited Block in a folder must pass readiness before anyone calls it ready.
|
|
@@ -84,6 +84,12 @@ const summarize=readiness=>(readiness?.items||[]).filter(item=>['failed','blocke
|
|
|
84
84
|
async function judge(path,main){
|
|
85
85
|
try{
|
|
86
86
|
const readiness=await main(['ready',path]);
|
|
87
|
+
// Every new or edited Block carries its feature map (builder-contract); search, matching and its listing read it.
|
|
88
|
+
const manifest=(await main(['inspect',path]).catch(()=>null))?.manifest;
|
|
89
|
+
// The starting map create writes (one entry copied from the description) does not count until it is rewritten.
|
|
90
|
+
const placeholder=features=>features.length===1&&features[0].id==='main'&&features[0].asks?.length===1&&features[0].asks[0]===features[0].does;
|
|
91
|
+
if(PASSING.has(readiness.status)&&manifest&&!(Array.isArray(manifest.features)&&manifest.features.length&&!placeholder(manifest.features)))
|
|
92
|
+
return {ok:false,status:'NO FEATURE MAP',failures:['Add manifest.features: one entry per separate thing the Block can do, each with id, label, does and asks (requests in a customer\'s words). StillMade search, matching and the listing read it.'],limitations:(readiness.limitations||[]).length};
|
|
87
93
|
return {ok:PASSING.has(readiness.status),status:readiness.status,failures:summarize(readiness),limitations:(readiness.limitations||[]).length};
|
|
88
94
|
}catch(error){
|
|
89
95
|
if(/^ready checks one Block package/.test(error.message)){
|
|
@@ -119,7 +125,7 @@ export async function runReadyGate({root=process.cwd(),main}={}){
|
|
|
119
125
|
|
|
120
126
|
export function gateMessage(verdict){
|
|
121
127
|
const lines=verdict.failing.map(result=>`- ${result.package} (${result.status}):\n${result.failures.map(failure=>` ${failure}`).join('\n')||' readiness check failed'}`);
|
|
122
|
-
return `StillMade readiness gate: ${verdict.failing.length} Block${verdict.failing.length===1?'':'s'} you changed ${verdict.failing.length===1?'is':'are'} not ready.\n${lines.join('\n')}\nFix these and re-run \`
|
|
128
|
+
return `StillMade readiness gate: ${verdict.failing.length} Block${verdict.failing.length===1?'':'s'} you changed ${verdict.failing.length===1?'is':'are'} not ready.\n${lines.join('\n')}\nFix these and re-run \`stillmade-block ready <folder>\` (\`npx stillmade-block ready <folder>\` in an npm project). Do not tell the user a Block is ready, done or working while this gate fails. If you cannot fix it, say plainly that the Block is NOT ready and list these failures.`;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
/** Hook entry. `agent` is claude, cursor or plain. Returns {exitCode, stdout}. */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import {HOSTED_OPERATION_NAMES} from './contracts.js';
|
|
10
10
|
|
|
11
11
|
export const BUILDER_CONTRACT_ID='stillmade.block-builder';
|
|
12
|
-
export const BUILDER_CONTRACT_VERSION='1.
|
|
12
|
+
export const BUILDER_CONTRACT_VERSION='1.6.1';
|
|
13
13
|
export const BUILDER_CONTRACT_STAMP=`${BUILDER_CONTRACT_ID}@${BUILDER_CONTRACT_VERSION}`;
|
|
14
14
|
export const BUILDER_CONTRACT_DOCS_PATH='/docs/build/block-builder-contract';
|
|
15
15
|
// Block names appear in step bars, cards and pickers; new releases keep them short.
|
|
@@ -46,6 +46,9 @@ const SECTIONS=Object.freeze([
|
|
|
46
46
|
rule('compose','both',
|
|
47
47
|
'Blocks compose: give every port a type, `semantic` and description (a `video` input with semantic `source_video`, never `inputPath: text`), one primary input and output, and outputs that are reusable StillMade values. The resolver fills inputs; users never wire JSON, IDs or paths.',
|
|
48
48
|
'A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks\' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block\'s output into another: `createUniversalResolver()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.'),
|
|
49
|
+
rule('feature-map','block',
|
|
50
|
+
'Keep `manifest.features` current: one {id,label,does,asks} per separate tool or mode, with requests it answers; never one the code lacks.',
|
|
51
|
+
'Every Block carries a feature map in `manifest.features`, written by whoever builds it while they build, so the person never writes it. Add one entry for each separate thing a person could ask for: a tool, a mode, a preset family or a kind of result (an editor\'s reaction GIFs, its motion presets, a recorder\'s screen recording), not one entry covering several tools. Each entry has a lowercase `id`, a `label` of 40 characters or fewer, `does` (what it does, 160 characters or fewer) and `asks`: 1–8 short requests in a customer\'s own words that this feature answers ("add a fire gif over my car"). `operation` names an id in the Block\'s own `manifest.operations` (omit it when there are none); `inputs`/`outputs` name declared ports. The gate fails a Block without one. Add or update the entry in the same change that adds or alters a tool, and remove it when the tool goes. List only what the code really does; listings are generated from this map, nobody can edit them, and StillMade matches requests to features. Features are not tested at publish: each shows as new until real use proves it.'),
|
|
49
52
|
rule('platform-primitives','block',
|
|
50
53
|
`Use SDK and host capabilities (hosted operations: ${hostedOperations}) before Block-local code; never rebuild assets, pickers, auth, credits, secrets, permissions, progress, sharing, persistence or providers inside a Block.`,
|
|
51
54
|
`Prefer, in order: an SDK capability; a documented host capability (the hosted operations are ${hostedOperations}; connected third-party apps go through \`connection.execute\`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.`),
|
|
@@ -130,7 +133,7 @@ export const BUILDER_CONTRACT=Object.freeze({
|
|
|
130
133
|
gapClasses:GAP_CLASSES,
|
|
131
134
|
});
|
|
132
135
|
|
|
133
|
-
const SECTION_TITLES=Object.freeze({'grounded-text':'Only supplied facts','source-of-truth':'Source of truth','reuse-first':'Reuse first','block-scope':'One job per Step','capability-contract':'Define the capability first','compose':'Blocks compose','platform-primitives':'Use platform primitives','no-workarounds':'No hidden workarounds','naming':'Name it briefly','ui':'Interface','multiplayer':'Shared editing','external-services':'External services','dependencies':'Dependencies','security':'Security','lifecycle':'Execution lifecycle','errors':'Useful failures','adapt-to-platform':'Adapt to the platform','verification':'What StillMade verifies','project-types':'Project Types','readiness-report':'Readiness report'});
|
|
136
|
+
const SECTION_TITLES=Object.freeze({'grounded-text':'Only supplied facts','source-of-truth':'Source of truth','reuse-first':'Reuse first','block-scope':'One job per Step','capability-contract':'Define the capability first','feature-map':'Feature map','compose':'Blocks compose','platform-primitives':'Use platform primitives','no-workarounds':'No hidden workarounds','naming':'Name it briefly','ui':'Interface','multiplayer':'Shared editing','external-services':'External services','dependencies':'Dependencies','security':'Security','lifecycle':'Execution lifecycle','errors':'Useful failures','adapt-to-platform':'Adapt to the platform','verification':'What StillMade verifies','project-types':'Project Types','readiness-report':'Readiness report'});
|
|
134
137
|
const inScope=(scope,wanted)=>wanted==='all'||scope==='both'||scope===wanted;
|
|
135
138
|
|
|
136
139
|
/** Render the contract for a model prompt ('compact'), a guide ('full') or an SDK folder ('agents-md'). */
|
|
@@ -25,6 +25,7 @@ import {validateDesktopTools} from './desktop-tools.js';
|
|
|
25
25
|
import {isFileValue,isDocumentValue,isTableValue,isUrlValue,isDateValue,isColorValue} from './data-values.js';
|
|
26
26
|
import {networkDeclarations,connectionDeclarations} from './network.js';
|
|
27
27
|
import {triggerProblems} from './triggers.js';
|
|
28
|
+
import {featureMapErrors} from './feature-map.js';
|
|
28
29
|
const connectionsValid=entries=>{try{connectionDeclarations(entries);return true;}catch{return false;}};
|
|
29
30
|
const networkProblem=entries=>{try{networkDeclarations(entries);return '';}catch(error){return error.message;}};
|
|
30
31
|
const networkValid=entries=>!networkProblem(entries);
|
|
@@ -112,8 +113,9 @@ export function validateManifest(manifest, { builtIn = false } = {}) {
|
|
|
112
113
|
const check = (ok, path, message) => { if (!ok) errors.push({ path, message }); };
|
|
113
114
|
try { safeDocument(manifest); } catch (e) { return { valid: false, errors: [{ path: '$', message: e.message }] }; }
|
|
114
115
|
if (!plain(manifest)) return { valid: false, errors: [{ path: '$', message: 'Manifest must be an object' }] };
|
|
115
|
-
const allowed = ['schemaVersion', 'id', 'name', 'version', 'sdkVersion', 'description', 'kind', 'inputs', 'outputs', 'permissions', 'runtime', 'jobs', 'ui', 'uiLayout', 'entry', 'license', 'provenance', 'summary', 'category', 'platforms', 'state', 'storage', 'semantics', 'desktopTools', 'operations', 'sharedWork', 'triggers'];
|
|
116
|
+
const allowed = ['schemaVersion', 'id', 'name', 'version', 'sdkVersion', 'description', 'kind', 'inputs', 'outputs', 'permissions', 'runtime', 'jobs', 'ui', 'uiLayout', 'entry', 'license', 'provenance', 'summary', 'category', 'platforms', 'state', 'storage', 'semantics', 'desktopTools', 'operations', 'sharedWork', 'triggers', 'features'];
|
|
116
117
|
for (const key of Object.keys(manifest)) check(allowed.includes(key), key, 'Unknown manifest field');
|
|
118
|
+
for (const message of featureMapErrors(manifest)) check(false, 'features', message);
|
|
117
119
|
if(manifest.sharedWork!==undefined){try{validateSharedWorkContract(manifest.sharedWork);}catch(error){check(false,'sharedWork',error.message);}check(manifest.permissions?.project?.includes('work.shared'),'sharedWork','Declare the work.shared permission');}
|
|
118
120
|
if(manifest.permissions?.project?.includes('work.shared'))check(manifest.sharedWork!==undefined,'permissions.project','Declare the shared-work contract');
|
|
119
121
|
if(manifest.semantics!==undefined)for(const message of semanticDeclarationErrors(manifest.semantics))check(false,'semantics',message);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// A Block's feature map: the separate things it can do (an Editor's reaction
|
|
2
|
+
// GIFs, motion presets, screen recording), each with the requests it answers.
|
|
3
|
+
// The builder AI writes `manifest.features` while it builds; creators never
|
|
4
|
+
// write it by hand. Blocks without one get a map derived from their operations
|
|
5
|
+
// and outputs, so every Block can be matched feature by feature. Listings are
|
|
6
|
+
// generated from this map and are not editable. Trust comes from real use (see
|
|
7
|
+
// block-platform/feature-usage.js), never from a publish-time test run.
|
|
8
|
+
import {plain} from './contracts-core.js';
|
|
9
|
+
|
|
10
|
+
export const FEATURE_MAP_LIMITS=Object.freeze({features:24,id:48,label:40,does:160,asks:8,ask:80});
|
|
11
|
+
const FEATURE_ID=/^[a-z][a-z0-9-]{0,47}$/;
|
|
12
|
+
const FEATURE_KEYS=['id','label','does','asks','operation','inputs','outputs'];
|
|
13
|
+
|
|
14
|
+
/** Validation messages for `manifest.features` ([] when valid or absent). */
|
|
15
|
+
export function featureMapErrors(manifest){
|
|
16
|
+
const features=manifest?.features;
|
|
17
|
+
if(features===undefined)return [];
|
|
18
|
+
if(!Array.isArray(features)||features.length===0||features.length>FEATURE_MAP_LIMITS.features)return [`Declare 1–${FEATURE_MAP_LIMITS.features} features`];
|
|
19
|
+
const errors=[],ids=new Set(),operations=new Set((Array.isArray(manifest.operations)?manifest.operations:[]).map(operation=>operation?.id));
|
|
20
|
+
const text=(value,max)=>typeof value==='string'&&value.trim().length>0&&value.length<=max;
|
|
21
|
+
for(const [index,feature] of features.entries()){
|
|
22
|
+
const at=`features.${plain(feature)&&typeof feature.id==='string'?feature.id:index}`;
|
|
23
|
+
if(!plain(feature)){errors.push(`${at}: Expected an object`);continue;}
|
|
24
|
+
const unknown=Object.keys(feature).filter(key=>!FEATURE_KEYS.includes(key));
|
|
25
|
+
if(unknown.length)errors.push(`${at}: Unknown feature field ${unknown.join(', ')}`);
|
|
26
|
+
if(!FEATURE_ID.test(feature.id||'')||ids.has(feature.id))errors.push(`${at}.id: Use a unique lowercase id such as "reaction-gifs"`);
|
|
27
|
+
ids.add(feature.id);
|
|
28
|
+
if(!text(feature.label,FEATURE_MAP_LIMITS.label))errors.push(`${at}.label: Name the feature in ${FEATURE_MAP_LIMITS.label} characters or fewer`);
|
|
29
|
+
if(!text(feature.does,FEATURE_MAP_LIMITS.does))errors.push(`${at}.does: Say what it does in ${FEATURE_MAP_LIMITS.does} characters or fewer`);
|
|
30
|
+
if(!Array.isArray(feature.asks)||feature.asks.length===0||feature.asks.length>FEATURE_MAP_LIMITS.asks||!feature.asks.every(ask=>text(ask,FEATURE_MAP_LIMITS.ask)))errors.push(`${at}.asks: Give 1–${FEATURE_MAP_LIMITS.asks} example requests of up to ${FEATURE_MAP_LIMITS.ask} characters`);
|
|
31
|
+
if(feature.operation!==undefined&&!operations.has(feature.operation))errors.push(`${at}.operation: Name a declared operation`);
|
|
32
|
+
for(const direction of ['inputs','outputs']){
|
|
33
|
+
if(feature[direction]===undefined)continue;
|
|
34
|
+
const ports=feature[direction];
|
|
35
|
+
if(!Array.isArray(ports)||ports.length>16||!ports.every(port=>typeof port==='string'&&Object.hasOwn(manifest[direction]||{},port)))errors.push(`${at}.${direction}: Name declared ${direction} only`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const slug=value=>String(value||'').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'').slice(0,FEATURE_MAP_LIMITS.id).replace(/^[^a-z]+/,'')||'main';
|
|
42
|
+
const clip=(value,max)=>{const text=String(value||'').replace(/\s+/g,' ').trim();return text.length<=max?text:text.slice(0,max-1).trimEnd()+'…';};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The Block's feature map, always non-empty for a valid manifest. Declared
|
|
46
|
+
* features are used as written; otherwise each operation becomes a feature,
|
|
47
|
+
* and a Block with no operations is one feature named after the Block.
|
|
48
|
+
* Every entry carries `source`: 'declared' or 'derived'.
|
|
49
|
+
*/
|
|
50
|
+
export function blockFeatures(manifest){
|
|
51
|
+
if(!plain(manifest))return [];
|
|
52
|
+
if(Array.isArray(manifest.features)&&manifest.features.length&&featureMapErrors(manifest).length===0)
|
|
53
|
+
return manifest.features.map(feature=>({...feature,asks:[...feature.asks],source:'declared'}));
|
|
54
|
+
const operations=Array.isArray(manifest.operations)?manifest.operations.filter(plain):[];
|
|
55
|
+
if(operations.length)return operations.map(operation=>({id:slug(operation.id),label:clip(operation.label,FEATURE_MAP_LIMITS.label),does:clip(operation.description||operation.label,FEATURE_MAP_LIMITS.does),asks:[clip(operation.label,FEATURE_MAP_LIMITS.ask)],operation:operation.id,source:'derived'}));
|
|
56
|
+
const outputs=Object.keys(plain(manifest.outputs)?manifest.outputs:{});
|
|
57
|
+
return [{id:'main',label:clip(manifest.name||manifest.id,FEATURE_MAP_LIMITS.label),does:clip(manifest.description||manifest.name,FEATURE_MAP_LIMITS.does),asks:[clip(manifest.description||manifest.name,FEATURE_MAP_LIMITS.ask)],...(outputs.length?{outputs:outputs.slice(0,16)}:{}),source:'derived'}];
|
|
58
|
+
}
|
|
@@ -21,8 +21,17 @@ export interface FrameView { runtime: 'frame'; html: string; css?: string; theme
|
|
|
21
21
|
export interface BlockPackage { portable?: PortableMetadata; manifest: Manifest; view?: BlockView|FrameView; code?: string; module?: ModuleDescriptor; comfyui?: ComfyWorkflow; capability?: ConnectedCapability|TextGenerationCapability|SpeechCapability|TranscriptionCapability|ImageGenerationCapability|VideoGenerationCapability|ImageDescriptionCapability|TimelineProposalCapability; recipe?: { schemaVersion: 1; steps: { id: string; op: string; args: Record<string, unknown> }[]; outputs: Record<string, unknown> }; tests: { name: string; input: Record<string, unknown>; expected?: Record<string, unknown>; state?: BlockState; expectedState?: BlockState; expectations?: Record<string, ConnectedResultExpectation|ComfyImageExpectation|CapabilityTextExpectation|CapabilityAudioExpectation|CapabilityTranscriptExpectation|CapabilityImageExpectation|CapabilityVideoExpectation|CapabilityTimelineExpectation> }[]; }
|
|
22
22
|
/** Named sandbox action with fixed declared input settings and produced ports. */
|
|
23
23
|
export interface BlockOperation {id:string;label:string;description:string;inputs:Record<string,string|number|boolean>;outputs:string[]}
|
|
24
|
+
/** One entry of a Block's feature map: a separate thing the Block can do and the requests it answers. */
|
|
25
|
+
export interface BlockFeature {id:string;label:string;does:string;asks:string[];operation?:string;inputs?:string[];outputs?:string[]}
|
|
26
|
+
export const FEATURE_MAP_LIMITS: Readonly<{features:24;id:48;label:40;does:160;asks:8;ask:80}>;
|
|
27
|
+
/** Validation messages for `manifest.features`; [] when valid or absent. */
|
|
28
|
+
export function featureMapErrors(manifest:unknown):string[];
|
|
29
|
+
/** The declared feature map, or one derived from operations or the Block itself. */
|
|
30
|
+
export function blockFeatures(manifest:unknown):(BlockFeature&{source:'declared'|'derived'})[];
|
|
24
31
|
export interface Manifest {
|
|
25
32
|
operations?: BlockOperation[];
|
|
33
|
+
/** Feature map: what the Block can do, tool by tool. Written by the builder; listings are generated from it. */
|
|
34
|
+
features?: BlockFeature[];
|
|
26
35
|
storage?: import('./block-storage.js').BlockStorageDeclaration;
|
|
27
36
|
/** Events the Block can run on by itself, once the project owner turns them on. */
|
|
28
37
|
triggers?: import('./triggers.js').BlockTrigger[];
|
|
@@ -6,6 +6,7 @@ export * from './workflow-events.js';
|
|
|
6
6
|
export * from './workflow-approvals.js';
|
|
7
7
|
export * from './package-semantics.js';
|
|
8
8
|
export {defineBlock} from './define-block.js';
|
|
9
|
+
export {blockFeatures,featureMapErrors,FEATURE_MAP_LIMITS} from './feature-map.js';
|
|
9
10
|
export * from './adapter-registry.js';
|
|
10
11
|
export * from './desktop-permissions.js';
|
|
11
12
|
export * from './desktop-tools.js';
|