Install

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.

For AI agents: the documentation index is at llms.txt, and this page is available as Markdown.

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.

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 24 or later. In your project’s directory:

npm

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

pnpm

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.

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:

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:

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

The script is short enough to read before you run it: 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:

$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 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 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 too; leave it out for a project without them.

mkdir handbook && cd handbook
ascribe init --templates

Getting started 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 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:

ascribe 0.4.4 on Linux
# 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"

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, and it adds none of them: no site address, no builds, and no dimensions.

Beside ascribe.toml, it writes ascribe.addresses, with no entries yet, so the project keeps the addresses it publishes from its first commit: once a page is published, moving it with git mv is 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 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; and last, the Astro guide, to publish the pages as a site.

ascribe 0.4.4 on Linux
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/

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 finds, by code, most first, and what to run next: --fix, then the most common code that’s left, and ascribe baseline write 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 goes through it, step by step:

ascribe 0.4.4 on Linux
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/

With --format json, it writes the document its schema describes. When templates.refused isn’t empty, written is empty.

The JSON for this example
ascribe 0.4.4 on Linux
{
  "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/"
}
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