
URL: /reference/cli

---
title: CLI
description: Every Tangly command (init, dev, build, check, migrate, eject, add, preview) with flags, defaults, and examples.
icon: "terminal"
---


The `tangly` binary is the primary surface for working on Tangly projects. Eight commands cover scaffolding, dev, build, validation, migration, ejection, and content scaffolding.

```bash
tangly --help
```

## Install

<PackageManager name="tangly" mode="dev" />

Or install globally:

```bash
bun add -g tangly
```

## Staying up to date

In an interactive terminal, `tangly` checks npm at most once every 12 hours and prints a one-line notice when a newer version is published:

```
⚠ Update available: tangly 0.1.5 → 0.1.6
  Upgrade: `npm i -g tangly@latest` (or run `npx tangly` for the local copy)
```

The check is fail-silent (offline or slow registry: no notice) and never runs in CI or piped output. Opt out with `TANGLY_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1`.

Every command also stamps the running version into its output, and `tangly --version` prints it directly, so you can always confirm which binary ran.

<Warning>
**Stale global install.** A global `tangly` (`bun add -g` / `npm i -g`) takes precedence on your `PATH` over a project-local one. If you upgrade the local copy (`npm list tangly` shows the new version) but the typed `tangly` command still behaves like an old version, the global is shadowing it. Run `tangly --version` to confirm, then `npm i -g tangly@latest`, or invoke the local copy with `npx tangly`. See [Same errors after upgrading](/troubleshooting/common-errors).
</Warning>

## Common flags

Every command accepts these:

| Flag | Default | Description |
|---|---|---|
| `--root <dir>` | `.` | Project root |
| `--config <path>` | `docs.json` | Path to `docs.json` relative to `--root` |

The other flags vary per command.

## init

Scaffold a new Tangly project. Two paths: pick a built-in template (interactive), or hand `--from <dir>` to ingest an existing folder of `.md`/`.mdx` files.

```bash
tangly init [dir] [--from <dir>] [--template <name>] [--name <name>]
```

| Arg / flag | Type | Default | Description |
|---|---|---|---|
| `dir` | positional | `.` | Target directory |
| `--from` | string | — | Path to existing markdown folder. Walks the tree (folders → groups), synthesizes `docs.json`. Idempotent: re-runs merge new files into existing nav. |
| `--template` | string | `starter` | Template name. Skip the interactive prompt. |
| `--name` | string | — | Project name. Skip the interactive prompt. |

### Examples

```bash
tangly init my-docs

tangly init . --from ../legacy-docs

tangly init --template starter --name "My Docs" my-docs
```

## dev

Start the dev server with HMR.

```bash
tangly dev [--port 4321] [--host] [--no-open] [--debug] [--tunnel]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--port` | string | `4321` | Port |
| `--host` | boolean | `false` | Bind `0.0.0.0` so other devices on the LAN can reach the dev server. Without this, only `localhost`/`127.0.0.1` works. |
| `--open` | boolean | `true` | Open browser on start. Use `--no-open` to suppress. |
| `--debug` | boolean | `false` | Verbose Vite/Astro logging |
| `--tunnel` | boolean | `false` | Expose via a `cloudflared` quick tunnel. Requires `cloudflared` on `PATH`. |

The dev server pre-validates your `docs.json` manifest before launching Astro. Schema errors surface immediately, not buried in a stack trace.

```bash
tangly dev --port 8080 --host --no-open
```

## build

Production build to `dist/`.

