TaskSultan Docs tasksultan.com

Robot

Packaging an automation

How to build an automation into a portable package, inspect what it declares and run it on a machine that has no source tree.

A package is a directory a Robot can execute without the repository, the Studio or the TypeScript sources. You build it once, inspect the manifest it carries and hand the directory to any Robot host.

#Building a package

tsx robot/cli.ts package process-orders --out ./out

buildPackage loads the automation from the registry, bundles the automation, the framework runner and the targets into one CommonJS file with esbuild and writes four files:

out/process-orders/
  automation.cjs   bundled automation, framework and targets
  manifest.json    the machine-readable contract
  config.json      runtime defaults (url, retry)
  package.json     declares the single host dependency, playwright

Playwright is not bundled. It is a host capability, the way a Docker base image provides Node. The package only declares the version it needs. That is why a package travels small and why the bundle keeps its dynamic requires native as CommonJS.

#What the manifest declares

manifest.json is the contract a Robot reads before it runs anything:

{
  "formatVersion": 1,
  "id": "process-orders",
  "name": "tasksultan-process-orders-package",
  "version": "0.1.0",
  "main": "automation.cjs",
  "configFile": "config.json",
  "targetUrl": "http://127.0.0.1:4100",
  "capabilities": ["web", "excel"],
  "engines": { "node": ">=20", "playwright": "1.49.0" },
  "contentHash": "sha256:...",
  "builtAt": "2026-10-03T09:15:00.000Z",
  "builtBy": "tasksultan robot package"
}

id is the product key and version is a label. The identity of the bytes is contentHash, a SHA-256 over automation.cjs. The package is immutable: the Robot reads it, executes it and writes evidence elsewhere. It never edits the package.

#Inspecting a package before you run it

Inspect the manifest as plain JSON. There is no build step and no Studio needed to read it. The fields worth a look are formatVersion, capabilities, engines and contentHash.

The Robot checks contentHash itself on every run-package call. If the bytes do not match the manifest, the Robot refuses:

# the Robot rebuilds the digest and compares; a mismatch is a refusal, not a warning
tsx robot/cli.ts run-package ./out/process-orders
# Package contentHash mismatch (...): refusing to execute a tampered or corrupted package.

robot list and robot show <automationId> <runId> show what runs and what a finished run recorded. A package directory is portable input, so it is worth copying it to a host, hashing automation.cjs yourself and comparing to contentHash before a first run.

#Running without the source tree

tsx robot/cli.ts run-package ./out/process-orders --artifacts ./evidence

The Robot reads manifest.json, requires formatVersion to be 1, verifies contentHash, loads the entry named by main and runs the same transaction lifecycle as run. No repository checkout is involved. The host provides node_modules with the declared Playwright and the browser binaries.

Machine-specific values never live in the package. Config precedence, from lowest to highest, is the automation's own defaults, then the package's config.json, then the Robot environment (TS_TARGET_URL, TS_DATA_DIR, TS_ROBOT_ID, TS_ENVIRONMENT, TS_CRED_*), then run or job parameters such as --url, --runId and --artifacts. Data files are referenced by relative name and resolved against TS_DATA_DIR, so the same package runs in dev against ./data and in prod against a provisioned directory without a rebuild.

#Things that catch people out

The Robot speaks formatVersion 1 only. A package from a newer format is refused, not guessed at.

A built package declares engines.node as >=20, which is what the packager emits and what a test pins, not the floor of the repository that produced it. Read it as what the package declares. Check your host's Node version against your own requirement rather than assuming the field is the tree's.

A package is a directory today, not a zip. Treat it as the unit to copy and version. Keep the sources in the repository as the single source of truth: automation.cjs is a build artifact, not something to edit.