Skip to content
Gordon Heydon edited this page Sep 18, 2026 · 4 revisions

TCL and Verbs

mvx is the classic shell: a prompt, a builtin table, VOC dispatch, and nothing else. Verbs are compiled BASIC programs.

mvx -a /path/to/account          # log on to an account
mvx -a acct -c "LIST PARTS"      # one command (ssh/cron style)

Interactive sessions have arrow-key history (persisted in ~/.mvx_history) and the prompt names the account.

Resolution order for a typed command: builtins → the account VOC → linked packages (in order) → the system account. Local entries override everything; the system account is the source of truth for standard verbs.

Builtins

OFF / QUIT / BYE end the session
LOGTO dir switch accounts
! command raw Unix (unrestricted tier)
SH interactive shell (unrestricted tier)

Standard verbs

verb
CREATE-FILE name {DIR|USING driver {conn}} create a file — local, directory, or on another backend
DELETE-FILE name delete a file
CLEAR-FILE name delete every record
LISTF the account's files
COUNT {DICT} file {WITH item op value} record count; pushes count(*) (optionally filtered) into a SQL backend, or uses an active select list
SUM {DICT} file field {WITH item op value} total a numeric field; pushes sum(col) into a SQL backend when the field is a mapped NUMERIC column, else scans and OCONVs
LIST {DESCRIBE} {DICT} file {items} {WITH item op value {AND …}} {BY item} query; multiple WITH/AND conditions are ANDed (pushed to one SQL WHERE); DESCRIBE prints the plan instead of running it
SORT {DESCRIBE} {DICT} file {items} {WITH item op value} {BY item} {FIRST n} like LIST, but sorted by id (or the BY key); FIRST n keeps the top n, pushed to ORDER BY … LIMIT on a SQL backend when the BY field is a mapped column
SELECT {DESCRIBE} {DICT} file {WITH item op value} form a select list for the next command
SSELECT {DESCRIBE} {DICT} file {WITH item op value} {BY item} like SELECT, but the list is sorted
MAP file {items...|ALL} {DATA} the relational schema the named items imply (SQL mapping preview)
CREATE-MAP file field… declare the mapping (%MAP%) + build it; writes are then mirrored live
BUILD-MAP file field… {PROGRESS} materialise the mapping in the backend and backfill (SQL columns); PROGRESS shows a live indicator
LIST-MAPS the account's mapped files: mode, state (current/stale vs the dictionary), and fields
MAP-MODE file {native|mirror} show or set the write policy (mirror: NULL on mismatch; native: reject the write)
DELETE-MAP file drop a mapping — tear down its columns/child tables and stop mirroring
CT {DICT} file id show a record, numbered attributes
COPY file id TO {file2} id2 copy a record
DELETE file id {id...} delete records
ED file id the built-in line editor (any tier)
VI file id edit a record in an external editor (unrestricted)
BASIC file item {NODEBUG} {STRIP} compile to an object (developer)
CATALOG file item {NODEBUG} {STRIP} compile, link and publish (developer)
PORT-SOURCE file item {TO file2} {item2} rewrite C-style comments as classic
CREATE-INDEX file item / DELETE-INDEX / LIST-INDEXES secondary indexes
LINK-PKG path / UNLINK-PKG path / LIST-PKGS packages
WHO TIME DATE session information
EXPORT {DICT} file {dir} copy records (or a dictionary) to a directory file
BUILD provision an account from its git-tracked config (post-clone)
IMPORT file {dir} mirror a directory file back (full sync)
GIT add|commit|branch|checkout|merge|cherry-pick|... git for records: full porcelain incl. branching
SET-CREDENTIAL driver target key field=value ... store a backend secret in .mvx-private (git-ignored)
LIST-CREDENTIALS list stored credentials, values masked
SET-CONNECTION name field=value ... define a named connection profile (@name in BINDINGS)
LIST-CONNECTIONS list connection profiles, secret fields masked

Select lists between commands

SELECT leaves its list for the next command:

> SELECT PARTS WITH COLOR = blue
2 record(s) selected
> LIST PARTS NAME PRICE          only the selected records

The list is consumed exactly once. Inside a program, EXECUTE "SELECT ..." followed by READNEXT uses the same mechanism.

DESCRIBE — how a query would run

Add DESCRIBE (or EXPLAIN) to LIST, SORT, SELECT, or SSELECT and the verb prints the plan the backend would use without running the query. The word can go right after the verb (LIST DESCRIBE …) or at the end of the sentence — either way it does the same thing. On a SQL backend the plan is the SQL text the push-down would issue, so you can see exactly what work moves server-side:

> LIST DESCRIBE CUST STATE WITH STATE = "VIC" AND PRICE > "500"
postgres: SELECT id FROM "acct"."CUST" WHERE "STATE" = 'VIC'
  AND NULLIF("acct".mvx_attr(rec,3),'')::numeric > '500'::numeric

> SORT ORDERS BY TOTAL FIRST 10 EXPLAIN
postgres: SELECT id FROM "acct"."ORDERS" ORDER BY "TOTAL" LIMIT 10

The plan follows the same push-down rules the query itself would: a mapped identity column is compared as a column, an unmapped or converted field through the record blob (mvx_attr), and a numeric range as a numeric comparison. When a condition can't be pushed (an @ID filter, a TRANS() join to another backend, a text range) the plan says so and notes that the verb applies it while scanning:

> LIST CUST WITH @ID = "C1000" DESCRIBE
postgres: SELECT id FROM "acct"."CUST"; 1 condition(s) applied in the verb

The description is backend-specific: a store with no server-side query planner (the local LMDB and directory backends) reports that the driver returns the id list — an index lookup when one exists, otherwise a sequential scan — and the verb applies the conditions and ordering. A future document backend would render its own native query the same way.

ED

The classic line editor: ED FILE ID.

Enter next line
n go to line n
L {n} list n lines
T / B top / bottom
I insert after current line — . alone ends input
DE {n} delete n lines
R/old/new replace in the current line
FI file (save) and exit
EX exit without saving

The privilege gate

Every spawn — !, SH, EXECUTE, and the compiler — passes through one gate in the runtime, controlled by $MVXPRIV:

tier may
restricted (default) run verbs, EXECUTE sentences
developer + compile (BASIC, CATALOG, COMPILE())
unrestricted + raw Unix (!, SH)

The check lives in the runtime because anyone who can compile could call the primitives directly — a shell-only check would be decorative.

Clone this wiki locally