Quickstart

Create the first Flywheel local setup and inspect the product surfaces.

On Windows, choose the install environment by where your AI coding host runs: use WSL when you launch the host from WSL, and use native PowerShell when the host runs as a native Windows process. See Install for the full Windows and WSL commands.

Install Flywheel with the installer on macOS, Linux, or WSL:

curl -fsSL https://flywheel.paradigma.inc/install | sh

After setup, open the Flywheel Web UI to inspect the Canvas, then use your CLI or MCP host to create and inspect graph nodes.

First local repository

Create a repository in an existing project directory, then run repository commands from that directory or any child directory. Flywheel discovers the nearest parent containing .flywheel:

Stage and inspect pending state

#!/usr/bin/env bash
flywheel repo:init . --format=json
CREATED="$(flywheel repo:node:create --title "Documentation local example" --format=json)"
NODE_ID="$(printf '%s' "$CREATED" | jq -er '.node_id')"
flywheel repo:diff --staged --format=json
# Review complete net Before/After values before running commit.sh.
flywheel repo:node:get "$NODE_ID" --staged --format=json

The node is staged locally. flywheel repo:status identifies saved work; flywheel repo:diff --staged shows the complete net graph changes before commit. The staged projection read inspects the resulting pending graph before any commit. Unflagged projection reads remain committed-only. Add --staged to exactly these existing readers when you need pending state: repo:node:get, repo:artifact:get, repo:containment:get, repo:edge:get, repo:tag:list, repo:lineage:list, repo:node-artifact:list, repo:edge-artifact:list, repo:containment:list, and repo:edge:list.

A staged result retains the reader's normal fields and adds view: "staged", the exact staging_digest, and the real nullable committed base at_head. Before the first local commit, at_head is null, and creation or update provenance for a new or changed entity is null. A later commit assigns provenance-bearing relationship-row provenance and digests, while graph values and field or property digests retain the unchanged reviewed value. These reads are local, offline, and non-mutating: they require neither credentials nor network access, fetch no artifact bytes, and do not publish. If an unflagged read reports a pending-only entity as unavailable, inspect the same entity with --staged. Unflagged repo:diff remains a committed-history comparison, not the staged review. No local commit or hosted change exists yet.

You can edit and correct the pending node before a single commit. Save the node_id returned above as NODE_ID, then run:

printf '%s' 'Draft content' > draft.txt
printf '%s' 'Corrected content' > corrected.txt
flywheel repo:node:set "$NODE_ID" --field content --value-file draft.txt --format=json
flywheel repo:node:set "$NODE_ID" --field content --value-file corrected.txt --format=json

Existing local edit commands use the graph after earlier pending edits, so the second setter compares against the draft, not an older committed value. This also applies to tags, relationships, edges, artifacts, and node lifecycle edits. No intermediate commit or new edit flag is needed. Local editing and committing work offline without credentials.

To stage a small semantic edge, create the graph container and both endpoints in this repository, derive their IDs from the create responses, establish containment first, then add the edge:

GRAPH="$(flywheel repo:node:create --title "Documentation graph" --format=json)"
GRAPH_ID="$(printf '%s' "$GRAPH" | jq -er '.node_id')"
SOURCE="$(flywheel repo:node:create --title "Source concept" --format=json)"
SOURCE_ID="$(printf '%s' "$SOURCE" | jq -er '.node_id')"
TARGET="$(flywheel repo:node:create --title "Target concept" --format=json)"
TARGET_ID="$(printf '%s' "$TARGET" | jq -er '.node_id')"
flywheel repo:containment:assign "$GRAPH_ID" "$SOURCE_ID" --format=json
flywheel repo:containment:assign "$GRAPH_ID" "$TARGET_ID" --format=json
flywheel repo:edge:add "$GRAPH_ID" "$SOURCE_ID" "$TARGET_ID" --format=json

The graph container, source, and target must belong to the same repository; the distinct endpoints must be descendants of the graph container. The containment assignments above are therefore required before repo:edge:add.

Graph rules are checked before an edit replaces staging. For example, adding the edge before its required containment fails and preserves the earlier pending nodes and values. An already-invalid batch reports its first failing operation instead of accepting another edit. If another local action changes the head or batch during an edit, reissue that edit; the CLI does not retry or erase staging.

Review the complete pending graph

flywheel repo:diff --staged --format=json

Inspect the complete before and after values for each node, relationship, tag and holder, artifact, and artifact reference. The result identifies the captured batch with staging_digest and its committed base with base_head, which is null before the first commit. A null side means the entity is absent; nullable fields remain inside an existing entity. Repeated edits show only the net change, and add/remove cancellations or empty staging yield changes: []. Full strings, structured properties, and repository-format numbers are preserved.

