Action Library How-To

Reference guide for building .sheevactions manifests for SheevChat

Not sure where to start? Use the Action Builder to answer a few questions and get a ready-to-import skeleton manifest for your app.

Open Action Builder →

Choose Your Workflow

  • New to manifests: copy the AI prompt and this guide's rules into your preferred LLM with the target app's API documentation.
  • Some API experience: use the Builder to create and partially complete a draft, then copy its AI handoff to resolve the remaining TODO values.
  • Experienced author: use the Builder workbench for live JSON, source maps, payloads, response ingestion, validation, import, and export.

Quick Rules for Authors and AI Agents

When creating a manifest, output only valid JSON. Do not include Markdown fences, comments, trailing commas, or invented fields. Use SheevChat's schema rather than a generic action schema; if an API cannot fit the supported schema, document the runtime support that would be required.

  • sources is an object keyed by source id, never an array.
  • Source rows, value, label, and extra belong inside source.map.
  • Every action must have an execute.type.
  • Placeholders are flat: use {scene_id}, not {scene.id}.
  • Channel WebSocket responses are wrapped as { "channel": "...", "payload": ... }.
  • Instance, endpoint-file, and pipeline discovery settings belong inside the connection's discovery object.
  • Hello/register setup uses connection.preflight; reusable token or authenticated-session flows use connection.auth.
  • JSON-RPC protocol envelopes belong inside message, and awaited responses should be matched by request ID.

Overview

Action libraries let you connect any local app to SheevChat's automation system. Once installed, the app's actions appear inside Custom Commands, Automations, Timers, and SheevPad — no custom code required.

Each library is a single JSON file describing how SheevChat connects to the app, what dynamic data it can fetch (for dropdowns), and which actions the user can trigger.

File Format

Action libraries can be imported as a plain JSON manifest with the extension .sheevactions, or as a packaged .sheevactionszip library.

action-libraries/installed/<library-id>/manifest.json

A packaged library is a zip containing manifest.json or one root-level .sheevactions file. When both are present, manifest.json wins. A package may also include an optimized .png, .jpg, .jpeg, .webp, .svg, or .gif brand icon; SheevChat selects the first compatible image and records its relative path.

my-library.sheevactionszip
  manifest.json
  brand-icon.png
Package paths must stay inside the package. Do not use absolute paths, drive letters, or ../. If there is no manifest.json, include only one root-level .sheevactions file.

Only schemaVersion: 1 is accepted.

Top-Level Fields

{
  "schemaVersion": 1,
  "id": "my-app",
  "name": "My App",
  "version": "1.0.0",
  "publisher": "Your Name",
  "category": "My App Controls",
  "description": "Adds My App actions to SheevChat.",
  "homepage": "https://example.com/docs",
  "icon": "",
  "trust": "third-party",
  "permissions": ["local_websocket"],
  "compatibility": {},
  "connections": [],
  "sources": {},
  "actions": []
}
FieldRequiredTypeNotes
schemaVersionYesnumberMust be 1.
idYesstringLowercase letters, numbers, hyphens, underscores. 3–64 chars. Must start and end with a letter or number.
nameYesstringUser-facing library name. Max 120 chars.
versionYesstringSemantic version e.g. 1.0.0, 1.2, 1.0.0-beta.1.
publisherNostringShown as "Category by Publisher". Max 120 chars.
categoryNostringUser-facing action category. Defaults to Community. Max 80 chars.
descriptionNostringLibrary summary. Max 700 chars.
homepageNostringDocumentation URL. Max 500 chars.
iconNostringRelative packaged icon path. Max 500 chars.
trustNostringFreeform label such as third-party, community, or local.
permissionsNoarraySee Permissions.
compatibilityNoobjectFreeform metadata; currently stored but not enforced.
connectionsNoarrayConnection definitions. See Connections.
sourcesNoobjectDynamic option sources keyed by source id. Must be an object, not an array.
actionsYesarrayMust contain at least one action.

ID Rules

All IDs — manifest, connection, source, action, field key, extra key — follow the same rule:

^[a-z0-9][a-z0-9_-]{1,62}[a-z0-9]$
GoodBad
vtube-studioVTubeStudio — uppercase not allowed
trigger_hotkeytrigger.hotkey — dots not allowed
state-node-id_hidden — cannot start with underscore
my-app-2id! — special characters not allowed

Permissions

