Action Annotations, Quick Reference
A one-page digest of ACTIONSPEC.md. That document is the specification and always wins; this page is a reading aid and defines nothing.
The idea
A plain SQL or command file describes itself inside its own comments: lines of the form @name value declare how the file is presented, guarded and executed. To any other tool it stays an ordinary SQL or text file. Three commitments shape everything:
- No escaping. A value is the rest of its line, verbatim.
- No injection. Data travels as bound parameters or single argv elements, never concatenated into SQL or a command line.
- Nothing in silence. Anything unreadable is diagnosed with its location; a typo never quietly does nothing.
Two kinds of file
| Query action | Command action | |
|---|---|---|
| Extension | .sql (and unknown extensions) | .run |
| Comments | -- lines and /* */ blocks | # lines |
| Body | SQL statements, split on top-level ; | exactly ONE command, \ continues a line |
| First command | the action itself | the action itself |
| Later commands | row buttons, each MUST carry @button or @open | not allowed, diagnosed |
| Runs | against the connection, answers with a result set | directly (never a shell), answers with its output |
Syntax in five rules
- An annotation's
@namemust be the first printable text of its comment line;- @descriptionis prose, not an annotation. - Only the header is read: the comment run directly above a command. Comments anywhere else are prose (an annotation found below the last command is reported, never applied).
- The value is the rest of the line, trimmed. No quoting, no escaping, no multi-line values.
- Names and keyword values are case-insensitive.
- Unknown names, wrong scope and wrong kind are diagnosed, and the bad line degrades as locally as possible (a broken
@atkeeps the button, a@buttonwith no label is no button).
Two-parameter form (@confirm, @alert-on-*, and the declarations @file, @dir and @var): a value shaped exactly "Title", "text" names the dialog's title too (a prompt and a default, on a declaration). Anything not matching that whole shape is one parameter, verbatim.
The annotations
Generated from the registry (quickReference() in web/src/lib/actions.ts); a test holds the two to each other, so this table cannot say less than the registry. Each row is the annotation's syntax and one line about it; the specification's section has the rest.
Both kinds, action header
@description <text> | what this action answers; shows under its row and in its header (alias @desc) |
@toolbar [place] [icon] [label] | a button in the title bar or a side strip: a place, an SVG icon, a name |
@color <name> | a color for every mark of this action: its row, its button, its tab |
@face result|source | what a click on the row opens: the file (source, default) or the answer (result) |
@connection <id or name> | run on this connection (its ID, or its name), from every door; nobody picks another |
@for <object type> [, <object type> ...] | attach the action to every object of these types; the object fills {schema}, {name}, {object}, |
Both kinds, either header (the declarations and the gate)
@file <name> ["<prompt>", "<default>"] | ask for a file first; the picked path becomes {<name>} |
@dir <name> ["<prompt>", "<default>"] | ask for a directory first; the picked path becomes {<name>} |
@var <name>=<value> or @var <name> ["<prompt>", "<default>"] | name a value once: = declares it, without = it is asked for in a form |
@confirm <text> or "<title>", "<text>" | ask this first, before the action or the button runs |
@pick <name> <query> | list the values an asked @var may take: the query's rows are the choices |
Query actions, action header
@refresh <interval> | ask again on a timer, while its tab is the active one |
@hide <column> [, <column> ...] | keep a column out of the grid, still there for $column |
@extension <name-or-path>[:<tab>] [as <title>] [, ...] | apply a grid extension (.plumesql.js, by name or store path) to a query or the whole file |
@view <name-or-path>[:<tab>] [as <title>] [only|beside] | open the result on a result view (its tab first; only hides the grid, beside puts the view next to it) |
@inputs <key>=<value> [, <key>=<value> ...] | answer a view's inputs in the file (x=ts, series=a,b), so it draws with no dialog |
@ask [<key> [, <key> ...]] | always ask before the run or the view draws, the form prefilled; with keys, ask those alone |
@noask | never ask: run and draw from the @inputs values and the defaults; a parameter without a value refuses the run |
@format-keywords lower|upper|preserve | this file's own keyword case; unset follows settings |
@format-commas trailing|leading | this file's own comma style; unset follows settings |
@format-indent <spaces> | this file's own indent width; unset follows settings |
@format-width <columns> | this file's own line width; unset follows settings |
@readonly | this file's session runs read only, server enforced |
@confirm-writes | runs from this file confirm structure and data changes |
@parse off|markers|outline|both | this file's own dynamic parser mode; unset follows settings |
Query actions, either header
@inline <name> literal|identifier | expand this value into the statement, server-quoted, for statements that bind nothing |
Query actions, button header (every command after the first)
@button <label> | the command below ACTS on the row that was clicked (alias @row-action, @action) |
@open <label> | the command below ASKS; its answer replaces the grid |
@on <column> | put the button ON this column: its cells become the button |
@at start|end|before <column>|after <column> | where the button's own column sits in the grid |
Command actions, action header
@env <key>=<value> | an environment variable for the command; the only place {password} may go |
@open-on-success <path> | open this file when the command succeeds |
@open-on-failure <path> | open this file when the command fails |
@open-on-done <path> | open this file when the command finishes, if it exists |
@alert-on-success <text> or "<title>", "<text>" | show this in a dialog when the command succeeds |
@alert-on-failure <text> or "<title>", "<text>" | show this in a dialog when the command fails |
@alert-on-done <text> or "<title>", "<text>" | show this in a dialog when the command finishes |
Values from outside the file
$column (row references, button commands only): the clicked row's value of that column. Always a bound parameter, never text in the SQL. In @confirm text it fills in as a message.
{name} (placeholders): values the app holds before the run.
| Connection | {host} {port} {db} {user} {sslmode} {conn} {server} {password} |
|---|---|
| Clock | {year} {month} {day} {hour} {minute} {second} {date} {time} {timestamp} |
| Declared | whatever @file / @dir / @var opened, in this file alone |
Expansion by context: a bound parameter in SQL, ONE argv element in a command (nothing to quote, ever), plain text in messages and paths. An unknown {name} is diagnosed and left standing.
The secret: {password} is legal in a command action's @env value and nowhere else. Both parsers refuse it elsewhere, and the server refuses it again at the point of use; it never reaches the page, argv, SQL, a path or a message.
Rules that connect annotations
- A second command without
@button/@opendoes not run (diagnosed). - One header, one button: repeats and
@button+@opentogether are diagnosed. @onand@atexclude each other;@onwins. One button per column, one column per button.@ona hidden column is diagnosed at parse time;@ona missing column falls back to an own column.@refreshnever re-asks@confirm(the gesture already answered) and refreshes the step on screen, not the root.- In each hook family,
-on-donecannot stand with-on-successor-on-failure; the specific ones win. At most one member of each family fires per exit. - A command using connection placeholders needs a connection; one using none must not demand one.
Examples
sql
-- @description Long-running queries
-- @refresh 5s
-- @hide pid
select pid, now() - query_start as runtime, query
from pg_catalog.pg_stat_activity
where state <> 'idle' and pid <> pg_backend_pid();
-- @button Cancel
-- @confirm "Cancel it?", "Cancel the query on backend $pid?"
select pg_catalog.pg_cancel_backend($pid);# @description Back up this database, custom format
# @env PGPASSWORD={password}
# @alert-on-success Wrote {db}-{timestamp}.dump
pg_dump --host={host} --port={port} --username={user} --no-password \
--format=custom --dbname={db} --file={db}-{timestamp}.dump