Automation Guide
Automation lets you create browser automation scripts that run on your desktop. You can build automations in three different modes, choosing what works best for your automation:
- Steps Mode — Configure individual actions (Click, Fill, Navigate, Wait, etc.) in a visual interface
- Script Mode — Write a script with variables, loops, conditionals, and functions
- Browser Script Mode — The same automation language in browser mode, for HTTP API automation and integration with external APIs
Requirements
Nocarta Desktop App Required
Automations execute browser automation using Playwright. For security and functionality, this runs on your desktop computer, not on the server.
Download Nocarta Desktop | Setup Guide
Automation Assistant
The Automation Assistant is your conversational helper for creating and testing automations. Open it from any automation page or the automations list.
Two Ways to Use It
| Mode | What It Does | AI Required? |
|---|---|---|
| Direct Commands | Execute immediately - open pages, navigate, record actions, take screenshots | No |
| Smart Commands | Understand natural language - find elements, click buttons by description, answer questions | Yes |
Nocarta AI is always available — smart commands work out of the box with no configuration. You can also bring your own LLM provider for additional flexibility. Direct commands always work, even without any AI.
Browser Commands
Control the browser with simple commands:
| Command | What It Does |
|---|---|
open google.com |
Opens a URL in the browser |
back |
Go back to the previous page |
forward |
Go forward in history |
refresh |
Reload the current page |
screenshot |
Capture the current page |
close browser |
Close the browser window |
browser status |
Check if the browser is running |
Recording Commands
Record your actions to create automations automatically:
| Command | What It Does |
|---|---|
start recording |
Begin capturing your clicks, typing, and navigation |
stop recording |
Stop capturing actions |
show recorded actions |
See what you've recorded so far |
save as automation |
Save your recording as a reusable automation |
Tip: When saving, you can select specific actions to include. Don't worry if you made mistakes during recording - just pick the steps you want!
Element Selector Tool
Need to find a CSS selector for an element? Use the selector picker:
| Command | What It Does |
|---|---|
get selector |
Click any element to get its selector |
pick element |
Same as above |
inspect element |
Same as above |
After typing the command, hover over elements in the browser - they'll highlight in blue. Click to capture the selector.
Direct Click with CSS Selectors (No AI needed)
When you know the CSS selector, you can click elements directly without AI:
| Command | What It Does |
|---|---|
click #submit-btn |
Clicks element by ID |
click .login-button |
Clicks element by class |
click input[type="submit"] |
Clicks element by attribute selector |
click table > tbody > tr:nth-of-type(2) |
Clicks with CSS combinators and pseudo-selectors |
if exists("#cookie-banner .accept") … click #cookie-banner .accept … end |
Clicks the element only if it exists — exists() tests a CSS selector in any condition, so a missing element is skipped and the run continues (a plain click fails and stops the run when the element is absent) |
Tip: Use get selector to pick an element visually, then copy the selector directly into a click command!
Optional steps: Wrap a click in if exists("...") … end for UI that may or may not appear (cookie banners, "maybe-open" dialogs) so a missing element never stops the automation.
Direct Fill with CSS Selectors (No AI needed)
When you know the CSS selector, you can fill fields directly without AI:
| Command | What It Does |
|---|---|
fill input[name="email"] with [email protected] |
Fills the email input directly |
fill {{SECRET.PASSWORD}} into #password |
Fills the password field with a secret |
fill .search-input with hello |
Fills element by class selector |
Tip: CSS selectors are detected automatically when they contain [], start with #, or start with .. This is faster than AI-powered fill and works without any AI configuration!
Keys and Focus
| Command | What It Does |
|---|---|
press Tab |
Sends the key itself, wherever the focus currently is (type Tab would type the letters T-a-b) |
press Enter in #search |
Focuses that element first, then sends the key |
press Control+A |
Chords work; so do Escape, Backspace, Delete, the arrows, Home/End/PageUp/PageDown and F1–F24 |
read focus / read active |
Reads back whatever element has the focus — its value, or its text, or its label |
Note: a word that is not a key name is still a button label, so press Cancel keeps clicking the Cancel button.
Click by Text (No AI needed)
Click the first element on the page that contains specific text. This works without AI and is useful when elements don't have stable CSS selectors:
| Command | What It Does |
|---|---|
click Sign In |
Clicks the first element containing "Sign In" (direct text match, no AI) |
click text "Submit Order" |
Same as above, explicit syntax (also: click by text) |
How it works: The command searches all visible elements on the page. It prioritizes exact matches, interactive elements (buttons, links), and shorter text. If multiple elements match, the best candidate is clicked automatically.
Attribute fallback: If no element is found by visible text, the command also searches name, id, and alt attributes. This is useful for elements without visible text like <input type="image" name="Search">. When matched by an attribute, the assistant tells you where it found the match (e.g., "matched by attribute: name").
Find Elements by Text (No AI needed)
Search for elements by their visible text content without clicking them:
| Command | What It Does |
|---|---|
find by text "Dashboard" |
Lists all elements containing "Dashboard" with their selectors |
find on selected |
Extracts interactive elements from your current text selection in the browser |
Select Dropdown Options (No AI needed)
Pick an option from a <select> dropdown by its visible text:
| Command | What It Does |
|---|---|
select "January" from select[name="month"] |
Selects "January" from the month dropdown |
pick "United States" from #country |
Selects "United States" from the country dropdown |
choose "High" from .priority-select |
Selects "High" from a priority dropdown |
Tip: The option is matched by visible text first, then by value attribute. If no match is found, the assistant shows all available options so you can pick the right one.
Frames, Iframes & Link Discovery (No AI needed)
Some websites use <iframe> or legacy <frame>/<frameset> elements to embed content. The assistant can navigate into these frames so you can interact with their content.
| Command | What It Does |
|---|---|
list iframes |
Show all iframes and frames in the current context (works at any nesting level) |
list frames |
Same as above |
list links |
List all links in the current frame with text, href, class, target, and CSS selectors |
list links [keyword] |
Filter links by keyword (searches text, href, class, name, id, title) |
switch to iframe #payment-frame |
Enter an iframe by CSS selector |
switch to frame Menu |
Enter a frame by name (bare name resolves automatically) |
exit iframe |
Return to the previous level (or main page) |
exit frame |
Same as above |
highlight [selector] |
Highlight an element with a red outline (default). Use to visually identify frames, iframes, or any element |
highlight [selector] [color] |
Highlight with a specific color: red, blue, green, black, white, orange |
clear highlights |
Remove all highlights from the page (alias: unhighlight) |
How frame context works: Once you switch into a frame, all subsequent commands (js, click, fill, list links, list iframes, show elements, etc.) execute inside that frame. Use exit iframe to go back one level.
Understanding Nested Frames
Many enterprise applications have multiple levels of frames: a top-level frameset with named frames, and inside each frame there may be additional iframes. The key is to navigate one level at a time:
Page (top level)
├── frame "Header" ← top-level frame
├── frame "Menu" ← top-level frame
├── frame "Body" ← top-level frame
│ └── iframe #iFrame_Tab ← NESTED iframe inside Body
│ └── (your links, tables, forms are here!)
└── frame "hiddenFrame" ← hidden utility frame
To reach content inside a nested iframe, you switch into each level:
switch to frame Body— enter the Body framelist iframes— now shows iframes inside Body, not the top-level onesswitch to iframe #iFrame_Tab— enter the nested iframe- All commands now operate on the actual content inside that iframe
Example: Finding Where an Element Lives
Can't find an element? It's probably inside a nested frame. Follow this discovery automation:
# 1. List the top-level frames
list frames
# Frame 0: name="Header" (986x40)
# Frame 1: name="Menu" (184x832)
# Frame 2: name="Body" (802x832)
# 2. Switch to the most likely frame (usually "Body" or "Content")
switch to frame Body
# 3. Check for nested iframes inside this frame
list iframes
# iFrame 0: id="iFrame_Content" src=".../search.jsp" (800x600)
# 4. Switch into the nested iframe
switch to iframe #iFrame_Content
# 5. Now search for your element using list links
list links
# 1. "View Details" class="action_link" target="Body"
# href: /app/actions?id=123&operation=view...
# selector: a.action_link
# 6. Filter links by keyword for faster results
list links employee
# 1. "Employee 12345" class="action_link"
# href: /app/actions?matricola=12345...
# 7. Click the link you found
click a.action_link[href*="matricola=12345"]
# 8. Exit back when done: iframe → frame → main
exit iframe # back to Body frame
exit iframe # back to main page
Example: Simple Frameset Navigation
# 1. See all frames on the page
list frames
# Frame 0: name="Header" src=".../header.jsp"
# Frame 1: name="Navigation" src=".../nav.jsp"
# Frame 2: name="Content" src=".../main.jsp"
# 2. Switch into the Navigation frame
switch to frame Navigation
# 3. Now all commands run inside the Navigation frame
click Reports
click Monthly Summary
# 4. Switch to the Content frame to see results
exit frame
switch to frame Content
# 5. Interact with the content
list links report
read table
click #export-button
# 6. Return to main page when done
exit frame
Example: Embedded iFrame (Payment Form)
# 1. On a checkout page with an embedded payment form
list iframes
# iFrame 0: id="stripe-frame" src="https://js.stripe.com/..."
# 2. Switch into the payment iframe
switch to iframe #stripe-frame
# 3. Fill the payment form inside the iframe
fill input[name="cardnumber"] with 4242424242424242
fill input[name="exp-date"] with 12/28
# 4. Return to the main page to click submit
exit iframe
click #place-order
Tips:
- Start with
list framesto see the top-level layout - Use
list iframesinside a frame to discover nested iframes (it always shows what's inside the current context) - Use
list links [keyword]to quickly find a specific link by any attribute (text, href, class, name, id) - For named frames, you can use just the name:
switch to frame Content - For iframes with IDs, use CSS selectors:
switch to iframe #my-iframe - Both
<iframe>and legacy<frame>elements are fully supported - Use
exit iframeonce per level to go back (iframe → frame → main page)
Page/Window Navigation (No AI needed)
Some websites open popups or new windows (via window.open(), target="_blank", or JavaScript calls like onclick="openWindow()"). The assistant can switch between these windows so you can interact with the popup content.
| Command | What It Does |
|---|---|
list pages |
Show all open windows/pages with their index, URL, title, and which one is active |
switch to page last |
Switch to the most recently opened window (the popup) |
switch to page 0 |
Switch to a window by index (0-based) |
switch to page "causale" |
Switch to a window whose URL contains the keyword |
Example: Interacting with a Popup
# 1. Click a button that opens a popup window
js scegliCausale(5)
# 2. List all open pages to find the popup
list pages
# [0] https://app.example.com/main (active)
# [1] https://app.example.com/popup/causale
# 3. Switch to the popup
switch to page last
# 4. Interact with the popup content
click Approve
fill #note with "Approved"
# 5. Switch back to the main window
switch to page 0
Tips:
- Use
list pagesto see all windows after a popup opens switch to page lastis the quickest way to jump to a newly opened popup- Frame context is reset when switching pages — you start at the main frame of the new page
- You can switch back and forth between windows as many times as needed
Table Read & Fill (No AI needed)
Read HTML tables and fill their input fields by row and column index. Useful for data entry forms, timesheets, attendance tables, and similar grid-based interfaces.
| Command | What It Does |
|---|---|
read table |
Read the first table on the page, showing all rows, columns, and editable fields |
read table #my-table |
Read a specific table by CSS selector |
fill table row 0 col 2 with 8 |
Fill the input at row 0, column 2 with "8" (0-indexed) |
read table pw |
Playwright-based table read — better for tables inside frames or complex pages |
read table pw #my-table |
Playwright-based read of a specific table |
fill table pw row 0 col 2 with 8 |
Playwright-based table fill — better for frames or complex pages |
Example: Filling a Timesheet Table
# 1. Read the table to see its structure
read table
# Output:
# Table: #timesheet (5 rows)
# row\col [0] Employee [1] Date [2] Hours [3] Notes
# 0 John Smith 2026-01-15 [text] 8 [text]
# 1 Jane Doe 2026-01-15 [text] [text]
# 2 Bob Wilson 2026-01-15 [text] 7.5 [text]
# 2. Fill empty cells
fill table row 1 col 2 with 8
fill table row 1 col 3 with Remote work
# 3. For tables inside frames, switch to the frame first
switch to frame Content
read table
fill table row 0 col 2 with 8
Tips:
- Row and column indices are 0-based
- Editable cells are highlighted with their input type:
[text],[select],[checkbox] - For dropdowns, use the visible option text:
fill table row 0 col 1 with January - Checkboxes accept
true/yes/1orfalse/no/0 - Secrets are supported:
fill table row 0 col 2 with {{SECRET.VALUE}} - Playwright variants:
read table pwandfill table pwuse Playwright instead of raw JS — better for tables inside frames or complex pages
Page Analysis (AI-Powered)
Discover what's on a page before building your automation:
| Command | What It Does |
|---|---|
show elements |
Analyze the page and list all interactive elements with their CSS selectors |
what's on the page? |
Same as above - natural language variant |
analyze the page |
Same as above - get a structured breakdown of the page |
The show elements command returns a categorized list of:
- Forms - with all their fields and submit buttons
- Buttons - clickable actions with their purpose
- Input fields - text boxes, checkboxes, dropdowns
- Links - navigation and action links
- Interactive elements - modals, dropdowns, accordions
- Data displays - tables, lists, cards
Each element includes its CSS selector, making it easy to use in subsequent commands or automation steps.
Smart Commands (AI-Powered)
When AI is configured, you can describe what you want in plain language:
| Example | What Happens |
|---|---|
search and click the login button |
AI finds and clicks the login button |
find the search box |
AI locates the element and shows its selector |
fill "hello" into the message field |
AI finds the field and types "hello" |
what step types are available? |
AI answers your question |
Using Secrets in Commands
When your automation has bound secrets, you can use them in commands:
| Example | What Happens |
|---|---|
fill {{MYSITE.USERNAME}} into the email field |
Fills the username from your bound secret |
type {{MYSITE.PASSWORD}} |
Types the password into the focused field |
open https://api.example.com?key={{API.VALUE}} |
Opens URL with your API key injected |
js fetch('/api?token={{AUTH.VALUE}}') |
Runs JavaScript with secret injected |
Security: Secrets are resolved on your desktop - they never go through the server during command execution. Tier 2 secrets require your vault to be unlocked.
AI Configuration
Smart commands work immediately with Nocarta AI — no setup needed. If you want to use your own LLM provider instead, configure it in Settings:
- Go to Settings
- Scroll to LLM Configuration
- Click Add LLM and choose a provider
- Enter your API key and select a model
Supported providers:
- Ollama - Free, runs locally on your computer
- OpenAI - GPT models (requires API key)
- Anthropic - Claude models (requires API key)
- Google Gemini - Gemini models (requires API key)
- Custom (OpenAI-compatible) - vLLM, LM Studio, Together AI, Groq, and more
Your own LLMs take priority when configured. Nocarta AI remains available as a fallback. See the AI Assistant & LLM Configuration guide for details.
How Automations Work
- Create: Choose a mode (Steps, Script, or Browser Script) and build your automation
- Bind: Connect secrets for credential injection (optional step)
- Execute: Run on your desktop with a live browser or as a script
Creating an Automation
Automation creation happens in 2 steps in the new wizard:
Step 1: Automation Description
- Navigate to New Automation
- Fill in the details:
- Name — a unique name to identify the automation
- Description — instructions or context about what the automation does
- Browser Type — choose between Chromium (recommended), Firefox, or WebKit
- Timeout — max time in seconds for the entire execution (default: 300s)
- Headless Mode — run the browser without visual interface (faster)
- Click Next to go to Step 2
Step 2: Choose Execution Mode
Choose how you want the automation to be built:
| Mode | Description | Best For |
|---|---|---|
| Steps (Steps Mode) |
Define individual actions manually: Click, Fill, Navigate, Wait, etc. Each step is a discrete action with configurable timeout and error handling. | UI automation, forms, website navigation |
| Script (Script Mode) |
Write a script using assistant commands with variables, loops, conditionals, and functions. The script is processed by the assistant. | Complex logic, data processing, loops and conditions |
| Browser Script (Browser Script Mode) |
The same automation language in browser mode, for HTTP API automation: REST requests, JSON handling, integration with external APIs. | API-based automation, webhooks, data pipelines |
Creating Automation (Steps Mode)
- After choosing Steps in Step 2, you'll see the steps editing area
- Click Add Step to create the first action
- For each step, configure:
- Step Type — Navigate, Click, Fill, Select, Wait, Scroll, Screenshot, etc.
- Selector — the CSS selector, XPath, or text to target an element (not needed for Navigate or Wait)
- Value — the URL, text to type, key to press, or condition to wait for
- Timeout — max wait per step in milliseconds (default 30000 = 30s)
- Delay Before / After — extra pause in ms around the step
- Continue on error — check if the automation should skip this step on error
- Use the Quick Help (expand question mark icon) to see example syntax
- Click Create Automation to save
- After creating, you can bind Secrets in the edit tab
Tips for Steps Mode:
- Use the Assistant to discover selectors: type
open [url], thenget selectorto visually pick elements - You can convert an automation with steps to Script using the Convert to Script button
- CSS selectors are faster; text selectors are more readable but less stable
Creating Automation (Script Mode)
- After choosing Script in Step 2, you'll see a text editor for script
- Write your assistant commands as a script
- Supports: variables, loops, conditionals, functions, instance data, and date/time
- Use the Quick Help to see variable and control structure syntax
- Click Create Automation to save
Quick Example:
url = "https://example.com"
open #{url}
fill #email with [email protected]
click #login-button
wait for #dashboard
Creating Automation (Browser Script Mode)
- After choosing Browser Script in Step 2, you'll see the script editor
- Write the automation language in browser mode:
httprequests plus variables, loops, and conditionals - Supports:
http get/post/put/patch/delete, headers, authentication, real variables and expressions - Use Check syntax to validate before saving
- Click Create Automation to save
Quick Example:
r = http get "https://api.example.com/users"
query page: 1
auth bearer {{API.TOKEN}}
end
validate r.status == 200
Working in the Script Editor
The editor on an automation's Script tab colours the script as you type, numbers the lines, counts the commands, and completes verbs: start typing cl and pick click # with the arrow keys, then Tab or Enter.
Right-click a line for the things you do most often:
- Run this line in the assistant — sends just that line (or every line the selection touches) to the assistant chat, so you can try one command without running the whole script
- Comment out / Uncomment, Duplicate line, Delete line, Copy line
Every item works on whole lines, and blank lines and comments are dropped before anything is sent to the assistant.
When a command fails, the message appears in the chat and as a red notice in the corner. The notices stack, and they stay for 5 seconds after the last one — so a run that keeps failing keeps its errors on screen instead of each one vanishing before you read it. Click the × to dismiss one.
Step Types (Steps Mode)
The following step types are available when you choose Steps Mode when creating the automation:
| Step | Description | Example |
|---|---|---|
| Navigate | Go to a URL | https://example.com/login |
| Click | Click an element | Button, link, checkbox |
| Fill | Enter text in a field | Username, search box |
| Select | Choose dropdown option | Country selector |
| Wait | Pause for condition | Wait for element to appear |
| Validate | Assert condition | Check if logged in |
| Extract | Get data from page | Scrape a value |
| Screenshot | Capture current state | For debugging/audit |
| Scroll | Scroll the page | Scroll to element |
| Hover | Mouse over element | Trigger dropdown menu |
| Press Key | Keyboard input | Enter, Tab, Escape |
| Press Sequentially | Type text char by char | Slow typing for reactive fields |
| Sleep | Pause for fixed time | Wait 2000ms between steps |
| JavaScript | Run custom JS | Advanced interactions |
Selectors
Each step that interacts with the page needs a selector. Use the get selector command to pick elements visually, or use these formats:
| Type | Syntax | Best For |
|---|---|---|
| CSS | #login-button, .submit-btn |
IDs and classes |
| XPath | //button[@type='submit'] |
Complex queries |
| Text | "Sign In" |
Button/link text |
Tips:
- Use
get selectorin the assistant to pick elements visually - CSS is fastest and most reliable
- IDs (like
#login-btn) are the most stable - Text selectors are readable but may break if text changes
Binding Secrets
After creating your automation, you can bind credentials stored in the Vault for secure injection in your steps or scripts:
- Create your secrets in the Vault first
- Open the created automation
- Go to the Bound Secrets tab
- Click Bind Secret
- Map a secret to a placeholder:
{{ALIAS.FIELD}}- Use the syntax:
{{STORE_NAME.VALUE}},{{STORE.USERNAME}},{{STORE.PASSWORD}},{{STORE.URL}} - In Steps, use the placeholder directly:
fill #email with {{MYSITE.USERNAME}} - In Scripts, also use:
fill #email with {{MYSITE.USERNAME}} - In Browser Scripts, use:
auth bearer {{API.VALUE}}
- Use the syntax:
Security: Credentials are injected on your device at runtime — on the desktop app for desktop automations, inside the browser sandbox for Browser Scripts. The server never sees the actual values during execution. Tier 2 secrets require your vault to be unlocked.
Executing Automations
From Web Interface
- Open the automation
- Click Execute
- If not on desktop: You'll be prompted to open the desktop app
From Desktop App
- Open Nocarta Desktop
- Navigate to Automations
- Click Run on any automation
- Watch the browser automation live
From Mobile (Dispatch)
- Open a Notebook on your phone
- Click Dispatch to Desktop
- Select the automation
- Execution starts on your desktop
Batches
For running the same automation with different inputs:
- Create an Automation Batch
- Add multiple runs with different parameters
- Execute the batch
- Each run executes sequentially with its own data
Use cases:
- Process multiple invoices
- Fill multiple forms
- Batch data entry
Script Mode (Assistant Commands)
Script Mode lets you write assistant commands as a script with variables, loops, conditionals, functions, and access to form instance data. The script is interpreted statement by statement at execution time by the same engine that powers the assistant chat — one language, one live variable environment shared by the chat, script runs, and the debugger.
Creating a Script Mode Automation
- Navigate to New Automation or edit an existing one
- On the Description tab, set Execution Mode to Script
- Switch to the Editor tab — write your script there
- Click Run in Assistant to execute, or Update Automation to save
Variables
# String
url = "https://example.com"
greeting = "Hello"
# Number
count = 42
total = 2 + 3
# Array
items = ["a", "b", "c"]
# Hash
config = {url: "https://app.com", port: 8080}
# Bare names in expressions; #{...} inside command text
open #{url}
fill #count with #{total}
Variables are plain assignments — there is no set keyword. In conditions, arithmetic, and other expressions, reference variables by bare name (total + 1). Inside command text (selectors, URLs, fill values) and double-quoted strings, interpolate with #{...}. Referencing an undefined variable is a runtime error naming it, with the line number. Secret placeholders {{SECRET.PASS}} pass through untouched — they are injected on your desktop at runtime.
Command text and message text (log, stuck, finish, ask) also accept two shortcuts: a value slot that is just one bare variable name works directly — select opt from #dept is the same as select #{opt} from #dept — and wrapping the whole slot in quotes is optional, so open "https://x" and open https://x render identically. An undefined bare name falls back to the literal word rather than erroring.
String Methods
| Method | Example | Result |
|---|---|---|
.upcase | name.upcase | "HELLO" |
.downcase | name.downcase | "hello" |
.length | name.length | 5 |
.strip | s.strip | trims whitespace |
.reverse | s.reverse | "cba" |
.replace(old, new) | s.replace("a", "b") | substitution |
.split(sep) | csv.split(",") | array from string |
.slice(start, end) | s.slice(0, 5) | substring |
.includes(str) | s.includes("x") | true/false |
Type Conversion Methods
Convert between types. Instance data field values are always strings — use .to_i or .to_f when you need to do arithmetic with them.
| Method | Example | Result |
|---|---|---|
.to_i | s.to_i | string → integer (errors on non-numeric) |
.to_f | s.to_f | string → float (errors on non-numeric) |
.to_s | n.to_s | number/boolean → string |
.abs | n.abs | absolute value |
.ceil | n.ceil | round up |
.floor | n.floor | round down |
.round | n.round | round to nearest integer |
Example: Instance Field Arithmetic
# Instance fields come as strings — convert before arithmetic
hours = instance.fields.monthly_hours.to_i
rate = instance.fields.hourly_rate.to_f
total = hours * rate
rounded = total.round
fill #total with #{rounded}
Array Methods
| Method | Example | Result |
|---|---|---|
.length | items.length | 3 |
.first / .last | items.first | "a" |
.join(sep) | items.join(", ") | "a, b, c" |
.reverse | items.reverse | ["c", "b", "a"] |
.includes(item) | items.includes("b") | true |
.sort | items.sort | sorted array |
.uniq | items.uniq | remove duplicates |
.compact | items.compact | remove empty values |
[n] | items[0] | "a" |
.push(v) | items.push("d") | append to array (mutates) |
Hash Methods
| Method | Example | Result |
|---|---|---|
.keys | config.keys | ["url", "port"] |
.values | config.values | ["https://app.com", 8080] |
.length | config.length | 2 |
| dot access | config.url | "https://app.com" |
| bracket access | config["url"] | "https://app.com" |
Method chaining is supported: items.uniq.sort.join(",")
Built-in Date Functions
Date and time functions are built into the language and can be called anywhere an expression is legal — in assignments, conditions, or inside #{...} interpolation in command text.
| Function | Example | Result |
|---|---|---|
weekday(y, m, d) | weekday(2026, 2, 1) | "SUN" |
weekday(y, m, d, locale) | weekday(2026, 2, 1, "it") | "DOM" |
days_in(y, m) | days_in(2026, 2) | 28 |
today() | today() | "2026-03-06" |
today(format) | today("dd/MM/yyyy") | "06/03/2026" |
now() | now() | "2026-03-06 14:30:00" |
now(format) | now("hh:mm tt") | "02:30 PM" |
format_date(y, m, d) | format_date(2026, 3, 6) | "2026-03-06" |
format_date(y, m, d, fmt) | format_date(2026, 3, 6, "dd/MM/yyyy") | "06/03/2026" |
Format Tokens
| Token | Meaning | Example |
|---|---|---|
yyyy | 4-digit year | 2026 |
MM | 2-digit month | 03 |
dd | 2-digit day | 06 |
HH | 24-hour hours | 14 |
hh | 12-hour hours | 02 |
mm | Minutes | 30 |
ss | Seconds | 45 |
tt | AM/PM | PM |
Weekday Locales
| Locale | Days (Sun–Sat) |
|---|---|
"en" (default) | SUN, MON, TUE, WED, THU, FRI, SAT |
"it" | DOM, LUN, MAR, MER, GIO, VEN, SAB |
"pt" | DOM, SEG, TER, QUA, QUI, SEX, SAB |
"es" | DOM, LUN, MAR, MIE, JUE, VIE, SAB |
"fr" | DIM, LUN, MAR, MER, JEU, VEN, SAM |
"de" | SO, MO, DI, MI, DO, FR, SA |
Example: Timesheet Loop
year = 2026
month = 2
total = days_in(year, month)
each i in 0..total - 1
day = i + 1
dow = weekday(year, month, day, "it")
date = format_date(year, month, day, "dd/MM/yyyy")
if dow != "DOM"
fill table row i col 0 with #{date}
fill table row i col 1 with #{dow}
end
end
Conditionals
if status == "active"
click #activate
elsif status == "pending"
click #wait
else
click #skip
end
Conditions are ordinary expressions — variables by bare name, full operator precedence, and parentheses. Operators: ==, !=, >, <, >=, <=. Compound: and, or, not. Substring and membership checks use the .includes(...) method: if title.includes("Invoice").
Loops
# Count with a range (i takes each value in turn)
each i in 0..2
fill table row i col 0 with data
end
# Iterate over a list
each item in items
click ##{item}
end
# With index variable
each item, idx in items
fill table row idx col 0 with #{item}
end
# Loop while a condition holds
while exists("#next-page")
click #next-page
end
Counted loops use ranges (each i in 1..n). break exits the innermost loop and next skips to the following iteration.
Variable scoping inside loops
Scoping is lexical: if/each/while bodies share the enclosing scope, so a variable assigned inside a loop is still visible after it. Only the loop variable itself (each n in …) is scoped to the loop; function bodies (def) are separate scopes.
# Accumulator declared outside, modified inside the loop
total = 0
each n in [1, 2, 3]
total = total + n # or the shorthand: total += n
end
log #{total} # → "6"
# A variable first assigned inside the loop lives on after it
each n in [1, 2, 3]
y = n
end
log #{y} # → "3" (the loop variable n is gone, y remains)
This makes "linear-walk to find a value" patterns work correctly — and break lets you stop at the first match:
my_row = 0
each r, idx in rows
if idx == target_idx
my_row = r # outer my_row is updated
break
end
end
log #{my_row} # the matched row
Expressions inside interpolation
#{...} holds a full expression — indexing, arithmetic, string building, and method calls all evaluate in one pass, no nesting needed:
rows = [10, 20, 30, 40]
i = 2
log #{rows[i]} # → "30"
# Dynamic field-name access is string-keyed hash access
chapter = 5
log #{instance.fields["title_" + chapter.pad_zero]}
# chapter.pad_zero → "05", so this reads the title_05 field
Padding
| Method | Example | Result |
|---|---|---|
.pad_zero | n.pad_zero | 2-digit string (5 → "05") |
.pad_left(n, char) | s.pad_left(4, "0") | "0007" from "7" |
.pad_right(n, char) | s.pad_right(6, "-") | "abc---" from "abc" |
Nullish-coalescing (??)
Provide a default value when a value might be nil. Useful for optional / sparse instance fields:
# Safe field access — falls back to "N/A" when the field is absent
log #{instance.fields.optional_phone ?? "N/A"}
# Other defaults
phone = instance.fields.optional_phone ?? "N/A"
count = instance.fields.item_count ?? 0
A missing instance field simply reads as nil — ?? is an optional convenience for supplying a default, not a requirement. (Referencing an undefined variable, by contrast, is a runtime error naming it.)
Table cursor
By default, fill table and read table target the first <table> in the active frame. To scope them to a different table on a multi-table page, set a cursor with switch to table SELECTOR:
switch to table "div.invoices > table"
fill table row 2 col 3 with OK
read table
# Cursor stays sticky until cleared:
fill table row 5 col 1 with Done
exit table # back to first-<table> default
Selectors with >, spaces, or pseudo-classes work as-is; surrounding double or single quotes are stripped.
The cursor is automatically cleared on any frame transition (switch to frame, switch to iframe, exit frame) because a CSS selector is document-scoped. If you need the cursor across frames, re-set it after each frame change.
Functions
# Define a reusable function
def login(url, user, pass)
open #{url}
fill #user with #{user}
fill #pass with #{pass}
click #submit
end
# Call it
login("https://app.com", "admin", "{{SECRET.PASS}}")
Functions can contain loops, conditionals, and any other commands. They are real functions: an early return works and returns a value you capture with an assignment, e.g. col = find_first_empty(1). Parameters can have defaults (def f(a, b = 0)). Secret placeholders inside string arguments pass through untouched.
The last statement is the value — the same rule Ruby uses, so return is only for leaving early:
def focused_field()
read focus # this line's value IS the function's value
end
def status_of(row)
if row == 0
"header" # if gives back the branch that ran
else
read table cell row row col 3
end
end
label = status_of(0)
A function that ends in a command which captures nothing (click, fill, press), in a loop, or in a log gives back nil.
Instance Data (Batches)
When a script runs as part of a batch, it receives instance data from the linked form instance. Access fields with instance.fields.field_name:
# Access instance fields
fill #email with #{instance.fields.email}
fill #phone with #{instance.fields.phone}
# Access metadata
fill #id with #{instance.instance_id}
fill #pub with #{instance.metadata.publisher}
# Iterate over all fields
each field in instance.fields
fill [name="#{field.name}"] with #{field.value}
end
instance.fields iterates as {name, value} entries, automatically filtering out UUID-keyed entries. A missing or empty field reads as nil — use ?? when you want a default. Dynamic field names are string-keyed hash access: instance.fields["prefix_" + var].
Important: All instance field values are strings, even if they look like numbers. Use .to_i or .to_f to convert before arithmetic:
# BAD — arithmetic on strings is an error
total = instance.fields.hours * instance.fields.rate # "8" * "25" = error
# GOOD — convert to numbers first
hours = instance.fields.hours.to_i
rate = instance.fields.rate.to_f
total = hours * rate # 8 * 25.0 = 200
Complete Example
# Define a reusable notification function
def notify(email)
fill #recipient with #{email}
click #send
sleep 1
end
# Split contacts from instance data into an array
emails = instance.fields.contacts.split(",")
# Fill the company field
fill #company with #{instance.fields.name}
# Notify each contact
each email in emails
notify(email)
end
Comments
Lines starting with # or // are comments and will be ignored. Blank lines are also skipped.
Browser Scripts
Browser Scripts are an alternative to step-based automations. You write the same automation language used everywhere in Nocarta, in browser mode: the http verbs plus the core language (variables, strings, arrays, loops, if, validate) — designed for HTTP API automation, data transformation, and integration tasks.
Browser Scripts are ideal when your automation is API-driven (REST calls, webhooks, data pipelines) rather than UI-driven (clicking buttons, filling forms). They run in an isolated sandbox inside your browser, and requests go straight from your browser to the target — never through Nocarta’s servers, and never carrying your Nocarta session. Page-driving verbs like open and click need the desktop app; using one in a Browser Script gives you a clear error saying exactly that.
You must list the hosts a script may contact, on the automation’s Settings tab. The list is deliberately strict: an empty list reaches nothing at all, so a script with no configured hosts cannot make a single request. Use exact hostnames (api.example.com) or a wildcard (*.example.com).
Creating a Browser Script Automation
- Navigate to New Automation or edit an existing one
- On the Description tab, set Execution Mode to Browser Script
- A script editor appears — write your script there (the Check syntax button also flags verbs that need the desktop app)
- Click Update Automation to save
Running from Diagram Output
Browser Scripts can be linked to Notebooks for diagram-to-API automations:
- Analyze a diagram in a Notebook (Drawing Translator)
- On the diagram output page, link a Browser Script automation via settings
- Open the Convert dropdown and click Run Script
- The Execution Monitor opens, showing real-time progress
The script receives the diagram context (nodes, edges, type) automatically, so you can iterate over diagram data and make API calls for each element.
Language Reference (browser mode)
HTTP Requests
Make HTTP requests to any API on the automation’s allowed-hosts list. Requests go directly from your browser to the target. Capture the response into a variable with =; add clauses in a block closed by end:
r = http get "https://api.example.com/users"
query page: 1
header Accept: application/json
auth bearer {{GITHUB.VALUE}}
end
Available verbs: http get, http post, http put, http patch, http delete. A request with no clauses fits on one line: r = http get "https://api.example.com/users"
| Clause | Purpose | Example |
|---|---|---|
query |
URL query parameter (repeatable) | query page: 1 |
header |
Custom HTTP header (repeatable) | header Accept: application/json |
auth |
Authentication | auth bearer {{TOKEN.VALUE}} |
json |
JSON request body — a real hash, values are expressions | json {title: "Test", userId: 1} |
body |
Raw string body | body "plain text content" |
The captured response is an ordinary value:
| Field | Returns |
|---|---|
r.status |
HTTP status code (e.g., 200). A non-2xx response is a value you inspect — it never aborts the run by itself. |
r.json |
Parsed JSON body (or nil when the response is not JSON) |
r.json.field, r.json.items[0].id |
Nested fields and array elements |
r.body |
Raw response text |
r.headers["content-type"] |
Response header value (lowercase names) |
Variables
count = 0
username = r.json.login
message = "Hello #{username}, you have #{count} items"
Variables are ordinary values: use them bare in expressions (count + 1), interpolate them into strings with #{...}, and use string/array methods (text.split(","), items.length, name.upcase).
Control Flow
each — loop over collections (add , i for the 0-based index):
each user, i in r.json.users
if user.status == "active"
http post "https://api.example.com/notify"
json {user_id: user.id, message: "Hello"}
end
log Processing #{user.name}
end
end
if / elsif / else — conditional execution:
if r.status == 200
log Success
else
log Failed with status #{r.status}
end
Operators: ==, !=, >, <, >=, <=, and, or, not, ?? (default when missing)
Logging, Waiting & Validation
| Command | Purpose | Example |
|---|---|---|
log |
Write to execution log | log Found #{count} items |
sleep |
Pause for seconds | sleep 2 |
validate |
Assert a condition (stops on failure) | validate r.status == 200 |
stuck |
Stop and say why, in your own words. Recorded as stuck with your category, so “the other service is down” is not filed as “your script is broken”. | stuck "Portal under maintenance", category: "external_outage" |
finish |
Stop early and successfully — nothing after it runs. Use it when there is simply nothing to do. | finish "nothing to invoice this month" |
Comments
# This is a comment
// This is also a comment
Diagram Context
When running from a Notebook diagram, the script gets a diagram variable with the analysis data:
| Expression | Returns |
|---|---|
diagram.nodes |
All diagram nodes |
diagram.edges |
All diagram edges |
diagram.type |
Diagram type (flowchart, mind_map, etc.) |
Example — create a Trello card for each node in a flowchart:
# Create a Trello card for each diagram node
each node in diagram.nodes
http post "https://api.trello.com/1/cards"
query key: "{{TRELLO.VALUE}}"
query token: "{{TRELLO_TOKEN.VALUE}}"
json {name: node.label, idList: list_id}
end
log Created card: #{node.label}
end
Secrets in Scripts
Use bound secrets with {{ALIAS.VALUE}} placeholders, just like in step-based automations:
r = http get "https://api.github.com/user"
auth bearer {{GITHUB.VALUE}}
end
log Logged in as #{r.json.login}
Security: Secret values are resolved inside the sandbox at the moment the request is sent — they are never stored in the script and never pass through Nocarta’s servers in resolved form. Tier 2 secrets require your vault to be unlocked. A missing credential stops the run with the placeholder’s name; an empty value is never silently sent.
Execution Monitor
When a script runs, the Execution Monitor shows real-time progress:
- Step list (left panel) — each command with status icon
- Detail panel (right panel) — request/response details, variables, errors
- Progress bar — completed vs. total steps
- Pause — pause before the next step
- Stop — abort execution
- Copy Log — export full execution data as JSON
Complete Example: API Chain
# Authenticate and fetch user data
auth_r = http post "https://api.example.com/auth"
json {email: "[email protected]", password: "{{SECRET.VALUE}}"}
end
validate auth_r.status == 200
token = auth_r.json.token
# Fetch paginated data
items_r = http get "https://api.example.com/items"
header Authorization: Bearer #{token}
query page: 1
query limit: 50
end
log Retrieved #{items_r.json.data.length} items
# Process each item
each item in items_r.json.data
if item.status == "pending"
http patch "https://api.example.com/items/#{item.id}"
header Authorization: Bearer #{token}
json {status: "processed"}
end
log Updated item #{item.id}
end
end
Signing an automation
You can sign an automation with a keypair from your Vault, and Nocarta will tell you if it is ever altered. Create the keypair once (Vault → New Secret → type Keypair), then use Sign on the automation’s Settings tab.
What gets signed is more than the script: it also covers the allowed hosts and the credentials bound to the automation. That matters, because the dangerous change is not always in the code — someone who could add a host to your allow-list could send your credentials somewhere new without touching a line of the script.
- Signed — it matches what you approved.
- Changed since signing — something has been altered. Review it and sign again.
- Signature invalid — the signature no longer belongs to the key it claims. Treat this as suspicious.
Tick Refuse to run if altered and the automation will not run at all while it does not match. Without that, signing still tells you — it just does not stop you.
The private half of your keypair is encrypted with your master password and never leaves your browser, so Nocarta cannot sign anything on your behalf. That is exactly what makes the signature worth something. Signing therefore needs your vault unlocked.
Limitations
- No page interaction —
open,click,filland the other page verbs need the desktop app; using one here gives a clear error saying so - Allowed hosts only — a script reaches nothing until you list its hosts, and nothing outside that list
- The target must permit browser access — a service that does not send
Access-Control-Allow-Origincannot be read by any browser script. You can still send the request; you just cannot read the reply. - 5-minute timeout — scripts must complete within 5 minutes
- The assistant is not reachable — a Browser Script that uses
askstops with a message saying so; run it as a desktop automation when you need the assistant. Everything else in the language works here, includingcheckpointandon stuck restart(only itswith fresh browseroption needs the desktop app)
Troubleshooting
| Issue | Solution |
|---|---|
| Automation doesn't start | Ensure Desktop app is running |
| Element not found | Use get selector to verify the element, or add a Wait step |
| Timeout error | Increase step/automation timeout |
| Login fails | Verify secret binding and values |
| Page changed | Update selectors using get selector |
| AI not responding | Check AI connection status in the assistant (click the CPU icon) |
| Recording doesn't capture actions | Make sure recording is started before interacting with the page |
Quick Reference: Assistant Commands
All commands you can use in the Automation Assistant:
Browser (No AI needed)
open [url]- Open a webpage (aliases:go to,navigate to,visit)back- Go back (aliases:go back,previous page)forward- Go forward (aliases:go forward,next page)refresh- Reload page (alias:reload)screenshot- Capture page (aliases:take screenshot,capture screenshot)browser status- Check if browser is openclose browser- Close browser windowpress Tab- Send a key where the focus is;press Enter in #searchfocuses that element first. Chords (press Control+A) andEscape/Delete/arrows/F1-F24included. A word that is not a key name still clicks that buttonread focus- Read the focused element's text (alias:read active); capture it withx = read focus
Recording (No AI needed)
start recording- Begin recording (alias:begin recording)stop recording- Stop recording (aliases:end recording,finish recording)show recorded actions- List captured actions (aliases:recorded actions,what did I record?)save as automation- Save recording (aliases:create automation,save actions)
Element Selection & Text Search (No AI needed)
get selector- Click to pick element (aliases:pick element,inspect element,selector)click Submit- Click first element matching visible text, name, id, or alt (aliases:click text "Submit",click by text)find by text "Dashboard"- Find elements containing text and show their selectorsfind on selected- Extract elements from your current text selectionfind input near "Password"- Proximity finder: locate the element (input/button/select/textarea/link/checkbox/radio/field) nearest a landmark (quoted text or a CSS selector) and return its selector. Capture and reuse:X = find input near "Password", thenclick #{X}if exists("#selector")…click #selector…end- Click only if the element exists; a missing element is skipped and the run continues (exists()works in any condition)
Find never stops the run on no-match. find column, find by text, find on selected, and find … near … return nil (or an empty array) when nothing matches — guard the result with if, e.g. if x != nil. (Action verbs — click/fill, click by text, fill by text — still fail if their target is missing; wrap a click in if exists("...") … end to make it optional.)
Select Dropdown Options (No AI needed)
select "January" from select[name="month"]- Pick dropdown option by visible textpick "Option" from #dropdown- Same as above (aliases:choose)
Frames, Iframes & Link Discovery (No AI needed)
list iframes- Show all iframes/frames in the current context (alias:list frames)list links- List all links in the current frame with selectors and attributeslist links [keyword]- Filter links by keyword (text, href, class, name, id)switch to iframe #id- Enter an iframe by CSS selectorswitch to frame Name- Enter a frame by nameexit iframe- Return one level up (alias:exit frame)highlight [selector]- Highlight an element with a red outlinehighlight [selector] [color]- Highlight with a color (red, blue, green, black, white, orange)clear highlights- Remove all highlights (alias:unhighlight)
Table Read & Fill (No AI needed)
read table- Read table contents with row/col indicesread table #selector- Read a specific table by CSS selectorfill table row 0 col 2 with value- Fill input at row 0, col 2 (0-indexed)read table pw/read table pw #selector- Playwright-based table read (better for frames)fill table pw row 0 col 2 with value- Playwright-based table fill
Direct Click with Selectors (No AI needed)
click #myId- Click by ID selectorclick .myClass- Click by class selectorclick input[type="submit"]- Click by attribute selectorclick div > span- Click with CSS combinatorsclick tr:nth-of-type(2)- Click with pseudo-selectors
Direct Fill with Selectors (No AI needed)
fill input[name="x"] with value- Fill by attribute selectorfill #myId with value- Fill by ID selectorfill .myClass with value- Fill by class selectorfill value into input[name="x"]- Alternative syntax
Secrets (No AI needed)
test secret {{ALIAS.VALUE}}- Test secret placeholder resolution (aliases:check secret,show secret,resolve secret)fill {{ALIAS.VALUE}} into #password- Fill with secret placeholderopen https://api.example.com?key={{API.VALUE}}- Use secrets in URLs
Security: Secrets are resolved on your desktop - they never go through the server during command execution. Tier 2 secrets require your vault to be unlocked.
JavaScript Execution
All JS commands run directly in the browser page — like typing in the Developer Console. Full access to document, window, fetch, and all browser APIs. If you used switch to iframe, JS runs inside that iframe automatically.
js [code]- Execute JavaScript in the pagejs document.title- Single-line JSjs fetch('/api?token={{AUTH.VALUE}}')- JS with secret injectionjs...end- Multi-line JavaScript block (Shift+Enter for newlines,endto finish)js from ~/script.js- Execute JavaScript from a local file (file stays on your machine)
Use return to send a value back, and capture it with an assignment: title = js document.title. Secrets like {{AUTH.TOKEN}} are resolved locally on your desktop before injection.
Playwright Commands
Direct access to the Playwright page object — the same API used by click, fill, find and all assistant commands internally.
pw page.click('#btn')- Single-line Playwright commandpw await page.waitForSelector('.loaded')- Wait for an elementpw...end- Multi-line Nocarta Assistant script blockpw from ~/script.js- Execute Nocarta Assistant script from a local file
The script receives a page argument with the full Playwright Page API. Use return to capture results. Secrets are resolved before execution.
Result Variables
employees = read table- Capture a command's result in a variable (any command works as the right-hand side of an assignment)show variables- List all variables in the environment (aliases:list vars,show results)clear variables- Reset the variable environment (aliases:clear vars,clear results)
Reference a captured result by its bare name in expressions (employees.length, cells.include?("Total")) or with #{...} inside command text. Structured results support dot-path and index access: employees.items[0].name. The same variables are live in the chat, script runs, and the debugger.
Page Analysis (AI required)
show elements- List all interactive elements with selectors (aliases:page elements,elements)what's on the page?- Analyze page structureanalyze the page- Same as above (alias:page analysis)
Smart Commands (AI required)
search and click [description]- AI finds and clicks element by descriptionfind [description]- Find element and show selector (aliases:locate,where is)fill [value] into [field description]- Fill a field by descriptiontype [value]- Type into the focused field- Any question or request in natural language
Note: click is always a direct text match (no AI needed). Use search and click for AI-powered element matching. click with CSS selectors (like click #id or click .class) also works without AI.
Tips & Shortcuts
Command History (Arrow Keys)
The assistant remembers the last 50 commands in each chat session. Navigate through them with your keyboard:
| Key | What It Does |
|---|---|
| Arrow Up | Go to the previous (older) command |
| Arrow Down | Go to the next (newer) command, or return to what you were typing |
Tips:
- Your current draft is preserved - pressing Up then Down returns to what you were typing
- Consecutive duplicate commands are not stored twice
- In multi-line inputs, arrow keys only navigate history when the cursor is at the first or last line
Smart Quotes Auto-Correction
When you paste commands from word processors (Microsoft Word, Google Docs, macOS TextEdit), curly "smart" quotes are automatically replaced with straight quotes. This prevents selector and string errors.
If smart quotes are detected, you'll see a notice:
Smart quotes detected and auto-corrected to straight quotes.
Tip: disable smart quotes in your text editor to avoid this.
Characters corrected: \u201C \u201D (double), \u2018 \u2019 (single), \u201E \u201A (low), \u00AB \u00BB (guillemets) - all replaced with standard ASCII quotes.
Multi-Command Execution (Batch Mode)
Send multiple commands at once, separated by ; (semicolon) or newline (Shift+Enter). They execute sequentially, one after another.
| Separator | How to Type | Example |
|---|---|---|
; |
Type directly on one line | switch to frame Body; list iframes; list links |
| Newline | Press Shift+Enter | Each line becomes a separate command |
Example: Navigate into nested frames in one command
switch to frame Body; list iframes; switch to iframe #iFrame_Tab; list links employee
Example: Multi-line batch
switch to frame Body
list iframes
switch to iframe #iFrame_Tab
list links employee
click a[href*="matricola=12345"]
How it works:
- Commands run one at a time, in order, with results shown after each
- A progress indicator shows
[1/4],[2/4], etc. - If a command fails, the remaining commands still execute
jscommands are not split by;(semicolons are valid JavaScript)
Example: Auto-Login Automation
Step 1: Navigate → https://app.example.com/login
Step 2: Fill → #username → {{username}}
Step 3: Fill → #password → {{password}}
Step 4: Click → button[type="submit"]
Step 5: Wait → .dashboard (wait for dashboard to load)
Step 6: Screenshot → (capture logged-in state)
Or record it:
- Type
open app.example.com/login - Type
start recording - Fill in username and password, click login
- Type
stop recording - Type
save as automation - Edit the saved automation to bind secrets for credentials
When an Automation Gets Stuck
Scripts can hit a wall — a page under maintenance, a renamed button, a slow load. Instead of a cryptic failure, you can make automations explain themselves, recover on their own, and ask for help. These are all optional; leave them out and nothing changes.
Checkpoints, stuck, and fail messages
checkpoint "login"— mark a point you trust. It shows in the run log and becomes the anchor an auto-restart resumes from. Put checkpoints at the top level (not inside an each/while loop or a def).stuck "Portal is under maintenance", category: "external_outage"— stop the run on purpose with your own message. That message is what the user sees, never a raw error. The category is an optional label.on fail message "Could not open the invoices list"— from this line on, if a command fails the user sees your message instead of the technical error.on fail message offclears it.finish "Nothing to do for this employee"— end the run early as a success, with your message in the run log. Use it for instances the script has nothing to do for (e.g. an empty month): test the precondition at the top,finish, and the rest of the script never runs. The run reports completed, never failed.
Automatic restart
Give a stuck run a second chance without a person watching:
on stuck restart from checkpoint max 2 # resume from the last checkpoint, up to twice
on stuck restart from start max 3 with fresh browser # start over with a clean browser
on stuck restart from command max 1 # retry just the failing command
Restarts run on your desktop. The automation only reports a final failure once the attempts are used up, and the run log shows each ↻ restart attempt. The built-in restart_attempt variable (0 on the first run, then 1, 2, …) lets a script behave differently on a retry.
Ask the assistant
When a run is stuck, open it and use Ask the assistant for a diagnosis and a suggested fix. You can pick your own configured AI, Nocarta AI, or — in the desktop app — the local Claude Code agent (which runs entirely on your machine and can look at the page screenshot without it ever leaving your computer). If the assistant proposes a change to the script, you can review the diff and apply it; a revertible version of the previous script is saved first.
Recipes: needs-help
When an automation runs as a step inside a Recipe and gets stuck, the whole recipe pauses in a needs help state instead of failing, and you (the owner) get an email. Open the run and choose Retry (run the step again with the same input), Skip (pass its input through unchanged), or Abort (cancel the run) — the same Ask the assistant diagnosis is available there too.
Need More Help?