PermissionMeaning
local_httpManifest may call local HTTP APIs.
local_websocketManifest may call local WebSocket APIs.
remote_networkAllows non-local HTTP/WebSocket hosts. Without this, remote URLs are rejected.
sheevchat_commandManifest may trigger a SheevChat command.
oauthReserved. Imported OAuth runtime not yet complete.
hotkeyReserved for hotkey packs.
launch_applicationReserved for launch actions.
write_fileReserved for file-writing actions.

For most local app packs use "permissions": ["local_websocket"] or "permissions": ["local_http"]. Only add remote_network if the manifest intentionally calls an internet host.

Connections

Connections describe how SheevChat talks to the target app.

FieldRequiredTypeNotes
idYesstringStable connection id. Usually local.
labelNostringUser-facing label. Defaults to id.
typeNostringSee connection types below. Defaults to none.
defaultUrlNostringBase URL e.g. ws://127.0.0.1:8001 or http://127.0.0.1:4567.
fallbackUrls / urlsNoarrayOrdered alternate local URLs tried after defaultUrl.
discoveryNoobjectinstance_file, endpoint_file, or safe response-based pipeline discovery.
preflightNoobjectHello, registration, or setup request sent before sources/actions without requiring an auth result.
authNoobjectLocal token/session authentication.
websocketNoobjectSafe WebSocket handshake settings. Use origin only when documented.
testNoobjectConnection test settings.
Connection TypeBehavior
noneNo app connection required. Status defaults to connected.
local_httpUses fetch against a local HTTP API.
local_websocketOpens a WebSocket, sends a message, optionally waits for response.
oauthRecognized but not fully supported for imported packs yet.
hotkeyRecognized as no app connection required.

Local WebSocket example:

{
  "id": "local",
  "label": "My App",
  "type": "local_websocket",
  "defaultUrl": "ws://127.0.0.1:8001",
  "test": { "url": "ws://127.0.0.1:8001" }
}

Local HTTP example:

{
  "id": "local",
  "label": "My App",
  "type": "local_http",
  "defaultUrl": "http://127.0.0.1:4567",
  "test": { "url": "/health", "method": "GET" }
}

Connection Preflight, Fallback URLs, and Response Matching

Use connection.preflight for a hello, registration, or public client-key message that must run before normal source and action requests but does not return a reusable token or authenticated status. Use connection.auth when the app returns a token or an authentication result. A connection may use both; preflight runs first.

"fallbackUrls": [
  "ws://127.0.0.1:20000/v1/",
  "ws://127.0.0.1:39273/v1/"
],
"preflight": {
  "beforeSources": true,
  "beforeActions": true,
  "awaitResponse": false,
  "request": {
    "action": "registerClient",
    "payload": { "clientKey": "public-client-key" }
  }
}

Use fallbackUrls for a small documented set of local ports. Use discovery when the port is arbitrary. When a WebSocket can emit greetings, acknowledgements, events, or unrelated frames, add responseMatch so SheevChat waits for the intended response.

"responseMatch": { "$.actionType": "getVoices" }
WebSocket headers are restricted. Use connection.websocket.origin only when the API documents an Origin requirement. Do not add Origin, Host, Connection, Upgrade, or Sec-WebSocket-* through generic headers.

Instance File Discovery

Use instance file discovery when the app writes local JSON files that tell clients which port/instance is currently active. This replaces a fixed defaultUrl.

{
  "id": "local",
  "label": "My App",
  "type": "local_websocket",
  "discovery": {
    "type": "instance_file",
    "directory": ".my-app/instances",
    "map": {
      "time":     "$.time",
      "id":       "$.id",
      "server":   "$.server",
      "name":     "$.name",
      "version":  "$.version",
      "language": "$.language"
    },
    "clientName": "SheevChat",
    "staleMs": 10000
  }
}
FieldRequiredNotes
typeYesMust be instance_file.
directoryYesPath relative to user home directory.
mapYesJSONPath mappings from instance file fields.
clientNameNoWritten into the instance file so the app knows SheevChat is connected.
staleMsNoHow old an instance file can be before it's considered stale.
preferredTypeNoPreferred instance type if multiple exist.
Discovery paths are already resolved under the user's home directory. Use a home-relative value such as .veadotube/instances. Never use %USERPROFILE%, $HOME, ~, an absolute path, or a drive letter.

Endpoint-File and Pipeline Discovery

Use endpoint_file when one stable JSON file contains the current port or WebSocket URL. The file must remain under the home or localAppData safe root.

"discovery": {
  "type": "endpoint_file",
  "root": "localAppData",
  "file": "Packages/Example.App/LocalState/ws-info.json",
  "map": { "port": "$.port" },
  "urlTemplate": "ws://127.0.0.1:{port}"
}