```bash
tangly build [--out ./dist] [--adapter <auto>] [--base /] [--site-url <url>] [--env <env>] [--analyze]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--out` | string | `./dist` | Output directory |
| `--adapter` | `vercel \| cloudflare \| node \| static` | (auto) | Auto-detected from project files: `vercel.json` → vercel, `wrangler.toml`/`wrangler.jsonc` → cloudflare, `Dockerfile` → node, else `static`. |
| `--base` | string | `/` | Subpath. Pass `--base /docs` to deploy under `/docs`. Rewrites every internal link, asset URL, sitemap entry, llms.txt URL, and Pagefind result. |
| `--site-url` | string | docs.json `siteUrl` | Absolute deploy URL. Overrides `siteUrl` for canonical, `og:image`, sitemap, and `robots.txt`. Also reads `TANGLY_SITE_URL`. |
| `--env` | `production \| preview` | (auto) | Deploy environment. `preview` emits `robots: noindex` and points canonical at the production domain. Also reads `TANGLY_ENV`. |
| `--analyze` | boolean | `false` | Write a build-size report to `dist/_tangly/`. |

### Site URL & environments

Canonical tags and `og:image` need an absolute URL, but the deploy host varies (production, staging, PR previews). Tangly resolves it per build, highest priority first:

1. `--site-url` / `TANGLY_SITE_URL`
2. Platform auto-detect — Vercel, Netlify, and Cloudflare Pages deploy URLs are read automatically
3. `siteUrl` in `docs.json`

`og:image` points at the **current deploy** (so the image resolves on a preview); `canonical` points at the **production** domain (so previews never compete in search). Non-production deploys (`--env preview`, or an auto-detected preview/branch context) also get `<meta name="robots" content="noindex, nofollow">`.

```bash
# Staging deploy: cards resolve at staging, canonical + sitemap stay on prod, noindex
tangly build --site-url https://staging.docs.example.com --env preview
```

On Vercel/Netlify this is zero-config — PR previews self-reference and go noindex automatically. Cloudflare Pages exposes no production signal, so set `--env preview` (or `TANGLY_ENV=preview`) on preview branches. In `tangly dev`, canonical/`og:image` use the live request origin so you can preview cards locally.

### Output

```
dist/
├── index.html              redirects to first nav page
├── introduction/index.html
├── guides/<slug>/index.html
├── images/                 copied from project root
├── logo/
├── og/<slug>.png           generated social cards
├── sitemap.xml
├── robots.txt
├── llms.txt
└── llms-full.txt
```

The build also generates a per-page `<slug>.md` file (markdown, no MDX) so AI agents can fetch a clean text version of any page.

### Examples

```bash
tangly build

tangly build --adapter cloudflare --base /docs

tangly build --analyze
```

## check

Validate config, links, frontmatter, and MDX.

```bash
tangly check [--strict] [--no-links] [--json] [--include-drafts]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--strict` | boolean | `false` | Warnings become errors (non-zero exit). Use this in CI. |
| `--no-links` | boolean | `false` | Skip link checking |
| `--json` | boolean | `false` | Machine-readable output |
| `--include-drafts` | boolean | `false` | Include `draft: true` pages in checks |

```bash
tangly check

tangly check --strict

tangly check --json | jq '.errors'
```

Exit code is `0` when there are no errors (or no errors+warnings under `--strict`), `1` otherwise.

### MDX validation

Every page is parsed with the same MDX *syntax* configuration the build uses (the remark plugins that affect what parses as a JSX expression), so `check` passing predicts `build` passing for this class of failure:

- **Syntax errors** (unclosed JSX tags, malformed expressions, bad `import`/`export`) are reported with `file:line:column`.
- **Unbound expression identifiers** are flagged before they become prerender `ReferenceError`s. The classic case is a literal placeholder in prose — `{search_term_string}` is valid MDX, compiles, and only explodes at render time:

```
✗ rules/schema/website-search.mdx:5:6 — ReferenceError at build: `search_term_string` is not defined
      hint: MDX treats {…} as a JSX expression — wrap literal placeholders in backticks
```

The fix is usually to wrap the placeholder in backticks: `` `{search_term_string}` ``. Identifiers bound by the file's `import`/`export` statements, expression-local declarations and arrow params, MDX-provided names (`props`, `frontmatter`, `components`), and JS globals are all recognized. Files under `/snippets` are never scanned — reusable snippets legitimately take bare `{props}`-style variables from the importing page.

