34. CLI reference
tcm help prints the same list in short form; tcm <command> --help (or tcm help <command>) shows a
command's arguments and options, with their defaults. A mistyped command gets a suggestion, and an argument
or option a command doesn't take is an error naming it, followed by what the command does take (its usage,
arguments and options, as --help shows them), with the option meant for a misspelt one (unexpected '--olds' (did you mean --old?)).
- Arguments for
tcm runthat start with-go after--:tcm run lib.main -- --verbose. - Missing arguments are asked for at a terminal:
tcm view,run,edit,docs,deps,mv,rm,devand others offer the codebase's names to search,tcm switchandmergeoffer branches,tcm resolvethe conflicts and their definitions,tcm resetthe recent commands to undo, andtcm upgradethe packages.tcm rmof a name others use asks instead of refusing. Elsewhere (scripts, CI, editors), a missing argument is a usage error. - Package installs show as a line each, replaced by what was installed and how long it took on a terminal.
tcm -C <dir> <command>runs as if tcm were started indir, asgit -Cdoes: on the codebase there, not oneTCM_CODEBASEor a session'sTCM_BRANCHnames (they're the caller's). Withtcm run --bin tcm, it's how a change to tcm is tried on a codebase made for it:tcm run --bin tcm -- -C /tmp/repro status(an agent:tcm_cliwithliveandcwd).tcm --build <build> <command>runs the command with another tcm: a buildpnpm verifykept (known-good,previous, or an idtcm buildslists) or atcm.js.-C <dir>after it runs it indir, on the codebase there, whichever build it is. It's how to show a bug is in an older tcm (developing); an agent:tcm_cliwithbuild.--branch <b>reads another branch: the commands that only read (view,search,find,ls,deps,dependents,docs,todo,status,history,diff,at) take it, and read that branch as it is: its code, how it reads (its comment and doc changes), its history and todo (statuslists its session's unsaved scratch files by name). Nothing changes it, or any branch. In a session, it's how to look at another session's work without moving: the session's own branch and#codebasefiles stay as they are. A command that changes things isn't given it, and says so, rather than acting on that branch asTCM_BRANCHwould. (tcm mcp --branchis another thing: the branch a session works on.)-m <message>says what a history entry is for (--message): the commands that make one (add,update,patch,mv,alias,rm,resolve,import,upgrade,fmt,merge,revert,reset) take it, andtcm historycalls the entry by it (messages).- A hash is
#and its first letters (#abc), as tcm shows it; one given with another#before it (##abc) is the same hash. - Shell completion:
source <(tcm complete zsh)(orbash,fish,powershell); see install.
34.1 Codebase
tcm init- set up a codebase here:
.tcm/codebase.sqlite,package.json#codebaseimports,tsconfig.json,scratch.ts tcm/tcm watch [--into ns] [--fresh]- watch
*.tsscratch files (and files kept as written that are there to save: a page saying where it goes, or, with--into, one the namespace holds, astcm editwrites aDockerfile): typecheck, hash, report, evaluate watch expressions and tests. Their results are cached by hash;--freshre-runs them. Names a file uses and doesn't import are said (not imported: …), not written: the file is open in an editor tcm lsp [--stdio]- language server for editors, over stdio (editors)
tcm mcp [--branch b | --direct]- tools for AI agents to work in the codebase: a Model Context Protocol server, over stdio (agents); a session on a branch of its own (a new one, or the one the last session from the same directory left holding its work;
--branch b: that one, if no other server is on it), landed bytcm_mergewhen you say so, and moved onto another withtcm_cli ["switch", "<branch>"];--direct, no session: on the codebase's own branch (--sessionis accepted, and the default) tcm complete <shell>- a completion script for zsh, bash, fish or powershell, completing names from the codebase too
tcm check [files...] [--fresh]- one-shot version of watch. Reports each definition as
new/identical/update/alias/resolves(saved at a conflicted name, it resolves the conflict) (more than a few identical ones as one line). Names the code uses and doesn't import are imported first, written into the file, as saving would (the lines added are said); an import nothing uses is left, and said (saving takes it out). Code that doesn't parse is said by its first syntax error alone, with the lines around it: no type errors that follow from reading it wrong, and nothing imported tcm add [files...] [--into ns]- save new definitions from scratch files (default: the codebase's
scratch.ts, wherever tcm runs from); names the code uses and doesn't import are imported first, and imports nothing in the file uses are taken out, astcm patchdoes (the lines added, and the names taken out, are said; scratch files) tcm update [files...] [--into ns] [--clear]- save, re-point changed names, and re-hash every dependent.
--cleardeletes each file once it's saved (what it held is in the codebase); a change to comments only (or to the name something from outside the codebase is imported as), the code the same, is saved too (~ text). Imports what the code uses and doesn't import, and takes out imports nothing uses, astcm adddoes. What the file imports that uses what it changes is re-derived against it and saved with it ((dependent)), so a class and what's typed with it change in one save (scratch files). Code that doesn't parse is said aschecksays it. Dependents that stop typechecking keep their old hash and go totodo. Also checks stored table rows against changed row types (data) (many dependents are listed by namespace;tcm difflists them all) tcm export <namespace> <dir>- write a namespace's files (pages, stylesheets, images, a
Dockerfile, a.gitignorewith its dot), or one file, to a directory, links and names as they are now tcm import <dir> [--into ns] [--update] [--exclude a,b] [--tests] [--mirror] [--verbose]- add an existing project, one namespace per file;
--mirrorkeeps its files in step with the codebase, and makes the releases recorded with them again (importing) tcm mirror [--check] [--unlink]- the directory changes are written back to;
--checkre-analyzes its files, which must hash to what the codebase holds (mirrors) tcm edit <name>... [--file f] [--force]- write definitions to a scratch file for editing (
scratch.ts, orscratch.tsxfor JSX), with references following any updates; added to the file if it holds unsaved work (--forcereplaces it); never over the codebase's ownpackage.jsonortsconfig.json. A conflicted name is written to a file of its own, named after it (lib.k.ts), as its two sides merged from the merge base's, with conflict markers where both changed the same lines, saying what each side changed and whether the merge typechecks; conflicted names given together (one using another) share one file. Saving it, once the markers are out, resolves the conflict, namedresolve <name> (merged)in history (merging) tcm patch <name> --old <text> --new <text> [--all] [--imports <json>]- change a definition by find-and-replace on its code as
tcm viewshows it, then save it asupdatedoes. Each old text must be there once (or--all), as written or spaced and wrapped otherwise (the code is formatted); nothing is saved if one isn't, or if the result doesn't typecheck.--oldand--neware taken as written: a\nin them (as a shell's'…'leaves it) is a backslash and an n, as code has it in"a\nb", not a line break. Write a line break as one (inside the quotes, or$'…\n…'in a shell), or give--editsas JSON, where\nis one; a\nin--newthat isn't in--oldis noted, and an--oldwith one that isn't found says this. Inside a template literal, the text is the source's, escapes and all (\na backslash and an n,\`,\${): an old text found only with its escapes taken the other way (a line break for\n, a bare backtick for\`,${for\${, as JSON makes easy to write) is matched so when that finds it once, the new text written the same way, and a note says so; a new text that would end or split a template literal it's inside (a bare backtick, or${without its}) changes nothing, and says which and how to escape it. Code that doesn't parse is said aschecksays it.--edits '[{"old": …, "new": …}]'makes several edits at once;--patches '[{"name": …, "edits": […]}, …]'changes several definitions, and pages (files kept as written), saved together as one update and one history entry (a signature and its callers, or code and the page saying what it does); each edit is matched against its definition astcm viewshows it, however they're written together to be checked (two functions declared alike, which one file can't hold, go in a file each, saved one after the other). An old text that isn't there is reported with the most similar line. Imports follow the code (a name the new code uses is imported from the codebase, a package the codebase's code imports it from, tcm's own modules, or the one Node builtin that exports it; one it stops using goes); one that can't be found that way (an alias:isImportDeclaration as ts_isImportDeclaration) is given with--imports '[{"name": "isImportDeclaration", "from": "typescript", "as": "ts_isImportDeclaration"}]'(namedefaultor*withas;"type": truefor a type only), or asimportsin a patch of--patches; it goes too if the code doesn't use it. The change is shown as lines removed and added; problems the check finds, and the line most like an old text that isn't there, name the definition and its line astcm viewnumbers it, the edits made (lib.f:8:36; imports added aren't counted) tcm at <file>[:<line>]- which definition is at a line of a file (a scratch file, a definition's document in its branch's views,
.tcm/srcor a session's.tcm/sessions/<branch>/src, a generated module in.tcm/cacheas stack traces name them, a mirrored file), or every definition in it with its lines. A view reads as its branch has it now; another branch's is that branch's to say (--branch <it>). A module named by its component's hash (<hash>.tsin the cache,modules/<hash>.jsin a packed package), even one that isn't here, names its component's definitions tcm view <name|#hash>... [--lines a-b] [--grep text]- show definitions, rendered with current names, with their placement for app code;
--linesor--grep(the lines with the text, as written, and two either side; or matching a regular expression written/…/, flags after it:--grep '/get|set/i') shows only some of each, numbered.--linestakes a line (120), a range (120-180), from a line on (120-) or several (1-20,90-). A name that isn't there is said first, with the names meant most likely (or, for a namespace, what it holds), and the rest are shown; the exit code is then 1.edit,rm,patchand the rest say what was meant the same way tcm ls [namespace]- list names
tcm find <query>- the names with every word of
queryin the name, signature text or doc (finding code) tcm search <text> [--regex] [--case] [--word] [--under ns] [--limit n]- find text in the code and pages, as find in files does: each line with it, by definition, numbered as
tcm view --linescounts. The text is as written, or a regular expression written/…/, as--greptakes it (--regex, the old way, takes all the text as one);--underkeeps to a namespace or a definition. It stops at 500 definitions or 5000 matches, and says so (finding code). Text that starts with-goes after--:tcm search -- --lines tcm find ':<type>'- search by type:
':(xs: T[]) => T'; unknown names are type variables (search) tcm deps <name>/tcm dependents <name>- what a definition uses / what uses it now (the named definitions that refer to it)
tcm docs <name>- show a definition's documentation, rendered: links and embeds with current names, examples evaluated (docs)
tcm run <name> [args...]- run a definition. Functions are called with the args; the result is awaited and printed. What it starts finds the codebase's commands in
<COMMAND>_BIN(TCM_BIN), unless the caller set one:TCM_BIN=<tcm.js> tcm run <e2e test>runs an end-to-end test against that tcm (and it's said). One atcm runfurther out set (the codebase's build as it was then: one of its runners in.tcm/cache) is replaced.tcm run --bin <command> -- [args...]runs a command of the codebase's packages (a manifest'sbin) instead tcm mv <from> <to>- rename a definition or namespace (no code changes)
tcm alias <name> <new>- add another name for a definition
tcm rm <name>... [--force]- remove names, each in turn. If it's the definition's last name and named definitions use it, refuses unless
--force(they keep working either way); ones being removed in the same command don't count tcm todo [--all]- conflicts, plus dependents that need fixing after an update (the first 20, the rest counted: fixing those often fixes them;
--alllists every one); and code using a version nothing names (left behind by a change it couldn't follow, or a name removed with--force) tcm status- where things stand: the branch, which tcm this is (its build, the hash of its code), the codebase by namespace, scratch files holding unsaved work and what saving them would do,
todo, other branches with workmaindoesn't have, and what this one has (history entries, and the names they changed), session directories whose branch is gone that hold unsaved work, whether a mirror's files are in step tcm fmt [namespace]- reformat stored definitions with oxfmt; no hash changes (formatting)
tcm lint [namespace] [--fresh]- lint definitions with oxlint, cached by hash (linting)
tcm packages- the npm packages the codebase runs: range, locked version, what uses them (packages)
tcm upgrade <pkg> [--force]- newest versions within the ranges in use (and of what's built against it); no hashes change, everything using them is re-checked
tcm upgrade <pkg>@<range|latest> [namespace]- re-pin code to another range: re-hashed, dependents follow
tcm lib- the libraries this branch holds: where each is mounted, what it is, where it came from (libraries)
tcm lib add <remote|url|dir|tcm> <package>[@<version>]|#<hash> [--as <name>] [--at <name>]- fetch a release's namespace (
<package>: its newest), or a namespace by its whole hash, with what it uses, and mount it read-only at<root>.lib.<name>(or--at), recorded beside it; it's called after its package (@alice/geo-tools:geo_tools) unless--assays otherwise, which a hash needs.tcmis the libraries this tcm ships (tcm lib add tcm react, offline) tcm lib add @<handle>/<package>[@<version>] [--as <name>] [--at <name>]- the same for a release its author announced on the AT Protocol (default: the highest version they announced), fetched by hash from the servers its announcement names, and recorded with their DID (announced releases)
tcm lib upgrade <name|mount> [<package>@<version>|#<hash>] [--source s] [--check]- move a library to its package's newest release (or the one given; for an announced one, the highest its author's DID announced), from the first of its sources that has it (
--source: tried first), as one update: its API's changes first (--check: only those), then code using it follows; what no longer typechecks goes to todo. Refused while code uses what the new one doesn't have tcm lib sources <name|mount> [--add <s>] [--rm <s>]- where a library is fetched from, tried in order (each answer is checked by hash): add a mirror (a remote, URL or codebase directory), or stop fetching from one (not the last) (sources)
tcm lib attest <lib|@who/pkg@ver|pkg@ver|#hash> review|advisory|yank [-m text] [--severity s] [--fixed-in v] [--as handle]- say something about a release, signed in your AT Protocol repository: a review, an advisory (
--severitylow|moderate|high|critical), or, its author's alone, a yank (what people say) tcm lib search <query> [--registry <url>]- find libraries on a registry: what its libraries' newest versions hold that matches, by name, words in docs or
:a type, each with what matched and itstcm lib addline; the registry is kept for next time, and without one it's tcm's,https://tcm.codes/registry(finding one) tcm lib trust [--add <handle>] [--rm <handle>]- whose reviews and advisories
tcm lib,addandupgradeshow, besides each library's author's: kept in the codebase's history tcm lib rm <name|mount>- remove a library's names and its record entry; refused while code outside it uses what only it names
tcm pack <package>[@version] [--out dir]- an npm package of a package (a
manifestdefinition's namespace): a release of it, or the branch as it is (publishing) tcm pack [[subpath=]<namespace|name>...] --name <pkg>[@version] [--bin cmd=<name>,...] [--with pkg,...] [--files a,b] [--out dir]- an npm package of the definitions given (publishing)
tcm release [<package>][@version] [--draft] [--notes file] [--remote r] [--force] [--announce [--as <handle>] [--source <url>]…]- release a package at the version its API's changes call for (or a further one), with notes whose list of changes tcm writes; a mirrored codebase's are recorded with its files, for git (releases);
--announcewrites acodes.tcm.releaserecord to your AT Protocol repository, signed in in the browser once (an existing version: announced as it is), fetched from--source(kept) or origin (releases) tcm release check [package]- what the next release would be, and each semver finding behind it; and what would stop it (work left, a tcm library using what isn't its own), exiting 1
tcm release view <package>@<version>- a release's notes
tcm releases [package]- the packages and their releases
tcm changelog [package] [--out file]- a package's changelog, from its releases
tcm resolve <name> <#hash> | --ours | --theirs- pick one definition for a conflicted name, by hash or by its side of the merge (
--ours: this branch's,--theirs: the merged branch's, astcm todolabels them, followed through what re-derived it since); code that used the others (each side's, after a merge) is re-derived against it; history names itresolve <name> (ours)(or(theirs)). To combine them instead,tcm editthe name (merging) tcm test [names...] [--fresh] [--definitions | --suites] [--bin cmd=<path>,...]- run tests (all of them, or the ones named and the ones under the namespaces named), cached by hash, then the suites packages declare, cached by the code they ran;
--definitionsskips the suites,--suitesruns only them,--binruns them against another build of a command, like a package packed from the codebase, or an oldertcm.js(fortcm, a kept build's name too:--bin tcm=known-good) to see a new end-to-end test fail before a fix (testing). Only passes are reused: a test that failed runs again. Suites, and test definitions' Vitest projects, run as many at once as the machine's budget allows, shared by every tcm on it with its builds, checks and verifies:TCM_SLOTS(how many; by default from the machine's cores and memory;TCM_SUITE_SLOTS, its name before, still works),TCM_SLOTS_DIR(where the slots are) (testing) tcm migrate [--if-needed]- move a codebase made with an older hash format or table layout to this tcm's, history and all (hashing); the database as it was is kept beside it (
.tcm/codebase.v<n>.sqlite, or.tcm/codebase.layout-<layout>.sqlitewhen only its tables move), and the migrated one used only if it reads back as itself.--if-neededsays nothing when there's nothing to do tcm compact- compress the codebase's long texts (definitions' code and source, metadata, history's namespace nodes) where a tcm from before it kept them as written, and give back to the disk the space nothing uses (SQLite's
VACUUM); other tcms on the codebase wait meanwhile. Then removes what.tcm/cacheholds that no branch uses, untouched for an hour (a day for suites' packed apps): past versions' modules,tcm run's runners; each is made again if it's needed (files) tcm verify [--fresh]- re-render and re-hash every live definition; must be the identity. What this same tcm verified already, named as it is now, isn't checked again;
--freshchecks everything (hashing) tcm builds- the tcm builds kept (where
TCM_BUILDSsays: tcm's repository'sdist/):known-good,previousand older ones, each with the history entry it was made from, when, what changed in it, and itstcm.js(developing) tcm builds make <entry>- a build of the codebase's tcm as it was at a history entry, packed from the codebase then and kept with the others, to run with
--build
34.2 History and branches
See history, branches and merging.
tcm diff- what the last command changed: edits, dependents re-hashed by them, renames
tcm diff <n|#hash> | <branch> | <a> <b>- changes since a history entry, to another branch, or between two branches or entries
tcm diff <#def> <#def>/tcm diff <conflicted name>- two definitions' code, line by line, as
tcm viewshows it; a conflicted name's: the merge base's against each side's tcm history [n] [--all]- this branch's history, newest first: an entry per command, with its hash, how many back it is, and what it changed by name, the definitions only re-hashed because a dependency changed counted (
--alllists them); the latest 30 unlessn(or-n <n>,--limit <n>) says. An entry is named by its command and what it changed (update lib.f, lib.g (+3)), and by its message where it has one (its first line; the rest, and the command, under it) tcm describe [n|#hash] [-m <message>]- say what a history entry is for (default: the latest): its message, which no hash has, so nothing else changes;
-m ""takes it away, and without-mit's shown. A codebase the entry was pushed to keeps the message it had (messages) tcm reset <n|#hash>- bring back the names as of an entry (
tcm reset 1undoes the last command), as a new entry tcm branch [name] [--delete]/tcm switch <name>- list, create or delete branches (not
main, the one you're on, nor one a session's server is running on; its session's directory goes too, unless it holds unsaved work) / switch (not in a session:TCM_BRANCHkeeps a process on its own branch) tcm merge <branch> [--no-fast-forward] [--squash] [--abort]- merge into this branch: nothing if it has the other's history, a fast-forward if the other has its (or, with
--no-fast-forward, a merge entry all the same, so what came in is one entry), otherwise three-way from their common ancestor (--squash: one entry whose only parent is this branch's, so the other's entries stay its own and pushing sends only what this branch names; how sessions land): a definition both sides edited is merged as text where the edits are apart and it typechecks, and every definition is re-derived against what the merge names. A merge that leaves a conflict, or code that stops typechecking, waits apart, this branch untouched, with scratch files holding what's to settle; commands on the branch work on it, and once nothing's left it's made (--abortdrops it) (waits apart). Onmain, or with--squash, the branch lands:mainmerged into it first, there, then itstcm todoand tests, and only then the branch onmain(landing) tcm revert <n|#hash>- undo one history entry (a landing, say) and nothing after it, as a new entry: what it changed goes back, three-way, so changes made since stay; one made since where it changed things is a conflict (
tcm resolve)
34.3 Sync and remote execution
See sync.
tcm remote [add <name> <url|dir> [--token t] [--fingerprint sha256] | rm <name>]- manage remotes: a
tcm serveURL or another codebase directory tcm push [remote] [--as branch] [--force]- send this branch's history (what the remote lacks) and move the remote branch to it (a contributor's, under
@<handle>/). Refuses if the remote has history you don't, unless--force tcm pull [remote] [branch]- fetch the remote branch's history into
<remote>/<branch>; fast-forward this branch if it can, merge from the common ancestor otherwise (one that leaves something to settle waits apart, astcm merge's does; onmain, it isn't made) tcm run <name> [args] --on <remote|url|dir>- send what the remote lacks, run there, stream stdout/stderr back
tcm contribute [remote] [--to main] [--as name] [--title t] [--description d]- push this branch (a contributor's as
@<handle>/<branch>) and open a contribution of it on the remote (pushing again updates it) (contributions) tcm contribution [--remote origin] [--all]- the remote's contributions
tcm contribution view|comment|merge|close|reopen <id>- a contribution's changes, whether it merges, and comments; comment (
--about <name>); merge it on the remote by history (refused on conflicts); close or reopen it tcm serve [--port 7737] [--host 127.0.0.1] [--token t] [--no-run] [--public] [--tls [--tls-cert f --tls-key f]] [--insecure] [--site dir] [--moved /path] [--proxy /path=url[|url],…] [--forge /path=dir,…]- serve this codebase over HTTP(S), and a static site (
--site), apps beside it (--proxy) and other codebases' web UIs, read-only (--forge), to anyone, the site's old addresses redirected to where its pages moved (--moved);--publiclets anyone pull and read contributions without a token (sync) tcm serve's web UI- at
/forge/(and/without--site): the codebase's pages, search, history, and contributions reviewed in the browser (web UI) tcm user [--remote origin]- who you are on a remote: its maintainer (and its contributors), a contributor, or a reader with no token
tcm registry [--port 8080] [--host 127.0.0.1] [--dir tcm-registry] [--base /path]- run a registry (under a path of a site, with
--base): it finds releases announced on the AT Protocol, checks each against its author's signed record, mirrors its code by hash and shows it; visitors sign in with their AT Protocol account to star libraries and see what people they follow, and the libraries they use, are up to (registry) tcm user add|rm <handle> [--remote origin]- the maintainer's: add a contributor (printing their token, once; again, a new one), who pushes branches under
@<handle>/and proposes them; or remove one, stopping their token (contributors)
34.4 Apps, data and jobs
See browser apps, production, data and jobs.
tcm dev <app> [--port 5173] [--http <namespace>[=/prefix]]- serve a browser app from the codebase with hot reload;
<app>is a function run on load, or a React component rendered into the page tcm build <app> [--force] [--static <dir> [--base /path]]- immutable production build into
.tcm/apps/<hash>/(reused if it exists); with an app'spwa, its manifest, icons and service worker (PWAs).--staticwrites it out as a static site, for any static host, for the site's root or--base: each page the app links to rendered as it's built, server components included; an app that calls server functions is refused (static sites) tcm start <app> [--port 8080] [--host] [--tls] [--keep N] [--http <namespace>[=/prefix]] [--base /path]- serve in production, following the app's name;
--baseserves it under a path of a site (deploying) tcm apps [prune [--keep 3]]- list builds (
*= current); prune keeps the current build plus the newest N others tcm data [table [n]]- typed tables: row counts, or a table's newest rows
tcm data migrate <table>- upgrade every stored row to the current row type now, through
migrate tcm jobs [n] | retry <id> | cancel <id>- list background jobs, or act on one