Use pipeline when a safe local bootstrap file points to a loopback discovery service that returns the final app endpoint.

"discovery": {
  "type": "pipeline",
  "cacheMs": 3000,
  "steps": [
    {
      "type": "json_file",
      "base": "programData",
      "file": "Vendor/App/coreProps.json",
      "map": { "service_address": "$.address" }
    },
    {
      "type": "http_request",
      "method": "GET",
      "urlTemplate": "https://{service_address}/endpoints",
      "allowSelfSignedLoopback": true,
      "map": { "service_url": "$.service.url" },
      "readiness": [{ "key": "service_url", "message": "The service is not ready." }]
    }
  ],
  "result": "{service_url}"
}

Pipeline file roots are home, localAppData, and programData. Files must be relative and may not contain environment variables, absolute paths, drives, UNC paths, or ... Pipeline HTTP calls and redirects must stay on loopback. allowSelfSignedLoopback applies only to loopback HTTPS discovery.

Set selectionMode to exact_or_newest, exact, newest, oldest, or ordinal. Ordinal mode may use a 1-based instanceSlot. Explicit instance dropdowns should retain instance_selection_mode: "exact" so an action does not silently switch targets.

Local App Authentication

Use connection.auth when a local app requires a token or session handshake before sources and actions can work. This is for local HTTP/WebSocket app APIs — not platform OAuth. SheevChat stores returned tokens locally in the user's config, keyed by library and connection.

Typical flow:

  1. User expands Action Libraries and clicks Test to confirm the app is reachable.
  2. User clicks Authenticate.
  3. SheevChat sends the token request, maps the returned token, stores it locally, then sends the session request.
  4. Future dropdowns and actions automatically run the session request first when required.
FieldRequiredTypeNotes
typeNostringMetadata. Use local_session unless a more specific label helps.
labelNostringUser-facing auth label shown in connection details.
buttonLabelNostringButton text. Defaults to Authenticate.
instructionsNostringShort user-facing note, e.g. "Approve the prompt in the target app."
tokenKeyNostringStored token field name. Defaults to token.
persistNobooleanDefaults to true. Set false for one-time session data.
statusRequestNoobjectOptional request to check if already authenticated.
statusMapNoobjectMaps response from statusRequest. e.g. {"authenticated": "$.data.authenticated"}.
tokenRequestNoobjectRequest used to obtain a token.
tokenMapNoobjectMaps response from tokenRequest. e.g. {"token": "$.data.authenticationToken"}.
tokenPathNostringShortcut for tokenMap.token.
sessionRequestNoobjectRequest to authenticate the current session/socket using the saved token.
sessionMapNoobjectMaps response from sessionRequest. e.g. {"authenticated": "$.data.authenticated"}.
authenticatedPathNostringShortcut for sessionMap.authenticated.
injectNoobjectControls automatic session preflight. Both default to true.

At least one of statusRequest, tokenRequest, or sessionRequest must be present.

Auth response truthiness. For statusMap.authenticated or sessionMap.authenticated, SheevChat treats these values as authenticated: true, 1, "true", "1", "yes", "authenticated", "connected", "ok", "success". Anything else is not authenticated.

WebSocket Token + Session Example

Use this shape for apps that first return a reusable token, then require the token to authenticate each WebSocket session:

{
  "id": "local",
  "label": "Example Local App",
  "type": "local_websocket",
  "defaultUrl": "ws://127.0.0.1:8001",
  "auth": {
    "type": "local_session",
    "label": "Local app authentication",
    "buttonLabel": "Authenticate",
    "instructions": "Approve the prompt in the target app if one appears.",
    "tokenKey": "token",
    "statusRequest": {
      "messageType": "APIStateRequest"
    },
    "statusMap": {
      "authenticated": "$.data.currentSessionAuthenticated"
    },
    "tokenRequest": {
      "messageType": "AuthenticationTokenRequest",
      "data": {
        "pluginName": "SheevChat",
        "pluginDeveloper": "SheevChat"
      }
    },
    "tokenMap": {
      "token": "$.data.authenticationToken"
    },
    "sessionRequest": {
      "messageType": "AuthenticationRequest",
      "data": {
        "pluginName": "SheevChat",
        "pluginDeveloper": "SheevChat",
        "authenticationToken": "{auth.token}"
      }
    },
    "sessionMap": {
      "authenticated": "$.data.authenticated"
    },
    "inject": {
      "beforeSources": true,
      "beforeActions": true
    }
  }
}

Auth Placeholders