## migrate

Migrate a project to Tangly. Two paths:

- Legacy: project has `mint.json` → full conversion via `convertMintToDocs`.
- Modern: project has `docs.json` → validate + update the `$schema` URL.

```bash
tangly migrate [--yes] [--keep-source]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--yes` | boolean | `false` | Skip confirmation prompts |
| `--keep-source` | boolean | `false` | Keep `mint.json` after legacy migration. Without this flag, `mint.json` is renamed to `mint.json.bak` (or `.bak.1`, `.bak.2`, …). |

### Examples

```bash
tangly migrate

tangly migrate --yes

tangly migrate --keep-source
```

If your `theme:` value isn't a Tangly theme (a Mintlify alias like `aspen` or `maple`), `migrate` rewrites it to `tang` — the schema maps every alias to `tang` at parse time anyway, so the rewrite only makes the file say what it does. The notice lists the other Tangly themes if you'd rather pick one.

When `docs.json` has no `siteUrl`, `migrate` prompts for your production URL (used for canonical links and social cards). Press Enter to skip; `--yes` skips the prompt entirely.

## eject

Materialize the synthesized Astro project into your repo. **Irreversible.**

```bash
tangly eject [--out ./.tangly] [--yes]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--out` | string | `./.tangly` | Eject destination |
| `--yes` | boolean | `false` | Skip confirmation |

After eject:

- `./.tangly/` contains a real Astro project (`astro.config.mjs`, `src/`, etc.)
- `package.json` adds raw Astro deps (`astro`, `@astrojs/mdx`, `tailwindcss`, …) and rewrites `dev`/`build`/`preview` scripts to call `astro` directly. The `tangly` package is **kept** as a dep. The materialized `astro.config` still imports `tangly/plugin` for the docs runtime, and theme components still import from `@tanglydocs/theme-ui`.

You manage `astro.config.mjs` and integrations from then on.

```bash
tangly eject --out ./astro-site --yes
```

## add

Scaffold a new page, snippet, or changelog entry.

```bash
tangly add <type> <path> [--template <name>] [--no-nav]
```

| Arg / flag | Type | Default | Description |
|---|---|---|---|
| `type` | positional | — | `page` \| `snippet` \| `changelog` |
| `path` | positional | — | Slug or filename. e.g. `guides/billing`, `shared/disclaimer`, `1.2.0` |
| `--template` | string | — | Template name from `./templates/` |
| `--no-nav` | boolean | `false` | Don't auto-insert into `docs.json` navigation |

### Page

Writes `<path>.mdx` and inserts the slug into the last `navigation.groups` (or `navigation.pages`) entry in `docs.json`.

```bash
tangly add page guides/billing
```

### Snippet

Writes `snippets/<path>.mdx`. Snippets aren't inserted into nav (they're not pages).

```bash
tangly add snippet shared/disclaimer
```

### Changelog

Writes `changelog/<path>.mdx` with an `<Update>` block ready to fill in. Date is today; version is parsed from the slug if it looks semver-shaped.

```bash
tangly add changelog 1.2.0
```

## preview

Serve the built `dist/` locally (Astro's static preview server).

```bash
tangly preview [--port 4321] [--out ./dist]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--port` | string | `4321` | Port |
| `--out` | string | `./dist` | Output directory to serve |

Use this to verify the production build before deploying.

```bash
tangly build && tangly preview
```

## Source

- [`packages/tangly/src/cli/commands/`](https://github.com/tanglydocs/tangly/tree/main/packages/tangly/src/cli/commands)
- Each command's source: [`init.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/init.ts), [`dev.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/dev.ts), [`build.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/build.ts), [`check.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/check.ts), [`migrate.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/migrate.ts), [`eject.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/eject.ts), [`add.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/add.ts), [`preview.ts`](https://github.com/tanglydocs/tangly/blob/main/packages/tangly/src/cli/commands/preview.ts)
