SOKO Actions Developer Documentation (Draft v1.2)
Scope: this document is inferred from the provided code and explicitly marks TODOs where the contract is not fully observable from code alone.
Update v1.2: added missing “contract-critical” details discovered from your in-house action pattern (notably SokoJob init signature, required InitApi() call, and canonical component path resolution).Convert
Table of contents
Action discovery & inventory SOKO Action specification (contract) Base infrastructure: SokoJob Action reference (per-action chapters) SetShotFrameRangeFromVersion How to build a new Action Gap analysis, risks, and recommendations 1) Action discovery & inventory
Execution host / runner
soko_electon_action_runner_example.js – Electron runner that writes script.py + arguments.json into a temp dir and executes Python via PythonShell with -u (unbuffered) and captures stdout lines as messages. Base class
SokoJob.py – reads the JSON args file and creates an authenticated soko_api client. Important clarification (v1.2):
While the runner passes one positional CLI argument (the arguments.json path), your action code convention passes sys.argv (the full argv list) into SokoJob, not only the JSON path. This affects how new actions must be written.Convert
Discovered Actions (entrypoints + files)
All actions follow the same pattern: define a class inheriting SokoJob, override Handle(), and run it under if __name__ == "__main__":.
Discovered Action input-form schema files
SetShotFrameRangeFromVersion.json – defines a startFrame input with default 1001. Format appears non-standard JSON Schema (see TODO in spec). 2) SOKO Action specification (contract)
2.1 Execution model (confirmed)
Where actions run: locally, on the user machine via Electron runner.
Invocation steps (confirmed):
Runner creates a temp directory and writes script.py and arguments.json. Runner executes Python with -u and passes a single argument: the JSON path (args: [argumentsFile]). Runner listens to each stdout line and classifies the line as WARNING/ERROR/INFO by substring match. Critical clarification (v1.2): argv contract for action authors
sys.argv[0] = <script.py path> sys.argv[1] = <arguments.json path> But your in-house action pattern passes the entire sys.argv list into the action class, and SokoJob consumes that (internally) to locate the json file.Convert Canonical entrypoint pattern (MUST for compatibility):
if __name__ == "__main__":
plugin = MyAction(sys.argv)
plugin.Handle()
…and constructor:
class MyAction(SokoJob):
def __init__(self, args):
super().__init__(args)
Convert
Concurrency (confirmed):
Runner maintains a task queue and can run actions in parallel up to MAX_CONCURRENT_ACTION_PROCESSES. If multiple actions are submitted in one payload, they run sequentially. Timeouts / retries (TODO):
No explicit timeout or retry policy is visible in the provided runner sample. TODO: where/if SOKO defines action timeouts, max runtime, retry on non-zero exit, and how failureDetectionJobErrors is used (several actions set it to 3). 2.2 Inputs: arguments.json schema (confirmed + inferred)
Top-level structure (confirmed):
The runner writes a JSON object containing at least:
contextData (user, project, folder) contextObjects (the selected objects) Required fields (confirmed by base class / runner):
pluginData.serverURL, pluginData.apiKey are required to init soko_api. pluginData.apiUser + pluginData.apiUserPassword, or pluginData.userApiKey, or pluginData.jwt
must exist, else login fails. contextObjects must exist for most actions (they iterate self.arguments["contextObjects"]). 2.2.1 contextType="version" – minimum reliable schema (v1.2, PARTIALLY CONFIRMED)
Based on observed actions, a Version context object typically contains:
components[].id (component UUID) components[].parsedPaths (list of OS-specific path entries) Optional / may exist, but not guaranteed (TODO to confirm):
name and/or output.name (human-readable) publishedAt / published_at publishedBy / published_by output.* (folderId, parentId, outputTypeId, suffix) depending on action Recommendation (MUST for action authors): treat anything beyond id, versionNumber, components[].parsedPaths as optional unless explicitly stated for the action.
2.2.2 Component path resolution (v1.2, CONFIRMED)
Earlier draft referenced get_component_path_for_current_os(), but your codebase convention uses:
from soko_api.utils import files
path_data = files.get_component_path_data_for_current_os(component["parsedPaths"])
local_path = path_data["path"]
Where path_data appears to be a dict (at least containing path, and possibly also prefix / prefixVariable).Convert
This is the canonical method (MUST) to resolve local file paths.
Do not hand-pick parsedPaths entries unless you have a hard reason.