PlaceholderMeaning
{auth.token}Saved token when tokenKey is token. Prefer this form — it makes the source obvious.
{token}Convenience alias for the same saved token.
{auth.some_key}Saved auth value from tokenMap.some_key.
{some_key}Convenience alias for the same saved value.

Auth Preflight

When inject.beforeSources is true, SheevChat sends sessionRequest before any connection_request source request — so authenticated dropdowns populate correctly.

When inject.beforeActions is true, SheevChat sends sessionRequest before an imported action runs.

For WebSocket APIs, the preflight and real request are sent on the same short-lived WebSocket connection. For HTTP APIs, SheevChat sends the preflight first, then the real request.

WebSocket Message Formats

SheevChat supports plain JSON, channel-wrapped JSON, and JSON-RPC WebSocket messages. Choosing the wrong envelope is a common integration failure.

Plain JSON WebSocket Messages

Some apps send and receive plain JSON objects. In this case, do not add a channel key — SheevChat sends the JSON object as-is:

"request": {
  "messageType": "ListHotkeysRequest",
  "data": {
    "modelID": "{model_id}"
  }
}

Response paths start directly at the root: $.data.hotkeys[]

Channel-Wrapped WebSocket Messages

Other apps prefix messages with a channel name:

nodes: {"event":"list"}

Represent these with a channel key:

"request": {
  "channel": "nodes",
  "payload": {
    "event": "list"
  }
}

SheevChat wraps the response as {"channel": "nodes", "payload": {...}} so all map paths must include $.payload:

"map": {
  "rows": "$.payload.entries[]",
  "value": "{id}",
  "label": "{name}"
}

For nested payloads where the app replies with nodes: {"event":"payload","payload":{"states":[...]}}, use:

"rows": "$.payload.payload.states[]"
Plain JSON vs channel-wrapped. If your map paths start with $.data or $.items the app uses plain JSON. If they need to start with $.payload the app uses channel messages. Mixing these up is the most common WebSocket mapping mistake.

JSON-RPC WebSocket Messages

For JSON-RPC, put the complete protocol envelope inside request.message or execute.message. A root-level method is transport metadata and will not be sent as part of the socket message.

"request": {
  "message": {
    "id": 101,
    "jsonrpc": "2.0",
    "method": "getItems",
    "params": {}
  },
  "responseMatch": { "$.id": 101 }
}

Awaited JSON-RPC sources and actions should match the echoed request ID so notifications cannot be mistaken for the response.

Sources

Sources power dropdowns and must be an object keyed by source id. Supported source types are static_options, instance_file, and connection_request. A source may define connectionId, dependsOn, request, map, options, and cacheMs as appropriate.

Sources power dynamic dropdowns in action fields. sources must be an object keyed by source id — never an array.

Static Source

Hard-coded list of options. No app request needed.

"sources": {
  "blend_modes": {
    "type": "static_options",
    "options": [
      { "value": "normal",  "label": "Normal" },
      { "value": "screen",  "label": "Screen" },
      { "value": "overlay", "label": "Overlay" }
    ]
  }
}

Source Mapping

A connection-request source maps an API response into dropdown options. Put all mapping fields inside map. Use rows for an array path, value for the stored option value, label for visible text, and extra for stable hidden values exposed as flat placeholders.

"scenes": {
  "type": "connection_request",
  "connectionId": "local",
  "request": { "messageType": "SceneListRequest" },
  "map": {
    "rows": "$.data.scenes[]",
    "value": "{id}",
    "label": "{name}",
    "extra": {
      "scene_id": "$.id",
      "scene_name": "$.name"
    }
  },
  "cacheMs": 1000
}

Supported map paths use a practical JSONPath subset: $, dot properties, bracket properties, numeric indexes, and a final [] for row iteration. General recursive descent, arbitrary wildcards, unions, slices, and script expressions are not supported.

Connection Request Source

Fetches options from the app at runtime. The map block is required and must be inside the source — not at the source level.

"sources": {
  "scenes": {
    "type": "connection_request",
    "connectionId": "local",
    "cacheMs": 3000,
    "request": {
      "channel": "scenes",
      "payload": { "event": "list" }
    },
    "map": {
      "rows":  "$.payload.scenes[]",
      "value": "{id}",
      "label": "{name}",
      "extra": {
        "scene_id":   "$.id",
        "scene_name": "$.name"
      }
    }
  }
}

Cascading Source

A source that depends on the selection of a previous field. Use dependsOn to reference the parent field key.

