& { failed?: Result };
/**
* Runs command lines as pasted into the console: several lines as one batch,
* checked first and approved once. Follows the page's policy, as callTool does. Resolves to the
* console's text, each line after "› ".
*/
export declare function execLines(
text: string,
options?: { registry?: Registry; signal?: AbortSignal },
): Promise<{ code: number; text: string; completed: number; total: number }>;
export declare function connectWebMCP(
registry?: Registry,
modelContext?: ModelContextLike | null,
): () => void;
```
## `@d34dman/sutram/events`
Declared events, and the events list and events recent commands.
```ts
export interface SutramEvent> {
id: number;
name: string;
payload: P;
source: string;
heardBy: string[];
}
export interface Events {
define(name: string, def: { description?: string; payload?: Schema }): void;
emit
>(
name: string,
payload?: P,
options?: { source?: string },
): SutramEvent
;
on(
name: string,
fn: (event: SutramEvent) => void,
options?: { as?: string; hidden?: boolean },
): () => void;
/**
* The first event named `name` ("*" for any) with an id above `since`: one
* still kept, or else the next one emitted. `since` defaults to the newest
* id. Resolves to null after `timeout` ms (default 10000) or on abort.
*/
next(
name?: string,
options?: { since?: number; timeout?: number; signal?: AbortSignal },
): Promise | null>;
list(): Array<{ name: string; description?: string }>;
lastId(): number;
clear(upTo?: number): void;
}
export declare function createEvents(
registry: Registry,
options?: { bufferSize?: number },
): Events;
export declare function getEvents(registry?: Registry): Events;
```
## `@d34dman/sutram/browser`
globalThis.Sutram: the registry and its helpers in one object, with /client as Sutram.client. Also dist/sutram.js.
```ts
export type SutramGlobal = {
EXIT: typeof core.EXIT;
CommandError: typeof core.CommandError;
registry: core.Registry;
events: Events;
/** Resolves once the page's scripts have run and their commands are registered. Wait for it before the first call on every page, and after a run that started Sutram again. */
readonly ready: Promise;
/** For a single-page app: its new commands are registered after Sutram started again. */
initialized(): void;
formats: core.Formats;
defineFormat(name: string, def: core.FormatDefinition): () => void;
register: core.Registry["register"];
tools(): Tool[];
call(
name: string,
input?: Record,
options?: { signal?: AbortSignal },
): ReturnType;
/** Command lines as pasted into the console: several as one batch. Answers with the console's text. */
exec(
text: string,
options?: { signal?: AbortSignal },
): ReturnType;
/** The agent prompt for this page, with its commands: the site's own (defineAgentPrompt) or the default. page defaults to document.title and location.href. */
agentPrompt(task?: string, page?: { title?: string; url?: string }): string;
/** core.defineAgentPrompt for this page's registry. */
defineAgentPrompt(make: (task: string, page: core.AgentPromptPage) => string): () => void;
mountConsole: typeof mountConsole;
connectWebMCP(): () => void;
/** @d34dman/sutram/client, for pages that build a client of their own. */
client: typeof client;
/** Kits loaded as their own files: dist/nav.js adds nav, dist/page.js page, and so on. */
kits: {
nav?: typeof nav;
page?: typeof page;
user?: typeof user;
messages?: typeof messages;
lists?: typeof lists;
forms?: typeof forms;
};
};
declare global {
// eslint-disable-next-line no-var
var Sutram: SutramGlobal;
}
declare const Sutram: SutramGlobal;
export default Sutram;
```
## `@d34dman/sutram/internals`
Helpers Sutram uses itself. No semver promise: any release may change them.
```ts
// Declarations for @d34dman/sutram/internals: helpers Sutram uses itself.
// No semver promise.
export declare function deref(schema: Schema, root: Schema): Schema | undefined;
export declare function distance(a: string, b: string): number;
export declare function formatValue(
value: unknown,
prop?: Schema,
options?: FormatOptions,
): string;
export declare function formatTable(
rows: Record[],
itemSchema: Schema,
root?: Schema,
options?: FormatOptions & { columns?: string[]; maxWidth?: number },
): { head: string; rows: string[] };
export declare function synopsis(def: CommandDefinition): string;
export declare function longSynopsis(def: CommandDefinition): string | null;
export declare function annotationLabel(annotations?: Annotations):
| "read-only"
| "consequential"
| "navigates"
| null;
export declare function describeShape(
schema: Schema,
root?: Schema,
depth?: number,
): string;
export declare function sentence(s: string): string;
export declare function formatsUsed(
def: CommandDefinition,
formats?: Formats,
): Array<{ name: string } & FormatDefinition>;
```
---
Source: https://d34dman.github.io/sutram/docs/reference/command-line.md
# The command line
Sutram parses one command line at a time. It is not a shell: no pipes,
redirection, variables or globbing.
## Grammar
```
[positional …] [option …] [-- value …]
option: --name=value
--name value
--flag
--no-flag
```
- **The command name** is one to six lowercase words: `cart add`,
`form set`. The longest registered name wins.
- **Positional values** fill the keys listed in `positional` that were not
given as options, in order. The last one takes the rest of the line:
`nav search menu settings` searches for "menu settings". A word with no key
left is a usage error (`unexpected argument`), never dropped.
- **Options** are GNU long options: `--qty=2` or `--qty 2`. There are no
short options except `-h`.
- **Booleans**: `--all` sets true, `--no-all` sets false. `--all=false` works
too; `--all false` is a usage error, so the word is not taken for an argument.
- **`--`** ends options: everything after it is a value, even if it starts
with `--`.
- **Lists** are comma-separated: `--tags=draft,urgent`. A list whose items
contain commas, or a list of objects, is written as JSON:
`--tags='["a,b","c"]'`. **Objects** are always JSON: `--meta='{"a":1}'`.
## Quoting
- Double or single quotes group words: `--who="Grace Hopper"`,
`set title 'Pour-over, step by step'`.
- A backslash escapes the next character, except inside single quotes.
- A backslash at the end of a line joins the next line to it, as in a shell:
long commands can be split over several lines. In the console,
Enter after a trailing backslash starts a new line.
- An apostrophe opens a single quote, so `don't` in an unquoted value is an
unclosed quote. Put such text in double quotes: `nav search "don't panic"`.
- A value that starts with a quote is never an option.
- An unclosed quote is a usage error (exit 2). In the console, Enter
inside quotes starts a new line of the value instead.
## Heredocs
For long or multi-line text, such as the HTML of a rich-text field, end the
line with `<Don't escape anything here.
- "Quotes" and \backslashes\ stay as they are.
END
```
- Any word works as the closing word: `<Enter inside one starts a new line.
- [`commandLine()`](api.md) writes a multi-line value back as a heredoc, so
what a person is asked to confirm stays readable.
## Several lines
Pasted text and multi-line entries are split into lines and run as one
[batch](../concepts/batches.md). Line breaks inside quotes or a heredoc
belong to the value. Lines starting with `#` are comments.
## Options every command has
| Option | Does |
|---|---|
| `-h`, `--help` | Show the command's usage |
| `--json` | Print the data unchanged, as agents get it |
| `--json=a,b` | Print only those fields: of the record, else of each row in its lists |
| `--wide` | Show every column |
A command cannot declare inputs named `help`, `json` or `wide`.
## Words clients own
| Word | Does |
|---|---|
| `help`, `help ` | List commands |
| `man ` | Show a command's manual |
| `status`, `status ` | Run every ` status`, or only those |
| `clear` | Clear the console |
Command names cannot start with these words, or with `sutram`, which
Sutram's own WebMCP tools (`sutram_status`, `sutram_batch`) use.
## Exit codes
| Code | Name | Means |
|---|---|---|
| `0` | `EXIT.OK` | Done |
| `1` | `EXIT.FAILED` | Ran, but failed |
| `2` | `EXIT.USAGE` | The input did not fit: unknown option, wrong type, missing value, bad quoting |
| `127` | `EXIT.NOT_FOUND` | No such command, with "did you mean" when one is close |
| `130` | `EXIT.CANCELLED` | Cancelled, or not allowed to run without approval |
## Annotations
| Annotation | `help` shows | WebMCP hint |
|---|---|---|
| `readOnly: true` | `[read-only]` | `readOnlyHint` |
| `consequential: true` | `[consequential]` | `consequentialHint` |
| `navigates: true` | `[navigates]` | in the description |
| `reinitializes: true` | `[reinitializes]` | in the description |
| `untrustedContent: true` | | `untrustedContentHint` |
## Tool names
A WebMCP tool's name is the command name with spaces replaced by
underscores: `cart add` → `cart_add`, `form set` →
`form_set`. `commandLine(def, input)` turns a tool call back
into the command line a person would type, and that line parses to the same
input: objects and awkward lists as JSON, `null` left out.
---
Source: https://d34dman.github.io/sutram/docs/why.md
# Why commands
Without Sutram, an agent that wants to use a web app has two options.
- **Read the page.** Take screenshots or an accessibility snapshot, work out
what is on the page, find the right elements, click and type, and read the
page again to see whether it worked. It is slow, it costs tokens on every
step, and it breaks when the layout changes. A headless browser and the
user's own browser behave differently, so the experience is not the same
either.
- **Call an API built for agents.** Someone writes a tool, with a schema, for
every action. That is work for every form and every page, and it usually
means a new way into the server, with its own login.
Sutram is the third option: **a standard interface on the page itself.** Any
part of the app (a form, the navigation, an editor, a module) hooks in the
same way, by registering commands. Agents find them with `help`, run them one
at a time or in one batch, and correct what goes wrong:
```sh
form fields node-article-form
form set node-article-form title "Pour-over, step by step"
form set node-article-form field_tags "coffee, v60"
form set node-article-form status false
form submit node-article-form
```
Each command has a JSON Schema for its input, and agents with WebMCP get it
as a tool. So an agent can also send everything in one go first, and fix
single values after.
## In the user's session
Commands run in the browser, as the logged-in user. A person can log in, then
let an agent take over: it can do exactly what the person could already do,
and nothing more. There is no new API, no extra login and no service account.
Access checks stay on the server, where they are today.
## The same everywhere
The same commands answer the same way whether the agent drives a headless
browser, uses WebMCP in the person's browser, runs JavaScript in the page, or
types into the console. Nothing
depends on screen size, layout or how the page looks.
## Precise edits
`form set