Skip to content

Latest commit

 

History

History
935 lines (722 loc) · 32.8 KB

File metadata and controls

935 lines (722 loc) · 32.8 KB

Connector Reference

Connectors define the actions a step can perform. Actions use a connector/action naming convention. For AI tool use (function calling), see Tool Use.

http/request

Makes an HTTP request.

Params:

Param Type Required Description
method string Yes HTTP method: GET, POST, PUT, PATCH, DELETE.
url string Yes Request URL.
headers map No HTTP headers as key-value pairs.
body any No Request body. Objects are JSON-encoded.

Output:

Field Type Description
status number HTTP response status code.
headers map Response headers.
body string Raw response body as a string.
json any Parsed response body. Only present when the response is valid JSON.

Example:

- name: create-item
  action: http/request
  params:
    method: POST
    url: https://api.example.com/items
    headers:
      Authorization: "Bearer {{ env.API_TOKEN }}"
      Content-Type: application/json
    body:
      name: "New Item"
      quantity: 5

ai/completion

Sends a prompt to an OpenAI-compatible chat completion API and returns the result. Requires a credential with an API key -- see the Secrets Guide for setup.

Params:

Param Type Required Description
provider string No AI provider to use: openai (default) or bedrock.
model string Yes Model name (e.g., gpt-4o, gpt-4o-mini, anthropic.claude-3-sonnet-20240229-v1:0).
prompt string Yes The user prompt to send.
region string No AWS region for the Bedrock provider (e.g., us-east-1). Only used when provider is bedrock.
system_prompt string No System message prepended to the conversation.
output_schema object No JSON Schema for structured output. When set, the model returns JSON conforming to this schema.
base_url string No Override the API base URL. Defaults to https://api.openai.com/v1. Use this for OpenAI-compatible providers like Azure, Ollama, or local models.
tools list No Tool declarations for function calling. See Tool Use.
max_tool_rounds integer No Maximum number of LLM-tool interaction rounds. Default: 10 (from engine.default_max_tool_rounds).
max_tool_calls_per_round integer No Maximum number of tool calls the LLM can make in a single round. Default: 10 (from engine.default_max_tool_calls_per_round).

Output:

Field Type Description
text string The raw completion text returned by the model.
json any If the response is valid JSON (e.g., from structured output), the parsed object. Only present when the response parses as JSON.
tool_calls list Tool invocations requested by the model. Each item has id, type, and function (with name and arguments). Only present when the model requests tool calls in the final response.
finish_reason string Why the model stopped generating. stop for normal text completion, tool_calls when the model requested tool invocations.
model string The model name as reported by the API.
usage.prompt_tokens number Number of tokens in the prompt.
usage.completion_tokens number Number of tokens in the completion.
usage.total_tokens number Total tokens used.

Example -- basic completion:

- name: summarize
  action: ai/completion
  credential: my-openai
  params:
    model: gpt-4o
    prompt: "Summarize this in 3 bullet points: {{ steps.fetch-data.output.body }}"

Example -- with system prompt and structured output:

- name: extract-entities
  action: ai/completion
  credential: my-openai
  timeout: 60s
  params:
    model: gpt-4o
    system_prompt: "You are a data extraction assistant. Always respond with valid JSON."
    prompt: "Extract all person names and companies from: {{ steps.fetch-data.output.body }}"
    output_schema:
      type: object
      properties:
        people:
          type: array
          items:
            type: string
        companies:
          type: array
          items:
            type: string
      required:
        - people
        - companies
      additionalProperties: false

The structured output is available as steps.extract-entities.output.json.people and steps.extract-entities.output.json.companies in subsequent steps.

Example -- custom base URL (Ollama):

- name: local-completion
  action: ai/completion
  params:
    model: llama3
    base_url: http://localhost:11434/v1
    prompt: "Explain this error: {{ steps.fetch-logs.output.body }}"

Example -- AWS Bedrock:

- name: summarize
  action: ai/completion
  credential: aws-bedrock-creds
  params:
    provider: bedrock
    model: anthropic.claude-3-sonnet-20240229-v1:0
    region: us-east-1
    prompt: "Summarize: {{ steps.fetch.output.body }}"

When running on AWS infrastructure with an IAM role attached (IRSA, instance profile, etc.), the credential field can be omitted -- the Bedrock provider uses the standard AWS credential chain automatically.