"sources": {
  "models": {
    "type": "connection_request",
    "connectionId": "local",
    "cacheMs": 3000,
    "request": {
      "channel": "models",
      "payload": { "event": "list" }
    },
    "map": {
      "rows":  "$.payload.models[]",
      "value": "{id}",
      "label": "{name}",
      "extra": { "model_id": "$.id", "model_name": "$.name" }
    }
  },
  "hotkeys": {
    "type": "connection_request",
    "connectionId": "local",
    "dependsOn": ["model"],
    "cacheMs": 3000,
    "request": {
      "channel": "hotkeys",
      "payload": { "event": "list", "modelId": "{model_id}" }
    },
    "map": {
      "rows":  "$.payload.hotkeys[]",
      "value": "{id}",
      "label": "{name}",
      "extra": { "hotkey_id": "$.id", "hotkey_name": "$.name" }
    }
  }
}

Nested Rows, Filtering, and Cache-Safe Dependencies

When parent rows contain nested child arrays, use map.childRows. Retain both parent and child identifiers through map.extra.

"map": {
  "rows": "$.devices[]",
  "childRows": "$.inputs[]",
  "value": "{device_id}:{input_id}",
  "label": "{device_name} — {input_name}",
  "extra": {
    "device_id": "$.parent.id",
    "device_name": "$.parent.name",
    "input_id": "$.id",
    "input_name": "$.name"
  }
}

map.where is an exact scalar filter: "where": { "type": "boolean" }. Do not invent operator objects such as { "path": "$.type", "equals": "boolean" }.

Every value that can change a source request or result belongs in source.dependsOn. For per-instance cascading data, include both instance and the selected parent so cached options cannot leak across app instances.

Instance File Source

Used alongside instance file discovery to populate an instance picker dropdown.

"sources": {
  "instances": {
    "type": "instance_file",
    "connectionId": "local",
    "cacheMs": 1000
  }
}

Actions

Each action is one thing the user can trigger. Every action must have an execute block with a valid type.

{
  "id": "set-scene",
  "label": "Set Scene",
  "description": "Switches to a specific scene.",
  "connectionId": "local",
  "contexts": ["timer", "automation", "command", "pad"],
  "fields": [
    {
      "key": "scene",
      "label": "Scene",
      "type": "dynamic_select",
      "source": "scenes",
      "labelKey": "scene_name"
    }
  ],
  "execute": {
    "type": "local_websocket",
    "connectionId": "local",
    "channel": "scenes",
    "payload": {
      "event": "switch",
      "id": "{scene_id}"
    }
  }
}
FieldRequiredNotes
idYesUnique action id following ID rules.
labelYesUser-facing action name.
descriptionNoShown in action picker tooltip.
connectionIdNoReferences a connection. Must exist in connections.
contextsNoWhere action appears. Use ["timer", "automation", "command", "pad"] for all.
fieldsNoUser-configurable inputs. See Field Types.
executeYesWhat SheevChat does when the action runs. Must include type.

Field Types

TypeDescription
textFree-text input. Value available as {key} in execute payload.
numberNumeric input. Use when the API requires a real number, not a string.
checkboxBoolean input. Use checkbox, never toggle.
textareaMulti-line text input.
colorColor picker.
fileFile path input.
hotkeyHotkey capture input.
hiddenHidden value available to placeholders.
infoInformational, non-input content.
selectStatic dropdown. Provide options array inline.
dynamic_selectDropdown populated from a source. Requires source to reference a source id.

Static select options can use valueJson when the API needs a real boolean or number. For a remembered toggle-state control, include the string "toggle" plus JSON booleans true and false:

"options": [
  { "value": "toggle", "label": "Toggle" },
  { "valueJson": true, "label": "Enabled" },
  { "valueJson": false, "label": "Disabled" }
]

With an exact placeholder, explicit choices send native booleans. The Toggle choice alternates remembered state per action target during the current runtime session. Use a stable target ID, and remember this is not a guaranteed read of external app state.

Placeholders in execute payloads are always flat: {scene_id} not {scene.id}. Hidden stable values from dynamic dropdowns come from the extra block in the source map.

Execute Types

local_websocket

"execute": {
  "type": "local_websocket",
  "connectionId": "local",
  "channel": "scenes",
  "payload": {
    "event": "switch",
    "id": "{scene_id}"
  }
}

WebSocket responses are wrapped as {"channel": "...", "payload": ...}. Source map paths should start with $.payload.

local_http

"execute": {
  "type": "local_http",
  "connectionId": "local",
  "url": "/api/scenes/switch",
  "method": "POST",
  "body": {
    "sceneId": "{scene_id}"
  }
}

Relative URLs are resolved against the connection's defaultUrl.

