a9script

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-run tells 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.
  • triggerswebhook / 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_structurefilestorehttp_connectionemail_outnotification_ruleemail_inresourceconfig_functionscript_settingsscript_libraryscript_mainautomation

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 doesuse it for
(default)one change per file, in kind order; each success is live at onceauthoring, iterating
--dry-runthe full gate — config schemas AND every script’s source compiled — then rolls everything backbefore the first apply, and before touching production
--atomicthe whole folder as ONE change: all of it lands or none of ita 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.

Rendered from docs/skills/a9script-authoring/reference/folder-model.md in the product's own repository, at build time. Found a problem on this page? Write to the address in the footer.