Authentication: The AI connector reads the credential's api_key field (or token or key as fallbacks) and sends it as a Bearer token. If the credential includes an org_id field, it is sent as the OpenAI-Organization header. See the Secrets Guide for how to create an openai-type credential.

ai/embed

Turns text into vector embeddings via an OpenAI-compatible embeddings API. Use it to build a semantic knowledge base (RAG) — see the RAG guide. Like ai/completion, it participates in token-budget accounting.

Params:

Param Type Required Description
provider string No Embeddings provider: openai (default) or bedrock.
model string Yes Embedding model. OpenAI: text-embedding-3-small, text-embedding-3-large. Bedrock: amazon.titan-embed-text-v2:0, amazon.titan-embed-text-v1.
input string | list Yes A single string or an array of strings to embed.
dimensions integer No Requested embedding dimension, when the model supports it (OpenAI text-embedding-3-*, Bedrock titan-embed-text-v2).
region string No AWS region for the Bedrock provider. Only used when provider is bedrock.
base_url string No Override the API base URL (OpenAI provider). Defaults to https://api.openai.com/v1. Use for Azure OpenAI or OpenAI-compatible servers.

Output:

Field Type Description
embedding list The first input's embedding as a float array. Present when there is at least one input.
vector string The first input's embedding as a pgvector text literal ("[0.1,0.2,...]"), ready to bind into a ::vector column.
embeddings list All embeddings, one float array per input, in input order.
vectors list All embeddings as pgvector text literals, in input order.
count number Number of embeddings returned.
dimensions number Dimension of the returned vectors.
usage.prompt_tokens number Tokens counted for the input.
usage.total_tokens number Total tokens used.

Example — embed and store in pgvector:

- name: embed
  action: ai/embed
  credential: my-openai
  params:
    model: text-embedding-3-small
    input: "{{ inputs.content }}"

- name: store
  action: postgres/query
  credential: kb-db
  depends_on: [embed]
  params:
    query: "INSERT INTO kb_documents (content, embedding) VALUES ($1, $2::vector)"
    args:
      - "{{ inputs.content }}"
      - "{{ steps['embed'].output.vector }}"

Authentication: same as ai/completion — the credential's api_key (or token/key) is sent as a Bearer token.

slack/send

Sends a message to a Slack channel via the chat.postMessage API. Requires a credential with a Slack Bot User OAuth Token.

Params:

Param Type Required Description
channel string Yes Slack channel — either a channel ID (e.g., C01234ABCDE) or a channel name with # prefix (e.g., #general). Channel IDs are preferred for reliability.
text string Yes Message text. Supports Slack mrkdwn formatting.

Output:

Field Type Description
ok boolean true if the message was sent successfully.
ts string Slack message timestamp. Use this to reference the message in follow-up API calls.
channel string The channel ID where the message was posted.

Example:

- name: notify-team
  action: slack/send
  credential: slack-bot
  params:
    channel: "C01234ABCDE"
    text: "Deployment complete: {{ steps.deploy.output.body }}"

Authentication: The Slack connector reads the credential's token field and sends it as a Bearer token. Create a credential of type bearer with a token field containing your Slack Bot User OAuth Token:

mantle secrets create --name slack-bot --type bearer --field token=xoxb-your-bot-token

slack/history

Reads recent messages from a Slack channel via the conversations.history API.

Params:

Param Type Required Description
channel string Yes Slack channel ID (e.g., C01234ABCDE).
limit number No Maximum number of messages to return. Default: 10.

Output:

Field Type Description
ok boolean true if the request was successful.
messages list Array of message objects. Each message contains fields like text, user, ts, and type.

Example:

- name: read-channel
  action: slack/history
  credential: slack-bot
  params:
    channel: "C01234ABCDE"
    limit: 5

- name: summarize-messages
  action: ai/completion
  credential: my-openai
  params:
    model: gpt-4o
    prompt: "Summarize these Slack messages: {{ steps['read-channel'].output.messages }}"

postgres/query

Executes a parameterized SQL query against an external Postgres database. The connector opens a connection per step execution and closes it afterward. Supports both read queries (SELECT, WITH) and write statements (INSERT, UPDATE, DELETE).

Params:

Param Type Required Description
query string Yes SQL query to execute. Use $1, $2, etc. for parameterized values.
args list No Ordered list of values to substitute into the parameterized query.

Output (SELECT/WITH queries):

Field Type Description
rows list Array of row objects, each mapping column names to values. Empty array if no rows match.
row_count number Number of rows returned.