sheevchat_command

"execute": {
  "type": "sheevchat_command",
  "command": "!sr {song_title}"
}

Runs a SheevChat command as if typed in chat. Requires "permissions": ["sheevchat_command"].

Interpolation and Response Ingestion

SheevChat recursively interpolates strings in request URLs, headers, bodies, WebSocket messages, and command strings. A string that consists only of a placeholder preserves the original value type, so "enabled": "{enabled}" can become a real boolean. A placeholder embedded in other text always becomes a string.

{
  "body": {
    "enabled": "{enabled}",
    "count": "{count}",
    "label": "Scene: {scene_name}"
  }
}

For boolean APIs use a checkbox and an exact placeholder occupying the complete property value. Never invent a toggle field type. Exact placeholders preserve native booleans, numbers, arrays, and objects; placeholders embedded in surrounding text always produce text.

HTTP and WebSocket actions may define responseMap. Later sequence steps can use {last.key}, {action_id.key}, or a stable responseNamespace such as {obs.current_scene}. Use responseTemplate only when the action itself should send a chat response.

"query": true,
"awaitResponse": true,
"responseNamespace": "obs",
"responseMap": {
  "active_scene": { "path": "$.data.activeScene", "default": "Unknown" },
  "streaming": { "path": "$.data.streaming", "default": false }
},
"outputLabels": { "active_scene": "Active scene", "streaming": "Streaming" },
"outputDescriptions": { "active_scene": "Current scene name.", "streaming": "Whether streaming is active." },
"outputTypes": { "active_scene": "string", "streaming": "boolean" },
"responseTemplate": "Active scene: {active_scene}"

Related queries should share one short integration namespace. Defaults must agree with outputTypes, and produced values should remain compact—avoid unbounded lists, logs, binary content, or large base64 payloads unless bulk retrieval is the explicit advanced purpose.

Common Mistakes

sources must be an object. It is keyed by source id — never an array.
map belongs inside the source. Do not place rows, value, label, or extra directly on the source. They must be inside source.map.
execute.type is required. Every action's execute block must include a type field.
Mapped field placeholders are flat. Use {scene_id}, not an invented object path such as {scene.id}. Documented namespaces such as {auth.token}, {last.amount}, and {obs.current_scene} are supported.
WebSocket response paths include $.payload. SheevChat wraps channel responses as {"channel": "...", "payload": ...} so source map paths start with $.payload.
Plain JSON vs channel-wrapped WebSocket. If your app uses plain JSON messages, do not add channel — SheevChat sends the object as-is and response paths start at $. If your app uses channel-prefixed messages, use channel + payload and all map paths must include $.payload.
Auth placeholders use {auth.token}. Prefer {auth.token} over {token} in manifests — it makes the source of the value explicit.
connection.auth is for local app auth only. Do not use connection.auth for platform OAuth (Twitch, YouTube, etc.) — those use a different system entirely.
discovery settings go inside discovery. Do not place instance discovery fields alongside discovery — they must be inside it.
Booleans and numbers must stay real JSON types. If the API requires true or 42, do not interpolate them as strings via placeholders.

API Analysis Checklist

Before writing a manifest from an application's API documentation, identify the transport and runtime requirements:

  • Is the API local HTTP, local WebSocket, or remote network?
  • Is its port fixed, or discovered from an instance file?
  • Could a small fallback URL list work, or does it require an endpoint file or discovery pipeline?
  • Does it require a hello/register preflight, authentication, or both?
  • Can unrelated WebSocket frames arrive before the requested response, requiring responseMatch?
  • Is the protocol JSON-RPC, and if so, what stable request ID should correlate each response?
  • Does it require a reusable token or per-session authentication?
  • Are WebSocket messages plain JSON or channel-wrapped?
  • Which requests return lists suitable for dynamic dropdowns?
  • Which stable identifiers should be placed in map.extra?
  • Are there cascading sources where one selection depends on another?
  • Do parent rows contain nested child arrays, and which dependencies must vary the source cache?
  • Which API values must remain real booleans or numbers?
  • Does any requirement fall under the known unsupported runtime features?

AI Prompt Template

Use this prompt with the target application's API documentation, then review and test the result before importing it.

Create, verify, correct, expand to cover any missing supported actions, and/or finish a SheevChat Action Library manifest using the authoritative API documentation below.

Return one import-ready .sheevactions file containing only valid JSON with schemaVersion 1. Do not use Markdown fences, comments, trailing commas, or invented keys.

