> For the complete documentation index, see [llms.txt](https://ascribed-dev.com/docs/llms.txt). This documentation describes Ascribe 0.4.4. Anything marked in main, not yet released isn't in that release yet.

# Install

> How-to · Install the ascribe command, in a project with npm, or on your path with Homebrew, the install script, PowerShell, or a release's archive; then start a project with ascribe init.

There are two ways to have `ascribe`: in a project, with npm, which pins its version for everyone who works on the project and for CI; or on your path, from a release, where there's no Node, such as a Python or Go repository whose hooks or CI run `ascribe`. The latest release is 0.4.4.

To install the editor, see [Editing](https://ascribed-dev.com/docs/editor.md).

## Platforms

Ascribe runs on macOS on Apple silicon, Linux (x64 and arm64, with glibc 2.28 or later (2.39 for 0.1.1 and earlier), which includes the build images of Netlify, Vercel, Cloudflare Pages, and AWS Amplify), and Windows (x64). Intel Macs aren't supported.

## In a project, with npm

You need [Node.js](https://nodejs.org) 24 or later. In your project's directory:

A reader sees one of the next 2 sections at a time, chosen by package manager.

### Package manager: npm

```sh
npm install --save-dev @ascribed/cli
npx ascribe --version
```

### Package manager: pnpm

```sh
pnpm add --save-dev @ascribed/cli
pnpm exec ascribe --version
```

`@ascribed/cli` installs the `ascribe` binary for your platform, and pins its version in `package.json`. Run it as `npx ascribe` (`pnpm exec ascribe` with pnpm), or from a script in `package.json`. If `ascribe` can't find the binary, your package manager left out optional dependencies; reinstall with them enabled.

> **Tip**
>
> To try what's on `main` before it's released, install `@ascribed/cli@next` and `@ascribed/astro@next` instead: a build published every night, with no promise of stability.

## On your path, without Node

Each of these installs exactly what a release published, and checks it against the release's `SHA256SUMS` first. Run it again to update.

### Homebrew

On macOS or Linux, with [Homebrew](https://brew.sh):

```sh
brew install ascribed-dev/tap/ascribe
```

### The install script

On macOS or Linux, the install script puts `ascribe` in `~/.local/bin`, never asks for `sudo`, and says when that folder isn't on your path. Add `-s -- --version <version>` for a release other than the latest, or `-s -- --to <folder>` for another folder:

```sh
curl -fsSL https://ascribed-dev.com/install.sh | sh
```

The script is short enough to read before you run it: [install.sh](https://ascribed-dev.com/install.sh).

### PowerShell

On Windows, in PowerShell, download the latest release's archive and its `SHA256SUMS`, check the one against the other, and put `ascribe.exe` on your path:

```powershell
$base = "https://github.com/ascribed-dev/ascribe/releases"; Invoke-WebRequest "$base/latest/download/SHA256SUMS" -OutFile SHA256SUMS; $v = (Select-String -Path SHA256SUMS -Pattern "github/ascribe-(.+)-win32-x64\.zip").Matches[0].Groups[1].Value; $zip = "ascribe-$v-win32-x64.zip"; Invoke-WebRequest "$base/download/v$v/$zip" -OutFile $zip
if (-not (Select-String -Path SHA256SUMS -SimpleMatch -Pattern "$((Get-FileHash $zip -Algorithm SHA256).Hash.ToLower())  github/$zip")) { throw "$zip doesn't match SHA256SUMS" }
Expand-Archive $zip "$env:LOCALAPPDATA\Programs" -Force; [Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";$env:LOCALAPPDATA\Programs\ascribe-$v-win32-x64", "User")
```

Open a new terminal, and `ascribe --version` prints the release.

### A release's archive

Each [release](https://github.com/ascribed-dev/ascribe/releases) has an archive for each platform, `ascribe-<version>-<platform>.tar.gz` (`.zip` for Windows), where the platform is `darwin-arm64`, `linux-x64`, `linux-arm64`, or `win32-x64`, and `SHA256SUMS`, which lists each archive's SHA-256 as `github/<archive>`.

1. Download your platform's archive and `SHA256SUMS`.
2. Check the archive's SHA-256 (`sha256sum` on Linux, `shasum -a 256` on macOS, `Get-FileHash` on Windows) against its line in `SHA256SUMS`.
3. Extract it. It holds one folder, `ascribe-<version>-<platform>`, with `ascribe` (`ascribe.exe` on Windows) in it.
4. Put that folder on your path, or move `ascribe` into a folder that is.

## Which `ascribe` runs

A machine can have more than one `ascribe`: the one on your path, the one `@ascribed/cli` pins in a project, and the one the editor's extension bundles. A bare `ascribe` runs your path's; `npx ascribe` runs the project's; the editor runs the project's when there is one, and its own otherwise, and puts that one first on the path of its terminals ([Agents](https://ascribed-dev.com/docs/editor/agents.md#agents-in-vs-code) says how). `ascribe --version` says which version you're running.

## Start a project

With `ascribe` installed, start a project in an empty folder. `--templates` takes the [page templates](https://ascribed-dev.com/docs/templates.md) too; leave it out for a project without them.

```sh
mkdir handbook && cd handbook
ascribe init --templates
```

[Getting started](https://ascribed-dev.com/docs/getting-started.md) goes on from there, to a first page and a built site.

### `ascribe init`

Starts a project: writes its first `ascribe.toml` and `ascribe.addresses`, and makes the folder its pages go in, `docs/`. With `--templates`, it takes the page templates too, as [`ascribe templates eject`](https://ascribed-dev.com/docs/templates/command.md#ascribe-templates-eject) with no name does. In a folder of pages already there, it declares no page types, checks the pages, and says what to run next.

- `[DIR]`: The folder to start the project in, from the current folder. It's made if it isn't there. By default, the current folder.
- `--templates`: Take the page templates too, as `ascribe templates eject` with no name does: six types, beside the type `page`.
- `--content-root <DIR>`: The folder the pages are in, from the project's folder, with `/` between its parts. By default, `docs`. When it holds Markdown already, `ascribe.toml` declares no page types, so no page needs `type:`, and `init` checks the pages and says what to run next. `--templates` declares types, so it's refused there.
- `--dry-run`: Say what it would change, and change nothing.
- `--force`: Change files even when git couldn't put them back as they are: ones with changes not committed, ones git doesn't track, or ones in no repository.
- `--format <FORMAT>`: How to show what was written.
  - `text` (the default): Short text, for people and agents to read.
  - `json`: One JSON document, for tools.

It writes in the current folder, or in `DIR`, a path from the current folder, which it makes if it isn't there. It asks nothing. Where there's an `ascribe.toml` in the folder or above it, it refuses, names the one it found, and writes nothing: `ascribe templates eject`, run there, adds the templates to that project. To start a second project in a repository that has one, name a folder outside it. It takes no `--config`, since the project is the one it starts.

The file it writes is small, and says what each part is for:

```toml
# The project's content model: where its pages are, and what each kind of
# page must say in its frontmatter. `ascribe check` and the editor read it.
# Every section is in the reference: https://ascribed-dev.com/go/content-model/
spec = "0.1"

[project]
# The folder the pages are in. A link that starts with `/` starts here.
content-root = "docs"

# What most projects add next:
#
# - A phrase, such as the product's name, written once here and on a page as
#   {product}: https://ascribed-dev.com/go/phrases/
# - A dimension, such as the reader's platform, for content that's for some
#   readers only: https://ascribed-dev.com/go/dimensions/
# - [consumer], the site the pages are published as, and its address:
#   https://ascribed-dev.com/go/consumer/

# The one kind of page, `page`, with a title and a description. Each page
# says what type it is in its frontmatter: `type: page`.
[types.page.frontmatter]
title = "string"
description = "string"
```

Tested with ascribe 0.4.4 on Linux.

It sets the content root, `docs`, though that's the default, since it's the first thing a project changes; `--content-root` names another, a folder inside the project's. It declares one type, `page`, with a title and a description; since it declares a type, each page names it in its frontmatter, with `type: page`. Its comments name what most projects add next, each with its section of the [`ascribe.toml` reference](https://ascribed-dev.com/docs/content-model.md), and it adds none of them: no site address, no builds, and no [dimensions](https://ascribed-dev.com/docs/glossary.md#dimension).

Beside `ascribe.toml`, it writes `ascribe.addresses`, with no entries yet, so the project [keeps the addresses it publishes](https://ascribed-dev.com/docs/publishing/redirects.md) from its first commit: once a page is published, moving it with `git mv` is [`address-lost`](https://ascribed-dev.com/docs/checks/diagnostics/project-files.md#asc255-address-lost) on the next check against the base, and its fix records the move. It does so in a folder that has pages already, too, and leaves an `ascribe.addresses` that's there as it is.

With `--templates`, the six types are added after `page`, and the templates, their guides, Ascribe's notes, and `templates.lock` are written into `templates/`, as `eject` writes them. When a file is where one would be written, it's named, as `eject` names it, and nothing is written.

`init` adds files, and changes one that's there only to add the templates to it, so it has little for git to refuse, but it's refused as every [command that writes](https://ascribed-dev.com/docs/command.md#commands-that-write) does: a file it would change that git couldn't put back is named, and nothing is written, unless you pass `--force`. With `--dry-run`, it says what it would write and make, and writes nothing.

It lists each file it wrote, `ascribe.toml` and then `ascribe.addresses` last, from the current folder; the content root, when it made it; with `--templates`, each type it added, as `eject` says it; then the next step, a first page in the content root and [`ascribe check`](https://ascribed-dev.com/docs/checks/check.md#ascribe-check); and last, the [Astro guide](https://ascribed-dev.com/docs/astro.md), to publish the pages as a site.

```text
wrote ascribe.toml
wrote ascribe.addresses, where a page's move is recorded, so the addresses it published keep working
made docs/, the folder the pages go in
Next, write a first page, docs/index.md, and run `ascribe check`
Then, to publish the pages as a site with Astro, read https://ascribed-dev.com/go/astro-guide/
```

Tested with ascribe 0.4.4 on Linux.

**In a folder of pages.** When the content root holds pages already, `ascribe.toml` declares no page types, so the pages are of the implicit type `page` and none of them needs `type:`. In place of the type, it declares `description` as a field every page can have. `--templates`, whose types each page would have to name, is refused. Then it checks the pages, and says how many it found, how many have no `title` and how `ascribe check --fix` gives them one, what [`ascribe check`](https://ascribed-dev.com/docs/checks/check.md#ascribe-check) finds, by code, most first, and what to run next: `--fix`, then the most common code that's left, and [`ascribe baseline write`](https://ascribed-dev.com/docs/checks/baseline.md) when there's advice to put off. It exits with 0 whatever the check finds, since the project is there. [Bring existing docs to Ascribe](https://ascribed-dev.com/docs/existing-docs.md) goes through it, step by step:

```text
wrote ascribe.toml
wrote ascribe.addresses, where a page's move is recorded, so the addresses it published keep working
found 4 pages in docs/; with no page types declared in ascribe.toml, no page needs `type:`
3 pages have no `title`; `ascribe check --fix` takes the opening heading as the title of 2 of them, and the rest need `title:` written in their frontmatter
`ascribe check` finds 4 errors, 0 warnings, 5 advice:
  3  ASC048 frontmatter-missing-field (error)
  3  ASC150 page-description-missing (advice)
  2  ASC151 heading-level-skipped (advice)
  1  ASC036 link-target-missing (error)
Next, run `ascribe check --fix`, which fixes 2 of them, then `ascribe check --code ASC048`
To put the advice off, so the check reports only what's new: `ascribe baseline write`
Then, to publish the pages as a site with Astro, read https://ascribed-dev.com/go/astro-guide/
```

Tested with ascribe 0.4.4 on Linux.

With `--format json`, it writes the document its [schema](https://ascribed-dev.com/docs/contracts/json-reports/model.md#ascribe-init) describes. When `templates.refused` isn't empty, `written` is empty.

**The JSON for this example**

```json
{
  "schema_version": 2,
  "ascribe_version": "{version}",
  "written": [
    "ascribe.toml",
    "ascribe.addresses"
  ],
  "dry_run": false,
  "content_root": "docs",
  "content_root_made": true,
  "pages": 0,
  "templates": null,
  "next": "https://ascribed-dev.com/go/astro-guide/"
}
```

Tested with ascribe 0.4.4 on Linux.

| Code | Meaning |
| --- | --- |
| `0` | The project is there: `ascribe.toml` and `ascribe.addresses` are written, the content root is there, and with `--templates`, every template and its type |
| `2` | It wrote nothing: there's an `ascribe.toml` in the folder or above it, the folder is a file, `--content-root` isn't a folder inside the project's, `--templates` was given in a folder of pages, a template was refused, it was given `--config`, or, without `--force`, a file git couldn't put back. Or a file couldn't be written; the files it wrote before that stay. |

## See also

- [Getting started](https://ascribed-dev.com/docs/getting-started.md): a first project, from `npm install` to a built page.
- [Bring existing docs to Ascribe](https://ascribed-dev.com/docs/existing-docs.md): a project around a folder of Markdown you have.
- [Command reference](https://ascribed-dev.com/docs/command.md): every command, its options, and its exit codes.
