Tutorial: your first codebase
You'll write a few functions, save them to a codebase, change one, rename, undo, and add a test and a doc. The output shown is what tcm prints.
1. Make a codebase
With tcm installed, in an empty directory:
mkdir tutorial && cd tutorial
tcm init
This makes a codebase and a scratch.ts to write in.
2. Write something
scratch.ts is ordinary TypeScript. Below its opening comment it has:
/** Square a number. */
export const square = (x: number): number => x * x;
square(12);
$ tcm check
scratch.ts
+ new square : (x: number) => number
7 │ square(12)
⧩ 144
run `tcm add` to save 1 definition
square is new: the codebase doesn't have it yet. square(12) is a watch expression: tcm ran it and
showed its value. Watch expressions are never stored.
To check on every save instead, leave tcm (no arguments) running in a terminal. The VS Code
extension shows the same results as you type.
3. Add it
$ tcm add + new square : (x: number) => number
square is in the codebase now. Delete it from scratch.ts and it's still there: files are where you write, the
codebase is where code lives.
4. Use a namespace
Replace scratch.ts with:
// tcm: namespace geo
/** A point on a plane. */
export type Point = { x: number; y: number };
/** The distance between two points. */
export const distance = (a: Point, b: Point): number => Math.hypot(a.x - b.x, a.y - b.y);
distance({ x: 0, y: 0 }, { x: 3, y: 4 });
$ tcm add + new geo.Point type + new geo.distance : (a: Point, b: Point) => number $ tcm ls geo.Point #no6urvx6 type geo.distance #vra42ccj : (a: Point, b: Point) => number square #kor774ja : (x: number) => number
The first line put the file's definitions in the geo namespace. The #… after each name is its hash, short
for the hash of its code. A name points at a hash; the hash is the definition.
5. Use what you saved
Import from #codebase (the root namespace) or #codebase/<ns>:
// tcm: namespace geo
import { distance, type Point } from "#codebase/geo";
/** How far you travel visiting the points in order. */
export const pathLength = (ps: Point[]): number => ps.slice(1).reduce((n, p, i) => n + distance(ps[i]!, p), 0);
pathLength([{ x: 0, y: 0 }, { x: 3, y: 4 }, { x: 3, y: 0 }]);
$ tcm add + new geo.pathLength : (ps: Point[]) => number
6. Change something
Round distances to two decimal places. The scratch file only needs what you're changing:
// tcm: namespace geo
import type { Point } from "#codebase/geo";
/** The distance between two points, to two decimal places. */
export const distance = (a: Point, b: Point): number => Math.round(Math.hypot(a.x - b.x, a.y - b.y) * 100) / 100;
$ tcm update ~ update geo.distance : (a: Point, b: Point) => number (was #vra42ccj) ~ updated geo.pathLength (dependent)
pathLength uses distance, so tcm checked it against the new version and updated it too. Had it stopped
typechecking, it would have kept its old version and gone on your todo list (tcm todo) with the error. The
codebase is never left half-updated.
7. See what changed
$ tcm diff
changes from `update geo.distance`
edited
~ geo.distance #vra42ccj → #vwvxl53m
/**
- * The distance between two points.
+ * The distance between two points, to two decimal places.
*/
- export const distance = (a: Point, b: Point): number => Math.hypot(a.x - b.x, a.y - b.y)
+ export const distance = (a: Point, b: Point): number => Math.round(Math.hypot(a.x - b.x, a.y - b.y) * 100) / 100
updated because a dependency changed (code unchanged)
~ geo.pathLength #kuir3xdg → #3ah7ke6j via geo.distance
8. Rename something
$ tcm mv geo.distance geo.dist
moved 1 name
note: geo.dist is still named `distance` at runtime (`.name` is part of its hash); changing that means editing it
$ tcm view geo.pathLength
// geo.pathLength #3ah7ke6j : (ps: Point[]) => number
// runs anywhere
import { dist } from "#codebase/geo";
import type { Point } from "#codebase/geo";
/**
* How far you travel visiting the points in order.
*/
export const pathLength = (ps: Point[]): number => ps.slice(1).reduce((n, p, i) => n + dist(ps[i]!, p), 0)
pathLength shows the new name, and its hash (#3ah7ke6j) didn't change: a rename moves a label, with nothing to
find and replace. The note is because a function's .name is visible at runtime, so tcm mv leaves it alone.
"Runs anywhere" means this code needs neither a server nor a browser.
9. Undo
$ tcm history 2
#upoceguk 0 2026-10-28 06:21:30 mv geo.distance geo.dist
geo.dist + #vwvxl53m
geo.distance - #vwvxl53m
#eizpcqxs 1 2026-10-28 06:21:30 update geo.distance
geo.distance #vra42ccj → #vwvxl53m
and 1 updated because a dependency changed (`tcm history --all` lists them)
Each command is one history entry. Go back one entry, to before the rename:
$ tcm reset 1 names as of #eizpcqxs (1 added, 1 removed)
distance is called distance again. The reset is a new entry and code is never deleted, so you can always go
back.
10. Test it
A test is a definition whose name starts with test. It passes if it returns without throwing:
// tcm: namespace geo
import assert from "node:assert/strict";
import { pathLength } from "#codebase/geo";
export const testPathLength = () => {
assert.equal(pathLength([{ x: 0, y: 0 }, { x: 3, y: 4 }]), 5);
assert.equal(pathLength([]), 0);
};
$ tcm add + new geo.testPathLength : () => void $ tcm test ✓ geo.testPathLength 2ms 1 passed, 0 failed $ tcm test ✓ geo.testPathLength (cached) 1 passed, 0 failed (1 cached)
The second run ran nothing. A test's hash covers everything it calls, so its result is cached until that code
changes. Change distance again and the test runs again.
11. Document it
Doc comments can link to definitions and hold examples, which tcm runs:
// tcm: namespace geo
import { distance, type Point } from "#codebase/geo";
/**
* How far you travel visiting the points in order: the sum of the {@link distance}s between neighbours.
*
* ```ts
* pathLength([{ x: 0, y: 0 }, { x: 3, y: 4 }, { x: 3, y: 0 }])
* ```
*/
export const pathLength = (ps: Point[]): number => ps.slice(1).reduce((n, p, i) => n + distance(ps[i]!, p), 0);
$ tcm update
~ doc geo.pathLength : (ps: Point[]) => number (doc changed)
$ tcm docs geo.pathLength
How far you travel visiting the points in order: the sum of the `distance`s between neighbours.
```ts
pathLength([{ x: 0, y: 0 }, { x: 3, y: 4 }, { x: 3, y: 0 }])
```
```text
⧩ 9
```
The code matches what's stored, so only the doc changed. Links and example names are stored by hash, like code:
rename distance and the doc shows the new name; update it and the example runs the new version. If an update
breaks an example, tcm update says so.
12. Find it again
Search by name, or by type:
$ tcm find ':(ps: Point[]) => number' geo.pathLength #3ah7ke6j (ps: Point[]) => number
Next
That's the daily loop: write in a scratch file, check, add, update, mv, history and reset, test,
docs.
- Your first web app: server and browser code in one codebase.
- Working in scratch files: the loop in more detail, including editing existing code and fixing what an update broke.