Hard rules:
- sources is an object keyed by source id; put rows, value, label, extra, and where inside source.map
- execute.type is required and must be local_websocket, local_http, or sheevchat_command
- connection.type must be local_websocket, local_http, or none
- field.type must be text, textarea, number, select, dynamic_select, checkbox, color, file, hotkey, hidden, or info; use checkbox, never toggle
- select options may use valueJson for native booleans/numbers; remembered toggle-state selects use string "toggle" plus valueJson true and false and require a stable target ID
- contexts may only contain timer, automation, command, and pad
- transforms may only be first, string, text, number, boolean, join, and json
- local_http methods may only be GET, POST, or PUT
- instance_file, endpoint_file, and pipeline belong inside connection.discovery
- instance directories are home-relative; endpoint files use home or localAppData; pipeline files use home, localAppData, or programData; never use environment variables, absolute paths, drives, UNC paths, or parent traversal
- pipeline HTTP discovery stays on loopback; allowSelfSignedLoopback is only for loopback HTTPS discovery
- use fallbackUrls for a small set of documented local ports
- use connection.preflight for hello/register setup that does not return a token or authenticated status; it may coexist with auth
- use connection.websocket.origin only for a documented Origin requirement; never add restricted WebSocket handshake headers
- local app authentication belongs inside connection.auth
- extraction values may be path strings, fallback path arrays, or objects using path/paths, default, transform, and separator
- use map.extra for hidden stable values; filter mixed resource types with map.where or separate typed sources
- map.where values are direct scalar exact matches, never invented path/equals operator objects
- use map.childRows for nested child arrays and preserve both parent and child identifiers in map.extra
- include every cache-varying field in source.dependsOn, including instance for per-instance cascading sources
- option values must be stable and collision-safe; use {type}:{id} when ids can overlap and preserve raw values in map.extra
- channel-wrapped WebSocket paths normally begin at $.payload; plain JSON messages do not use channel
- add responseMatch when WebSocket greetings, acknowledgements, notifications, or unrelated events can arrive before the intended response
- JSON-RPC envelopes belong inside request.message or execute.message and awaited calls match the echoed id
- dynamic-port actions put the instance field first
- placeholders are flat; auth uses {auth.token}; an exact placeholder preserves its native type and recursively resolved values such as "{last.amount}", while surrounding text forces a string
- every query action sets awaitResponse: true, query: true, responseNamespace, responseMap, outputLabels, outputDescriptions, and outputTypes
- related queries share a stable namespace; output defaults agree with outputTypes; produced values remain compact and avoid unbounded or binary data
- prefer one friendly operation dropdown for general users; use separate advanced actions when that materially improves clarity
- use responseTemplate only when the action should send its own chat response

Before output, verify every enum is allowed; every connectionId, source, dependsOn, labelKey, placeholder, and response path resolves; typed actions use filtered sources; values are stable and collision-safe; queries wait and document typed outputs; and no unsupported method, path form, or invented key remains.

If the API requires unsupported runtime behavior, omit the unsupported action and explain the gap outside the file rather than inventing schema keys.

API documentation:
[paste documentation here]

Validation Checklist

  • schemaVersion is 1
  • All id values use only lowercase letters, numbers, hyphens, and underscores
  • sources is an object, not an array
  • Every dynamic_select field's source exists in sources
  • Every field dependsOn references another field in the same action
  • Every connectionId exists in connections
  • Every execute has a valid type
  • Every field uses an allowed type; boolean inputs use checkbox, never toggle
  • Connections, contexts, transforms, and HTTP methods use only their documented allowlists
  • Discovery directories are home-relative and contain no environment variable, home alias, absolute path, or drive letter
  • Endpoint and pipeline files use approved safe roots and relative paths; pipeline requests stay on loopback
  • Hello/register messages use connection.preflight; token/session flows use connection.auth
  • WebSocket JSON-RPC envelopes are nested inside message and awaited responses are matched by ID
  • Source maps are inside map, not at the source level
  • Mixed resource sources are filtered by type, and option IDs are collision-safe
  • where values are direct scalars, nested children use childRows, and every cache-varying field appears in source.dependsOn
  • Channel WebSocket response paths include $.payload
  • Nested channel payload paths use $.payload.payload when needed
  • Mapped field placeholders are flat; dotted placeholders use documented auth or response namespaces only
  • Boolean/numeric API values are not accidentally receiving interpolated strings
  • Remote URLs include remote_network in permissions only when intentionally needed
  • Exact placeholders preserve native values, including recursively resolved values such as {last.amount}
  • Every query waits for its response and defines namespace, map, labels, descriptions, and types for its outputs
  • Related queries share a stable namespace, defaults agree with output types, and outputs avoid unnecessary bulk or binary values
  • Friendly packs combine related operations; advanced packs separate actions only when that improves clarity