Output (INSERT/UPDATE/DELETE statements):

Field Type Description
rows_affected number Number of rows affected by the statement.

Example -- read query:

- name: fetch-users
  action: postgres/query
  credential: my-database
  params:
    query: "SELECT id, email FROM users WHERE active = $1 LIMIT $2"
    args:
      - true
      - 100

Example -- write statement:

- name: update-status
  action: postgres/query
  credential: my-database
  params:
    query: "UPDATE orders SET status = $1 WHERE id = $2"
    args:
      - "shipped"
      - "{{ steps['create-order'].output.json.order_id }}"

Authentication: The Postgres connector reads the database connection URL from the credential's url field (or key as a fallback). Create a credential with the full Postgres connection string:

mantle secrets create --name my-database --type generic --field url=postgres://user:pass@host:5432/dbname?sslmode=require

email/send

Sends an email via SMTP. Supports plaintext and HTML content.

Params:

Param Type Required Description
to string or list Yes Recipient email address(es). A single string or a list of strings.
from string Yes Sender email address.
subject string Yes Email subject line.
body string Yes Email body content.
html boolean No Set to true to send the body as HTML. Default: false (plaintext).
smtp_host string No SMTP server hostname. Can also be provided via credential.
smtp_port string No SMTP server port. Default: 587. Can also be provided via credential.

Output:

Field Type Description
sent boolean true if the email was sent successfully.
to string Comma-separated list of recipient addresses.
subject string The subject line that was sent.

Example:

- name: send-report
  action: email/send
  credential: smtp-creds
  params:
    to:
      - "alice@example.com"
      - "bob@example.com"
    from: "reports@example.com"
    subject: "Daily Report — {{ steps.generate.output.json.date }}"
    body: "{{ steps.generate.output.json.html_report }}"
    html: true

Authentication: The email connector reads username, password, host, and port from the credential. If host or port are not in the credential, they fall back to the smtp_host and smtp_port params. Create a basic credential with SMTP fields:

mantle secrets create --name smtp-creds --type basic \
  --field username=apikey \
  --field password=SG.your-sendgrid-key \
  --field host=smtp.sendgrid.net \
  --field port=587

TLS: The email connector enforces TLS for all SMTP connections. Port 465 uses implicit TLS (SMTPS); all other ports use STARTTLS and fail if the server does not support TLS. Plaintext SMTP is not supported.

email/receive

Reads messages from an email mailbox. Supports filtering by folder and read status.

Params:

Param Type Required Description
folder string No Folder to read from (e.g., INBOX, Archive, [Gmail]/Sent Mail). Default: INBOX.
filter string No Filter messages by status: all, unseen, recent, flagged. Default: unseen.
limit number No Maximum number of messages to return. Default: 10.
mark_seen boolean No Mark retrieved messages as seen. Default: false.

Output:

Field Type Description
message_count number Number of messages returned.
messages array Array of message objects. Each message contains: message_id (string), from (string), to (string), cc (string), subject (string), body (string), date (RFC 3339 timestamp), headers (map), flags (array of strings), uid (number, IMAP UID).

Authentication: Credentials are provided via the step-level credential field. The email connector reads username, password, host, and port from the credential (IMAP-compatible).

Example:

- name: read-inbox
  action: email/receive
  credential: company-inbox
  params:
    folder: INBOX
    filter: unseen
    limit: 20
    mark_seen: true

email/move

Moves an email message to a different folder.

Params:

Param Type Required Description
uid number Yes IMAP UID of the message.
source_folder string No Source folder (for reference). Default: INBOX.
target_folder string Yes Destination folder path (e.g., Archive, [Gmail]/All Mail).

Output:

Field Type Description
moved boolean true if the move was successful.
uid number The IMAP UID of the moved message.
target_folder string The folder the message was moved to.

Authentication: Credentials are provided via the step-level credential field.

Note: Gmail's "archive" action is implemented as a move to [Gmail]/All Mail.

Example:

- name: archive-message
  action: email/move
  credential: company-inbox
  params:
    uid: "{{ trigger.uid }}"
    source_folder: INBOX
    target_folder: Archive

email/delete

Deletes an email message.

Params:

Param Type Required Description
uid number Yes IMAP UID of the message.
folder string No Folder containing the message. Default: INBOX.

Output:

Field Type Description
deleted boolean true if the deletion was successful.
uid number The IMAP UID of the deleted message.

Authentication: Credentials are provided via the step-level credential field.

