Skip to content
titah

Reference

Development

Working on Titah itself: the commands, the repository layout, the conventions, and the tests that matter.

Commands

npm run typecheck            # tsc --noEmit
npm test                     # build, then 1,242 tests — no real LLM calls
npm run watch                # rebuild on save
npm run build                # tsc -p tsconfig.json
npm run schema               # regenerate config.schema.json from zod
npm run smoke                # one real LLM call (needs a live provider)
npm run release:check        # npm pack --dry-run
node src/cli.ts doctor       # headless commands run from source, no build

npm test builds dist/ first: the TUI tests render real Ink components, and Node cannot load .tsx directly.

Two rules that bite

!!! warning “The TUI only runs from dist/” Node runs .ts directly but not .tsx — JSX is not stripped. Headless commands (run, serve, doctor, …) run from source with node src/cli.ts; the TUI needs npm run build first.

!!! warning “config.schema.json is generated” It comes from src/core/schema.ts via zod. Do not edit it by hand — run npm run schema after changing the schema, or the two silently disagree.

Layout

src/
  core/         the agent loop, permissions, storage, config, rpc
  core/tool/    every built-in tool, one file each
  core/storage/ SQLite plus the blob directory
  core/delegate/ external-CLI adapters
  server/       HTTP + SSE, and the embedded web client
  tui/          Ink components
scripts/        schema generation, smoke test
test/           node:test suites
docs/           design specs and gap analyses
editor/nvim/    a Neovim plugin

The boundaries are deliberate: the package ships as one npm module, but core knows nothing about server, and server knows nothing about tui. That is what makes titah run able to call the core in process while titah goes over HTTP, with no second implementation.

Conventions

Code comments in this repository are written in Indonesian; the user-facing interface and all documentation are in English. That split is intentional and consistent — do not mix them.

What the tests cover

Thorough unit tests concentrate on the parts that fail silently:

  • edit — string matching, the non-unique case, and that a miss writes nothing
  • bash — timeout and cancel, including the process group
  • the permission engine — the full check order, including auto-deny with no client attached
  • delegation adapters against a stubbed external CLI

That last one matters most: tests that burn tokens never get run, so the adapters are tested against a stub that speaks the same JSON the real CLIs do.

Three to five end-to-end scenarios against a small fixture repo are run manually before a release rather than in CI. Jumping to a full eval before the core is stable means debugging the harness instead of the product.

The smoke test

node scripts/smoke.ts

One real LLM call against a live provider. It is deliberately not part of npm test — a suite that needs credentials and network is a suite people stop running.

Adding a tool

  1. Implement it in src/core/tool/, and register it in that directory’s index.ts — allTools() is the single list.
  2. Put it on a permission axis — reuse an existing one only if it protects the same thing. network exists because confidentiality is not covered by bash; mcp exists because unclassifiable third-party code is not covered by either.
  3. Decide what happens on failure. A tool that half-succeeds is worse than one that refuses — see edit and patch.
  4. Add tests for the silent-failure paths, not the happy one.
  5. Update README.md’s tool table and this documentation.

Adding an external agent adapter

Usually you do not need to: the registry is config-driven, and externalAgent.<name> with command, args and format covers most CLIs without touching code. See Delegation.

Write an adapter only when the CLI needs a different protocol rather than different arguments. The internal interface is shaped like ACP — prompt, sessionUpdate, requestPermission, cancel — precisely so a future ACP transport is one new adapter rather than a rebuild.

Releasing

npm run release:check     # npm pack --dry-run

prepublishOnly runs typecheck, schema, build and test in that order, so a release cannot ship with a schema that has drifted from the zod source.

Reporting a bug

Include the output of:

titah doctor --probe

It reports environment, config locations, the merged config (redacted), which credentials resolved, which external agents were detected, skills per namespace, and every model missing a contextWindow — which is most of what any bug report needs.

Issues go to the tracker on GitHub.