Failure-Driven Troubleshooting

When a generated manifest fails, preserve the failing example and turn the cause into four durable improvements: a clearer validator message, an AI prompt constraint, a wiki entry, and a regression test. This keeps the Builder aligned with the runtime instead of merely patching one pack.

Locally imported development packs surface empty-list warnings in the Action Builder. Marketplace/catalog installs suppress them because an end user may simply not have the app connected, authenticated, or populated. Use the local warning code and reported response keys to identify the failing stage.

FailureCauseCorrection
fields[n].type is unsupported: toggletoggle is not a field type.Use checkbox for booleans or select for named operations.
Discovery finds nothing with %USERPROFILE%The directory is already resolved under the user's home.Use .veadotube/instances.
A boolean action lists number or state nodesA mixed source was reused without filtering.Add map.where or create separate typed sources.
Typed dropdowns are empty despite matching rowswhere uses an invented operator object.Use a direct scalar match such as { "type": "boolean" }.
empty_rows_pathThe rows path is wrong or an unrelated WebSocket frame arrived first.Compare reported keys, correct map.rows, or add a stable responseMatch.
empty_child_rows_pathParent rows were found but the child path returned nothing.Check childRows relative to one parent row.
empty_where_filterEvery row was removed by the exact-match filter.Confirm the key and scalar value exactly match the response.
empty_value_mappingOption values are empty or duplicated.Map a stable ID or composite value such as {type}:{id}.
empty_instance_fileNo active instance file was discovered.Start the app and verify the discovery directory plus discovery.map.server.
Cascading options come from another app instanceThe source cache omits a value that changes its result.Add instance and every selected parent to source.dependsOn.
JSON-RPC connects but commands do nothingThe protocol envelope was placed at the execute/request root.Move id, jsonrpc, method, and params inside message.
JSON-RPC consumes a notificationThe awaited response was not correlated.Use a stable request ID and match $.id.
A dropdown loses an option sharing the same IDThe API reuses IDs across resource types.Use {type}:{id} and keep raw fields in map.extra.
A read action produces no sequence variablesIt does not wait for or map the response.Add query sequencing metadata and typed output metadata.
The API receives "true" or "4"The placeholder was embedded in text or its source value was text.Use an exact value such as "{enabled}" or "{last.amount}".
A dynamic option lacks a usable label or request IDlabelKey or map.extra omits required row data.Map stable IDs and names into flat extra keys.

When a future failure reveals a reusable rule, update all four layers rather than only correcting the generated manifest.

DevTools Testing Commands

Open SheevChat's DevTools and run these in the console to test your manifest.

List installed libraries:

fetch('/api/action-libraries').then(r => r.json()).then(console.log)

Reload libraries:

fetch('/api/action-libraries/reload', { method: 'POST' }).then(r => r.json()).then(console.log)

Test a connection:

fetch('/api/action-libraries/my-app/connections/local/test', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({})
}).then(r => r.json()).then(console.log)

Resolve a source:

fetch('/api/action-libraries/my-app/sources/scenes/resolve', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ values: {} })
}).then(r => r.json()).then(console.log)

Resolve a cascading source:

fetch('/api/action-libraries/my-app/sources/hotkeys/resolve', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ values: { model_id: 'abc123' } })
}).then(r => r.json()).then(console.log)

Run an action:

fetch('/api/action-libraries/my-app/actions/set-scene/run', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ scene_id: 'abc123' })
}).then(r => r.json()).then(console.log)

Known Current Limits

The following are fully supported for third-party packs today:

  • Local HTTP actions
  • Local WebSocket actions
  • Generic instance file discovery
  • Safe endpoint-file and response-based pipeline discovery
  • Fallback local URLs and connection preflight messages
  • WebSocket response matching and JSON-RPC envelopes
  • Static option sources
  • Connection-request option sources
  • Cascading dropdowns
  • SheevChat command execution

These require additional runtime work and should not be used in imported packs yet:

  • OAuth action packs
  • Pairing flows
  • Cloud APIs with secrets
  • Request signing or HMAC
  • Binary protocols
  • Persistent event subscriptions
  • Custom SDKs
  • Generalized launch application / hotkey / write-file actions

If your app requires one of those, document the gap rather than inventing unsupported manifest fields.

Ready to build? The Action Builder generates a ready-to-import skeleton based on your app's connection type and dropdown needs.

Open Action Builder →