Example:

- name: delete-spam
  action: email/delete
  credential: company-inbox
  params:
    uid: "{{ trigger.uid }}"
    folder: INBOX

email/flag

Adds or removes flags (labels) on an email message.

Params:

Param Type Required Description
uid number Yes IMAP UID of the message.
flags array Yes List of flag names to modify (e.g., ["flagged", "important"]).
action string Yes add to set flags, remove to unset flags.
folder string No Folder containing the message. Default: INBOX.

Standard IMAP Flags:

Flag Description
seen Message has been read.
flagged Message is flagged for follow-up.
answered Message has been replied to.
deleted Message is marked for deletion.
draft Message is a draft.

Custom Keywords: Most email providers support custom flag names beyond the standard set. These are often used as tags or labels (e.g., important, urgent, client-xyz).

Output:

Field Type Description
updated boolean true if the flag operation was successful.
action string The operation performed: add or remove.
uid number The IMAP UID of the message.
flags array The flags that were modified.

Authentication: Credentials are provided via the step-level credential field.

Example:

- name: flag-important
  action: email/flag
  credential: company-inbox
  params:
    uid: "{{ trigger.uid }}"
    flags: ["flagged", "important"]
    action: add

s3/put

Uploads an object to an S3-compatible storage bucket.

Params:

Param Type Required Description
bucket string Yes S3 bucket name.
key string Yes Object key (path) within the bucket.
content string Yes Object content as a string.
content_type string No MIME type for the object. Default: application/octet-stream.

Output:

Field Type Description
bucket string The bucket the object was uploaded to.
key string The object key.
size number Size of the uploaded content in bytes.

Example:

- name: upload-report
  action: s3/put
  credential: aws-s3
  params:
    bucket: "my-reports"
    key: "reports/{{ steps.generate.output.json.date }}.json"
    content: "{{ steps.generate.output.json.report }}"
    content_type: "application/json"

s3/get

Downloads an object from an S3-compatible storage bucket.

Params:

Param Type Required Description
bucket string Yes S3 bucket name.
key string Yes Object key (path) within the bucket.

Output:

Field Type Description
bucket string The bucket the object was downloaded from.
key string The object key.
content string Object content as a string.
size number Size of the downloaded content in bytes.
content_type string MIME type of the object as reported by S3.

Example:

- name: download-config
  action: s3/get
  credential: aws-s3
  params:
    bucket: "my-configs"
    key: "app/config.json"

s3/list

Lists objects in an S3-compatible storage bucket, with optional prefix filtering.

Params:

Param Type Required Description
bucket string Yes S3 bucket name.
prefix string No Filter results to keys that start with this prefix.

Output:

Field Type Description
bucket string The bucket that was listed.
objects list Array of objects. Each object has key (string), size (number), and last_modified (string, RFC 3339).

Example:

- name: list-reports
  action: s3/list
  credential: aws-s3
  params:
    bucket: "my-reports"
    prefix: "reports/2026/"

S3 Authentication

All S3 connectors (s3/put, s3/get, s3/list) read the following fields from the credential:

Field Required Description
access_key Yes AWS access key ID.
secret_key Yes AWS secret access key.
region No AWS region. Default: us-east-1.
endpoint No Custom S3 endpoint URL. Use this for S3-compatible services like MinIO, DigitalOcean Spaces, or Backblaze B2.

Create a credential for S3:

mantle secrets create --name aws-s3 --type generic \
  --field access_key=AKIA... \
  --field secret_key=wJalr... \
  --field region=us-west-2

For S3-compatible services, add an endpoint field:

mantle secrets create --name minio --type generic \
  --field access_key=minioadmin \
  --field secret_key=minioadmin \
  --field endpoint=http://localhost:9000

workflow/run

Invokes another workflow as a child execution. The child workflow runs synchronously within the parent step, with full checkpoint-and-resume support. If the parent crashes and recovers, the child execution is reused rather than re-executed.

Params:

Param Type Required Description
workflow string Yes Name of the child workflow to execute.
version integer No Specific version to run. Omit to use the latest applied version.
inputs map No Input parameters to pass to the child workflow.

Output:

Field Type Description
execution_id string The child workflow's execution ID.
status string Final status of the child execution (completed or failed).
steps map Map of child step names to their results. Each entry has an output field (and optionally error).

Accessing child results in CEL:

Child step outputs are nested under the parent step's output:

