Docs / Agents: CLI, MCP, skill
The folder model — what a file looks like
One .json file per entity, plus the .js files your scripts live in. The
platform reads kind and name, never the filename — but a READER follows
references by eye, so name the file after its entity: the library
sync-helpers lives in sync-helpers.json + sync-helpers.js, and a link
to { "kind": "script_library", "name": "sync-helpers" } finds its file
without opening anything. A folder whose filenames say something else makes
every reference a search.
integration/
crm.json an http_connection named "crm"
getOrder.json a config_function calling it, named "getOrder"
formatting.json a script_library named "formatting"
formatting.js …its source
on-order.json a script_main named "on-order"
on-order.js …its source
_input.json yours — ignored by apply
The shape every file has
{
"kind": "script_main",
"name": "on-order",
"displayName": "On order",
"codeFile": "on-order.js",
"config": { }
}
kind— one of the kinds listed under the order files are applied in, below.name— what the upsert matches on, and how other files refer to this entity. Changing it in the file creates a second entity; it does not rename the first.displayName— optional; defaults to the name.config— the entity’s own settings, strictly validated per kind. A--dry-runtells you exactly which field is wrong.
Source lives in .js files
Use codeFile instead of writing code into a JSON string — it is read from
beside the JSON file:
{ "kind": "script_library", "name": "formatting", "codeFile": "formatting.js" }
bodyFile does the same for a resource body. Neither is sent to the server:
they are resolved into config.code / config.body before anything is posted.
Referring to other entities
Relations (a main script’s libraries, an automation’s members, a trigger’s source) are written by name, and may point at something the same folder creates:
{
"kind": "script_main",
"name": "on-order",
"codeFile": "on-order.js",
"links": [{ "kind": "script_library", "name": "formatting" }],
"triggers": [
{
"kind": "webhook",
"source": { "kind": "http_connection", "name": "orders-in" },
"sourceDetail": "orders"
}
]
}
links— the libraries and settings a main script assembles (main scripts only). Config functions are not linked; a script simply calls them. What the order means, and what a settings script must answer, is in scripts.members— the main scripts an automation groups.triggers—webhook/api_service(source: a connection, detail: one of its endpoint paths),scheduler(a 5-field cron),email_in(source: an email source),system(no source: what it binds is which of the platform’s own occurrences the script wants, authored with the trigger).
One endpoint, several scripts. A webhook or api_service trigger may say
which of the endpoint’s events it wants, so two small mains can share one door
instead of one script branching on the payload:
"triggers": [
{
"kind": "webhook",
"source": { "kind": "http_connection", "name": "jira" },
"sourceDetail": "jira",
"when": [{ "path": "webhookEvent", "equals": "comment_created" }]
}
]
A rule is a dot path into the event body plus exactly one of equals,
notEquals, startsWith or exists (a boolean); the rules of one trigger all
have to hold. No when means every event, which is the default. An event no
trigger takes is accepted and runs nothing — so if a script never fires, check
its filter first.
Test the event’s SHAPE here, its CONTENT in the script. “This kind”, “this
field present”, “this prefix” belong in the filter — an event it drops costs
no run, where an if … return at the top of a script costs one:
"when": [
{ "path": "webhookEvent", "equals": "jira:issue_updated" },
{ "path": "changelog.items", "exists": true }
]
exists means present and not null; an empty list passes it. The path reaches
a list by POSITION (changelog.items[0].field) and has no wildcard — a * is
refused, because it would parse as a key nothing has and match nothing for
ever. Which ITEM of a list carries what you want is content: ask that in the
script.
A relation group you leave out is left alone; an explicit [] clears it.
A config function’s connection is named too, with connectionName:
{
"kind": "config_function",
"name": "getOrder",
"config": {
"type": "http",
"connectionName": "orders-in",
"method": "GET",
"path": "/orders/{{id}}",
"params": [{ "name": "id", "description": "the order id" }]
}
}
A config function’s name is what a script calls, so it must be a plain
identifier (getOrder, not get-order).
The order files are applied in
Always this, whatever the filenames are:
parameter_structure → filestore → http_connection → email_out →
notification_rule → email_in → resource → config_function →
script_settings → script_library → script_main → automation
Two things follow. A file may reference anything of an earlier kind that the same folder creates. And arming happens last: triggers ride on main scripts, so a folder that fails halfway has not armed anything.
The three modes of apply
| what it does | use it for | |
|---|---|---|
| (default) | one change per file, in kind order; each success is live at once | authoring, iterating |
--dry-run | the full gate — config schemas AND every script’s source compiled — then rolls everything back | before the first apply, and before touching production |
--atomic | the whole folder as ONE change: all of it lands or none of it | a coordinated edit to a live environment |
--atomic cannot resolve a connectionName whose connection the same run
creates — one change cannot create a connection and store its id at once.
Apply without --atomic the first time; --atomic afterwards.
Secrets
The folder that lives in git carries a blank where a credential goes:
{ "type": "bearer", "token": "" }
A blank preserves what is stored, so re-applying never wipes a credential someone entered. Secrets never come back out of the platform — a read shows a fixed mask, never the value.