This inspection is local: it makes no network requests, reads no artifact bytes, and changes neither staging nor history. Do not combine --staged with --base or --head. If an operation cannot apply, the command identifies its ID, type, entity, and reason and leaves the batch saved. Review again after changing the batch or head; this view does not reserve what a later commit will record.

For any of the six list readers above, a staged first page and continuation use the returned opaque next_cursor; no --at_head is required:

TAGS="$(flywheel repo:tag:list "$GRAPH_ID" --staged --format=json)"
NEXT_CURSOR="$(printf '%s' "$TAGS" | jq -r '.next_cursor // empty')"
if [ -n "$NEXT_CURSOR" ]; then
  flywheel repo:tag:list "$GRAPH_ID" --staged --after "$NEXT_CURSOR" --format=json
fi

Staged pages contain at most 256 results. Their cursor binds the repository, reader and query, nullable committed base, and staging digest; if any of those change, the cursor is stale and you must restart at the first staged page. An optional staged --at_head is only a current-base consistency check, not a pending-state selector.

Commit locally

#!/usr/bin/env bash
flywheel repo:commit --message "Create documentation node" --format=json

The node is now durable in the local repository, and the staging area is empty. Projection reads such as flywheel repo:node:get <NODE_ID> now see it in a new CLI process; use the node_id returned by the create command. Their at_head is a current committed-head consistency guard: a stale or mismatched head rejects, and it does not select historical state. For a paged collection, reuse that exact at_head with the response's next_cursor; --after requires that explicit --at_head.

GRAPH_READ="$(flywheel repo:node:get "$GRAPH_ID" --format=json)"
HEAD="$(printf '%s' "$GRAPH_READ" | jq -er '.at_head')"
EDGES="$(flywheel repo:edge:list "$GRAPH_ID" --at_head "$HEAD" --format=json)"
NEXT_CURSOR="$(printf '%s' "$EDGES" | jq -r '.next_cursor // empty')"
if [ -n "$NEXT_CURSOR" ]; then
  flywheel repo:edge:list "$GRAPH_ID" --at_head "$HEAD" --after "$NEXT_CURSOR" --format=json
fi

A local commit records the unchanged reviewed batch in local history; it does not push or otherwise publish it to the hosted service.

Recover already-invalid staging

If an older saved batch cannot commit, repo:status --format=json still reports its staging.digest and staging.operation_count without applying pending operations. Discard is an explicit choice to remove all saved pending operations and associated values from only the selected repository. That uncommitted work is not recoverable through graph history.

#!/usr/bin/env bash
# Run only when you choose to lose all saved pending work in this repository.
flywheel repo:status --repo ./my-repo --format=json
# Optional: preserve the repository, including its invalid staging batch.
# Restoring this archive does not repair that batch.
# flywheel repo:backup ./before-discard.flywheel-backup.zip --repo ./my-repo
# Copy staging.digest from the status output; do not substitute a refreshed digest.
flywheel repo:staging:discard --repo ./my-repo --staging-digest "<DIGEST_FROM_STATUS>" --confirm --format=json
# Stage corrected work and record it locally. This does not push or publish.
flywheel repo:node:create --repo ./my-repo --title "Corrected work" --format=json
flywheel repo:commit --repo ./my-repo --message "Record corrected work" --format=json

Backup is optional and remains a separate command. Its archive preserves the repository's staging row, including an invalid batch; restoring it does not repair the batch. Discard creates no backup or export automatically, requires no credentials, and sends no data off the machine.

Both --staging-digest and --confirm are required, including in scripts. A stale digest or concurrent workspace change fails without discarding a different batch; inspect status again before making another choice. A matching empty batch is a normal zero-discard success. JSON and the existing human, table, CSV, and TSV formats retain repository_id, discarded.staging_digest, discarded.operation_count, resulting.staging_digest, and resulting.operation_count.

Discard preserves committed graph data and history, local and remote-tracking heads, objects, synchronization metadata, rebase/conflicts, and other repositories. It does not repair the graph, create a commit, or publish. It is never implicit in reads, edits, validation, failed commit, pull, push, revert, startup, migration, or upgrade.

Publish explicitly

#!/usr/bin/env bash
flywheel repo:push --create-remote --format=json

This first push creates the hosted repository and publishes the local commit. It requires CLI authentication to the intended service.

To create a personal repository, run flywheel repo:init --personal. The default location is $HOME/Flywheel; later commands select it with --personal. An explicit --repo <root> remains available and cannot be combined with --personal.

Later repo:push and repo:pull reuse the stored hosted association and selected credential profile. Hosted edits use a different path: see the tutorial.

Migrating hosted-write scripts

Hosted graph writes are supported through the hosted: namespace. Every superseded command fails without mutating and prints its exact repo: or hosted: replacement. Pass --format=json explicitly in scripts.

Use the CLI reference for command details and the tutorial for a guided walkthrough.