steps['my-step'].output.steps['child-step'].output.field

Depth limiting: Workflow nesting depth is configurable via engine.max_workflow_depth (default: 10). Exceeding this limit returns an error.

Checkpoint recovery: If the parent workflow crashes mid-execution, the child execution record is preserved in the database. On resume, the engine detects the existing child and reuses its result instead of creating a duplicate.

Cancellation: Running mantle cancel on a parent execution cascades cancellation to all child executions.

Example -- parent workflow invoking a reusable child:

name: order-pipeline
description: Process an order using a reusable validation workflow
steps:
  - name: validate
    action: workflow/run
    params:
      workflow: validate-order
      inputs:
        order_id: "{{ inputs.order_id }}"

  - name: notify
    action: slack/send
    credential: slack-bot
    params:
      channel: "#orders"
      text: "Order validated: {{ steps.validate.output.steps['check-inventory'].output.available }}"

Example -- child workflow (validate-order):

name: validate-order
description: Validate an order's inventory and pricing
inputs:
  order_id:
    type: string
steps:
  - name: check-inventory
    action: http/request
    params:
      method: GET
      url: "https://api.example.com/inventory/{{ inputs.order_id }}"

  - name: check-pricing
    action: http/request
    params:
      method: GET
      url: "https://api.example.com/pricing/{{ inputs.order_id }}"

docker/run

Runs a Docker container to completion and captures its output. The container is created, started, waited on, and optionally removed. Non-zero exit codes do not constitute a step failure — use if conditions to branch on exit code.

Params:

Param Type Required Default Description
image string Yes Container image (e.g., alpine:latest)
cmd array No Command and arguments
env object No Environment variables
stdin string No Data piped to container stdin
mounts array No Volume/bind mounts (each with source, target, readonly)
network string No bridge Docker network mode (bridge or none)
pull string No missing Image pull policy: always, missing, never
memory string No Memory limit (e.g., 512m, 1g)
cpus number No CPU limit (e.g., 1.5)
remove boolean No true Remove container after completion

Output:

Field Type Description
exit_code integer Container exit code
stdout string Container stdout (capped at 10MB)
stderr string Container stderr (capped at 10MB)

Authentication: The Docker connector uses a docker credential type for daemon access. All fields are optional — an empty credential connects to the local Docker socket. For private images, use registry_credential with a basic credential type. Note that registry_credential is a step-level field (alongside credential), not a param.

Security: Containers run with all Linux capabilities dropped (CAP_DROP ALL), no-new-privileges, and a PID limit. Only bridge and none network modes are permitted.

Example:

- name: process-data
  action: docker/run
  credential: my-docker
  registry_credential: my-registry
  timeout: "2m"
  params:
    image: myorg/processor:latest
    cmd: ["process", "--format", "json"]
    stdin: "{{ steps['fetch-data'].output.body }}"
    memory: "512m"
    cpus: 1.0

browser/run

Runs browser automation scripts (JavaScript, TypeScript, or Python) using Playwright. Scripts run in a containerized browser environment and can interact with web pages, perform DOM queries, take screenshots, and generate structured output.

Params:

Param Type Required Default Description
language string No javascript Script language: javascript, typescript, or python.
script string Yes Browser automation script. The global browser object is a Playwright Browser instance.
output_format string No text Output format: json or text. JSON output is automatically parsed.
env object No Environment variables accessible in the script via process.env (JS/TS) or os.environ (Python).
pull string No missing Image pull policy: always, missing, never.
memory string No 1g Memory limit (e.g., 512m, 1g).

Output:

Field Type Description
exit_code integer Script exit code (0 = success).
stdout string Script stdout output (capped at 10MB).
stderr string Script stderr output (capped at 10MB).
json any Parsed JSON output. Only present when output_format: json and stdout is valid JSON.

Container Images:

By default, browser/run uses the official Microsoft Playwright images, which install Playwright inside the container on every invocation (adds ~30 s cold-start). To skip this step, use the pre-built Mantle images:

Language Default Image Pre-built Image
JavaScript / TypeScript mcr.microsoft.com/playwright:v1.52.0-noble ghcr.io/dvflw/mantle-playwright-node:1.52.0
Python mcr.microsoft.com/playwright/python:v1.52.0-noble ghcr.io/dvflw/mantle-playwright-python:1.52.0

Pass a pre-built image via the image param:

