Docs

Tranquil API

What a script uses to work with Tranquil itself: tabs to find and open browser tabs, ui to tell or ask the person running it, files to read and write files, and context for facts about the run. The SDK form methods (fields, fill, click and the rest) have their own page, Forms API, and so does running your own code inside a page, Page Code API.

All of it comes from one import:

import { clipboard, config, context, FieldError, files, tabs, ui, workspace } from "tranquil/automation";

tabs

tabs resolves browser tabs; each method returns a Tab handle. A handle is bound to a stable tab, so it survives navigation: tab.url is a live read, not a snapshot. These are the ways to get one, which differ only in how the tab is located:

MethodReturnsDescription
tabs.active()Promise<Tab>The tab you’re looking at (or most recently were). Opens one on the start page if no tab is open.
tabs.find(urlOrRegExp)Promise<Tab>An open tab matching an exact URL string or a RegExp.
tabs.all()Promise<Tab[]>Every open http(s) tab.
tabs.open(url, { background?, location?, activate?, hideURLBar? })Promise<Tab>Opens a tab. background: true opens it off-screen (for scraping or signed-in fetches), capped at about 4 concurrent, inheriting this window’s login session. The other options are under Where a tab opens.
tabs.triggered()Promise<Tab>In a URL-triggered run, the tab whose load fired the trigger.

All of them need the tabs grant (which forms, submit, page-js and browser include).

The Tab handle

What you can do with a handle, apart from the SDK form methods: wait on the page, navigate it, run page code, and change how the tab is presented.

MemberReturnsDescription
tab.waitFor(target, { state?, timeout? })Promise<void>Waits until a target is visible, or with state: "hidden" until it’s gone. Any visible match will do. On timeout, the error says what it found.
tab.waitFor((page) => …)Promise<void>Polls page code until it returns something truthy. Needs page-js.
tab.evaluate((page, ...args) => …, ...args)Promise<T>Runs a function in the page with JSON-serializable args; resolves its return value. Needs page-js. See Page Code API.
tab.useCustomParts(...maps)voidTells fill, clear and fields() where a form that isn’t built to standards keeps a field’s value, open control and clear button. Optional. See Custom Parts.
tab.goto(url, { waitUntil? })Promise<void>Navigates the tab. waitUntil is "load" (default) or "domcontentloaded".
tab.waitForNavigation(opts?)Promise<void>Awaits a navigation something else triggered (a click, a redirect).
tab.frame(urlOrRegExp)Promise<Frame \| null>A form inside an iframe, with the same methods as a tab, or null when no frame matches.
tab.title()Promise<string>The page title.
tab.urlPromise<string>The current URL (a live read).
tab.screenshot()Promise<Uint8Array>A PNG of the tab.
tab.toggleURLBar(hidden?)Promise<boolean>Hides or shows the tab’s address bar; flips it when called with no argument. Resolves to the new state.
tab.close()Promise<void>Closes the tab.

Tabs are AsyncDisposable, so await using tab = await tabs.open(url) closes the tab when the block ends, even if it throws: no leaked tabs in loops.

Where a tab opens

A tab opens in the center, takes focus, and shows an address bar unless you say otherwise:

  • location accepts "center", "right" or "bottom"; a dock location opens the dock as well as the tab;
  • activate: false opens without taking focus, which puts a panel beside your work instead of in front of it;
  • hideURLBar drops the address bar, for a page presented as a panel rather than as a browser (remembered across a window reload).
// Tranquil automation. Rules for writing and changing it: ~/.tranquil/llm/writing-automations.md
// @permissions tabs
import { tabs } from "tranquil/automation";

await tabs.open("https://example.com/", { location: "right", activate: false, hideURLBar: true });

For a tab that’s already open, tab.toggleURLBar() does the same live, hiding the strip with the URL field and the navigation buttons, not the tab itself.

Name the location on every open in a script that uses a dock, including the ones you want in the center. Tranquil remembers where a URL was last opened and reuses that the next time the same URL is opened, so a script that puts one page in a dock can find its later tabs going there too. Passing location explicitly overrides what was remembered.

ui

Three ways to reach the person running the script: tell them something, ask them something, or put a file in front of them. None needs a grant.

MethodDescription
ui.notify(message, { level? })Shows a notification and writes the message to the run output. level is "info" (default), "success", "warning", or "error".
ui.confirm(message, { timeout? })Asks before something that can’t be undone, with Allow and Deny buttons. Resolves true only on Allow; false on Deny, after timeout (default 60 seconds) with no answer, and at once in a run nobody started by hand (a URL trigger). A submission needs a yes: the SDK refuses one unless the run’s most recent ui.confirm returned true.
ui.open(path, { split? })Opens a file in the editor, or reloads and reveals it if it’s already open. split is "left" \| "right" \| "up" \| "down".

files

Reading and writing files next to the script. Paths resolve against the script’s own folder (context.scriptDir).

MethodReturnsDescription
files.write(name, content)stringWrites the file; returns its absolute path (feeds ui.open).
files.read(name)stringReads a file as UTF-8.

Four rules govern both:

  • They work on disk, so files.read sees the saved file, not an unsaved editor buffer.
  • Permissions apply. files.read("input.txt") needs read=input.txt, and files.write("out.md") needs write=out.md. The script’s own folder is not granted for free.
  • You name the file, not the folder.
  • No missing folders are created. For a subfolder, call Deno.mkdirSync(dir, { recursive: true }) first; the SDK has no mkdir of its own.
// Tranquil automation. Rules for writing and changing it: ~/.tranquil/llm/writing-automations.md
// @permissions tabs write=title.txt
import { files, tabs, ui } from "tranquil/automation";

const tab = await tabs.active();
await ui.open(files.write("title.txt", await tab.title()));

context

Facts about the current run (its identity, its folder, why it started, how it stops) and the one way to hand a result back:

MemberDescription
context.runIdThis run’s unique id.
context.scriptDirThe script’s folder, which files resolves against.
context.triggerHow the run started: { kind: "manual" }, { kind: "command", name }, or { kind: "url", url }.
context.signalAn AbortSignal that fires when the run is cancelled: pass it to fetch, timers and anything else you wait on.
context.result(json)Hands back what the run found, as plain JSON (at most 64 KB). It appears in the Runs panel.

Everything else

clipboard, workspace and config, then shell commands:

MethodDescription
clipboard.read() / clipboard.write(text)The system clipboard (distinct from the page’s clipboard inside evaluate). Needs clipboard.
workspace.projectDir()Absolute path of the first open project folder, or "". Needs tabs.
config.get<T>(key) / config.set(key, value)Per-script state that survives runs and restarts, kept under the script’s own section: a script can’t read or write app settings, or another script’s state. get resolves undefined for a key never set; name the type you stored: config.get<string[]>("seen").

To run a shell command, use Deno directly under a declared run= grant:

// Tranquil automation. Rules for writing and changing it: ~/.tranquil/llm/writing-automations.md
// @permissions run=git
const out = await new Deno.Command("git", { args: ["rev-parse", "HEAD"] }).output();
console.log(new TextDecoder().decode(out.stdout).trim());

Page code

For anything the SDK form methods don’t cover, tab.evaluate() runs a function of yours inside the page, and tab.waitFor(fn) polls one; both need page-js. They have their own page: Page Code API, with the typed Page declaration, examples, and when to use a custom parts map instead.