steps:
  - name: run_browser
    action: browser/run
    timeout: "2m"
    params:
      image: ghcr.io/dvflw/mantle-playwright-node:1.52.0
      language: javascript
      script: |
        const page = await browser.newPage();
        await page.goto('https://example.com');
        console.log(await page.title());

The latest tag tracks the most recent stable release. Images are rebuilt automatically when the Playwright version is updated.

Artifacts: Scripts can write files to /mantle/artifacts/ directory for screenshots, PDFs, HAR files, and other outputs. Declare artifacts in the step to register them with the execution.

Credentials: Secrets are injected as environment variables via the env param. Access them in scripts using process.env.VAR_NAME (JS/TS) or os.environ['VAR_NAME'] (Python).

Security: Containers run with all Linux capabilities dropped (CAP_DROP ALL), no-new-privileges, and a PID limit. Same security hardening as docker/run.

:::caution[Script Injection Risk] The script field is concatenated directly into a Playwright wrapper template. If the script content is derived from untrusted input (e.g., script: "{{ trigger.body }}"), an attacker could inject arbitrary code that executes inside the container. The container sandbox limits blast radius, but injected code can still make network requests, access environment variables, and consume resources.

Best practice: Never interpolate untrusted input directly into the script field. Pass untrusted data via env variables and access them through process.env (JS/TS) or os.environ (Python), which treats them as string values rather than executable code. :::

Example -- JavaScript with login and screenshot:

- name: scrape-portal
  action: browser/run
  timeout: "2m"
  params:
    language: javascript
    output_format: json
    env:
      USERNAME: "{{ inputs.username }}"
      PASSWORD: "{{ inputs.password }}"
    script: |
      const page = await browser.newPage();
      await page.goto('https://portal.example.com/login');
      await page.fill('#username', process.env.USERNAME);
      await page.fill('#password', process.env.PASSWORD);
      await page.click('#login-button');
      await page.waitForSelector('.dashboard');

      const data = await page.evaluate(() => {
        const rows = document.querySelectorAll('.data-table tr');
        return Array.from(rows).map(row => ({
          name: row.querySelector('.name')?.textContent,
          value: row.querySelector('.value')?.textContent,
        }));
      });

      await page.screenshot({ path: '/mantle/artifacts/dashboard.png' });
      console.log(JSON.stringify({ records: data, count: data.length }));
  artifacts:
    - path: dashboard.png
      name: dashboard-screenshot

Example -- TypeScript with form submission:

- name: submit-form
  action: browser/run
  timeout: "2m"
  params:
    language: typescript
    output_format: json
    script: |
      const page = await browser.newPage();
      await page.goto('https://portal.example.com/form');
      await page.fill('#email', 'user@example.com');
      await page.fill('#message', 'Automated submission');
      await page.click('#submit-button');
      await page.waitForSelector('.success-message');

      const confirmationId = await page.textContent('.confirmation-id');
      console.log(JSON.stringify({ submitted: true, confirmation_id: confirmationId }));

:::caution[TypeScript Limitations] TypeScript support uses Node.js --experimental-strip-types, which only strips type annotations. The following TypeScript features are not supported:

  • Enums (enum Direction { Up, Down })
  • Namespaces (namespace Foo { })
  • Decorators (@decorator)
  • import = / export = syntax
  • npm imports — the container does not have node_modules beyond Playwright itself

Use TypeScript for type annotations only. If you need advanced TypeScript features, use JavaScript instead. :::

Example -- Python with PDF generation:

- name: generate-pdf
  action: browser/run
  timeout: "2m"
  params:
    language: python
    output_format: json
    script: |
      import os
      import json

      page.goto('https://example.com/report')
      page.pdf(path='/mantle/artifacts/report.pdf')

      file_size = os.path.getsize('/mantle/artifacts/report.pdf')
      print(json.dumps({'generated': True, 'size_bytes': file_size}))
  artifacts:
    - path: report.pdf
      name: generated-report

Playwright API Reference:

Within browser/run scripts, use the standard Playwright API:

  • Page navigation: page.goto(url), page.goBack(), page.goForward(), page.reload()
  • Interactions: page.fill(selector, text), page.click(selector), page.selectOption(selector, value), page.press(key)
  • Waiting: page.waitForSelector(selector), page.waitForNavigation(), page.waitForFunction(fn)
  • DOM queries: page.textContent(selector), page.getAttribute(selector, name), page.evaluate(fn)
  • Screenshots/exports: page.screenshot(options), page.pdf(options), page.recordHar(path)