PC

pa1ar/craft-cli

Developer tools
21 stars Quality 41 Trend 31

hybrid CLI for Craft Docs optimized for speed and reducing API calls

Overview

Local-first CLI for Craft Docs: fast reads from Craft Desktop's cache when eligible, REST fallback, and API-only writes. Single-binary Bun CLI, AI-agent-first, runs on macOS and Linux. Also exports a TypeScript library for Raycast and Node scripts. Unofficial. Not affiliated with Craft Docs. On macOS, keep craft source auto. Unfiltered docs ls and simple docs search use Craft's local cache first and fall back to REST automatically. Do not pass --api routinely. Markdown document reads also use the cache. Structured/raw reads, tasks, reminders, collections, filtered queries, and every write use REST. To install craft-cli, paste the block below into your AI coding agent (Claude Code, Codex, OpenCode, Cursor, etc.). The agent will handle clone, build, install, authentication, and skill registration. A local-first CLI for Craft Docs. The primary goal is to make Craft content as easy to read and edit from AI coding agents (Claude Code, Codex, OpenCode, etc.

README

craft-cli

Local-first CLI for Craft Docs: fast reads from Craft Desktop’s cache when eligible, REST fallback, and API-only writes.

Single-binary Bun CLI, AI-agent-first, runs on macOS and Linux. Also exports a TypeScript library for Raycast and Node scripts.

Unofficial. Not affiliated with Craft Docs.

Agent read policy: On macOS, keep craft source auto. Unfiltered docs ls and simple docs search use Craft’s local cache first and fall back to REST automatically. Do not pass --api routinely. Markdown document reads also use the cache. Structured/raw reads, tasks, reminders, collections, filtered queries, and every write use REST.


Install (paste this to your AI agent)

To install craft-cli, paste the block below into your AI coding agent (Claude Code, Codex, OpenCode, Cursor, etc.). The agent will handle clone, build, install, authentication, and skill registration.

You are installing craft-cli (https://github.com/pa1ar/craft-cli) for me. Use the latest public stable release and its matching bundled skill. Proceed end-to-end; ask only when a required choice or credential is missing.

1. RUNTIME CHECK
   Check whether `bun` is on PATH. If yes, continue.
   If not, use my stated package-manager preference, or install Bun from https://bun.sh. This project builds with Bun.

2. RELEASE + BUILD + INSTALL
   Resolve the latest public stable release:
   CRAFT_RELEASE_TAG="$(bun -e 'const r = await fetch("https://api.github.com/repos/pa1ar/craft-cli/releases/latest"); if (!r.ok) throw new Error("GitHub release lookup failed: " + r.status); const release = await r.json(); if (!release.tag_name || release.draft || release.prerelease) throw new Error("No stable release"); console.log(release.tag_name)')"
   Stop if that lookup fails. Do not silently install main or a local development binary.
   CRAFT_RELEASE_DIR="$HOME/.local/share/craft-cli/releases/$CRAFT_RELEASE_TAG"
   If the release directory already exists, preserve it. Verify its origin, tag commit and tracked source state before reuse; use a fresh directory if it has local edits.
   git clone --branch "$CRAFT_RELEASE_TAG" --depth 1 https://github.com/pa1ar/craft-cli.git "$CRAFT_RELEASE_DIR"
   cd "$CRAFT_RELEASE_DIR"
   Read this release's install.sh, then run ./install.sh.
   If install.sh fails, diagnose and report the failure before using its documented manual build/link equivalent. Keep the same release tag.
   Verify `~/.local/bin` is on PATH and `command -v craft` resolves to this release's binary. Add the PATH entry to my shell rc only if missing (detect zsh vs bash). Use `craft` from PATH for normal work.
   Record the release tag and `git rev-parse HEAD`; version alone does not prove a build came from a release.

3. CONNECTION
   Run `craft doctor --json` to check existing configuration. Preserve a working connection and verify the intended space; do not ask for credentials again.
   If configuration is missing or invalid, ask for the Craft API URL and API key from Craft → Connections → New API Connection, then run `craft setup --url "" --key ""`.
   Do not print stored API keys. A Craft connector may point to a different space than the CLI.

4. READ SOURCE
   On macOS with Craft Desktop installed, run `craft source auto`. Eligible reads use the local cache first and fall back to REST. Do not persist API-only mode or add `--api` to ordinary reads.
   On Linux, remote/headless hosts, or machines without Craft Desktop, use `craft source api`.

5. VERIFY
   command -v craft
   craft doctor --json     # connection, intended space, source and local availability
   craft docs ls           # local output is marked "documents (local)" when the cache is available
   Use `craft --help` and `craft which ` for this release's command names. Do not substitute Craft MCP command syntax.

6. MATCHING SKILL
   Use the complete `skill/` directory from the same release, including references, rather than a development checkout's skill.
   Honor my canonical skills folder first. Preserve existing customizations before replacing or updating a skill. For an existing custom skill, compare it with this release and explicitly resolve differences; do not silently mix versions.
   Register the canonical skill directory in the harness's supported user-skill location. Prefer symlinks and preserve real directories. If no canonical folder is specified, use the current harness's supported location; ask only if it cannot be determined.
   Inspect installer-created skill links: older releases may link only Claude automatically. Ensure the current harness can read the matching `SKILL.md` and its references, then read that skill before normal CLI work.
   Keep fixes for the next release in the development repo; keep the installed release and its skill unchanged while testing the public version.

7. REPORT BACK
   Tell me the release tag and commit, resolved binary path, skill location and harness registration, effective read source, intended Craft space, local-cache availability, and any installation failure or workaround.

For humans

A local-first CLI for Craft Docs. The primary goal is to make Craft content as easy to read and edit from AI coding agents (Claude Code, Codex, OpenCode, etc.) as local files are in tools like Obsidian.

The CLI handles the API’s footguns and undocumented behaviors internally - rate limits, inconsistent payload keys, silent routing of unanchored inserts, missing content keys at depth 0, RE2 regex edge cases, backlink resolution via title search - so that agents and scripts don’t have to.

Built with Bun. Ships as a single compiled binary. Also exports a TypeScript library for programmatic use.

Why this exists

Craft has a solid API but no official CLI. AI agents need a fast, predictable, scriptable interface to work with Craft content at the same speed they work with local files. This fills that gap.

Distribution

This is a personal tool published as-is. I don’t work for Craft and don’t plan to maintain package manager distributions (Homebrew, npm global, etc.). If Craft wants to adopt or fork this into an official CLI, they’re welcome to.

Install from source: see the install block above.

Setup

Getting your API key

  1. Open the Craft app (macOS/iOS) or craft.do in a browser
  2. Go to Connections
  3. Click the + button (top left) and select New API Connection
  4. Choose which documents will be accessible via this connection
  5. Switch Access Mode from “Public” to API Key
  6. Click + next to “API Keys”, enter a name, and click Create
  7. Copy the key immediately - you won’t see it again
  8. Copy the URL from the connection panel

Then run:

craft setup --url "https://connect.craft.do/links/XXX/api/v1" --key "pdk_..."

Credentials stored at ~/.config/craft-cli/config.json (mode 0600). Env overrides: CRAFT_URL, CRAFT_KEY, CRAFT_PROFILE, CRAFT_SOURCE (see Read source), legacy CRAFT_MODE, CRAFT_LOCAL_PATH, CRAFT_LOCAL_TIMEOUT_MS, CRAFT_ON_DEVICE_ASSETS_PATH.

On Linux or any host where Craft is not installed, run craft source api after setup to skip local-store discovery entirely.

Commands

craft whoami                     identity and space info
craft doctor --json              auth/API/source/local health check
craft agent-context              JSON manifest for agents
craft which          find command for an intent
craft profiles list              manage multiple spaces
craft source [auto|api|local]    show or persist read source (auto default)

craft folders ls                 folder tree
craft folders mk / rm            create / delete folders

craft docs ls                    list documents (unfiltered: local-first in auto)
craft docs search "query"        search content (local FTS5 or API RE2)
craft read                   read cached Markdown, with API fallback
craft read  --outline        list headings with line numbers
craft read  --budget 2000     bound output for an agent
craft docs get  --links      include backlinks (API)
craft docs daily [DATE]          today's daily note
craft docs mk / mv / rm          create / move / trash documents
craft docs open              print deeplink and open in Craft app

craft blocks get             read a block tree
craft blocks search  "re"   search within a document
craft blocks append  --markdown "text"
craft blocks append --date today --markdown "text"
craft blocks insert / update / mv / rm
craft blocks mermaid  --file diagram.mmd # rendered diagram in Craft

craft tasks                       all tasks across the space
craft tasks ls [scope]            optional API scope: inbox/active/upcoming/logbook/document
craft tasks ls --state todo --document "Project" --deadline-to tomorrow
craft tasks ls --reminder yes --repeat yes --json
craft tasks add / update / rm       create, reschedule, move, complete, or remove tasks

craft reminders                    incomplete block reminders
craft reminders add  [--at ISO8601]
craft reminders reschedule  --at ISO8601|none
craft reminders complete / reopen / rm ...

craft col ls / schema / items    collections and structured data
craft col items add / update / rm
craft col views ls/create/update/active/rm

craft links out              outgoing links (parsed from markdown)
craft links in               backlinks (title-based vault search)

craft upload  --parent 
craft comment  "text"
craft wb mk / el add / el get / el update / el rm

craft patch  --old STR --new STR  find and replace in blocks
craft cat  [id...]               read multiple docs at once
craft diff                       compare to last known state
craft undo [id] [--force]            revert last mutation
craft log [id] [--last N]            mutation history

craft raw GET|POST|... /path     escape hatch for any API endpoint

craft skills ls/search/show      discover bundled + ~/.craft-cli/skills automations
craft skills validate 
craft skills run   [...args] [--estimate] [--max-cost EUR]
craft media local       resolve the on-device full asset path
craft media analyze     analyze Craft media, preferring the local asset
craft media replace    upload, verify, and replace media

Global flags: --json, --select id,title, --profile NAME, --quiet, --depth N, --no-links, --source auto|api|local, --api, --dry-run on writes.

Mermaid diagrams

Craft natively renders Mermaid code blocks as diagrams inside documents. The API stores and returns the source; the Craft app renders it.

craft blocks mermaid PAGE_ID --file diagram.mmd --position start
craft blocks mermaid PAGE_ID --code "flowchart LR; Visitor --> App; App --> Database"
cat diagram.mmd | craft blocks mermaid PAGE_ID -

Pass bare Mermaid source without Markdown fences. --date DATE targets a daily note, --dry-run --json previews the payload, and craft undo PAGE_ID can undo the insert. Syntax is rendered and checked by Craft, not by the CLI.

The equivalent structured block for craft blocks insert PAGE_ID --file blocks.json is:

[{"type":"code","language":"mermaid","rawCode":"flowchart LR\n  Visitor --> App\n  App --> Database"}]

Agent read routing

Keep source=auto on macOS. It uses Craft’s local SQLite and PlainTextSearch cache for unfiltered docs ls and simple docs search, then falls back to the API if the cache is unavailable or the query needs API-only filters. media local separately resolves downloaded assets from Craft’s on-device cache.

Markdown read, docs get/daily, blocks get, and cat also prefer the Desktop cache. Structured/raw/depth/metadata reads, tasks, reminders, collections, backlinks, filtered/fetch-block searches, and all writes use REST. Strict local Markdown reads reject missing content or unsupported flags without network access. Use --source api only when the task explicitly requires authoritative remote state or API-only query behavior. Check routing with craft source --json and availability with craft doctor --json.

Current Craft API coverage

Current coverage includes experimental block reminders, collection-view CRUD and active-view selection, space-wide tasks through documented scope=all, page styling and separator fields, typed media upload/insert, local media resolution, and safe media replacement. craft raw remains the escape hatch for newly published endpoints.

Reminder availability depends on the connection. OAuth connections support single-user and multi-user spaces. Link-based API connections support reminders only when the link creator is the space’s sole active participant. Reminder times require an ISO 8601 timestamp with Z or an explicit UTC offset; omitting the time saves the block for later without a notification.

Craft app 3.6 features including editable inline tags, arbitrary custom colors, and Daily Notes range export do not currently have documented REST operations, so craft-cli does not claim support for those app-only features.

Collection views

craft col views manages stored collection view configuration: table, gallery, kanban, filters, sorting, grouping, hidden fields, column order, calculations, and the active view. These endpoints do not execute filters/sorts/groups or return filtered items; fetch items separately with craft col items .

craft col views 
craft col views create  --file view.json
craft col views update   --file view.json
craft col views active  
craft col views rm  

Skills

craft skills is a demand-loaded automation layer. V1 discovers curated bundled repo skills plus explicit local skills from ~/.craft-cli/skills; there is no remote/community install flow. Search is manifest keyword search over name, description, tags, and commands.

craft skills ls
craft skills search media
craft skills show media-analyze
craft skills validate media-analyze
craft skills run media-analyze analyze  --estimate

Skills run as subprocesses with structured JSON stdin/stdout. The skill can propose writes, but craft-cli owns all Craft writes and journal integration.

Media analysis

craft media local 
craft media local  --all --json
craft media analyze 
craft media analyze  --estimate
craft media analyze  --max-cost 0.50 --json
craft media replace  edited.mov --content-type video/quicktime

craft media local reads Craft’s OnDeviceAssets/index.json and prints the existing full-resolution asset path, falling back to a preview only when no full asset is present. These files are owned by Craft: copy them before editing.

craft media analyze is a curated alias for craft skills run media-analyze analyze . It prefers an on-device asset and falls back to the fresh signed Craft URL. V1 is generic media analysis only, OpenAI-first, with a default EUR 1 cap. Useful artifacts are written into a Craft-visible run block under the source block: final analysis, transcript text when available, contact-sheet path, metadata JSON, model/cost metadata, and failure/partial status. Raw intermediate media files stay in ~/.cache/craft-cli/media-analyze.

craft media replace validates the existing image/video/file block, uploads the replacement immediately before it, verifies that the new block has the same media type, and only then deletes the old block. Craft’s REST API cannot change an existing media block’s URL, so the replacement receives a new block ID. The command refuses blocks with children or comments.

Why this is faster than the API or MCP

craft-cli uses a hybrid read architecture on macOS: eligible listing, search, and Markdown reads come from Craft’s local SQLite FTS5 index and PlainTextSearch JSON files, while structured block reads and all writes use the REST API. The local stores typically update within about a second while Craft Desktop is running and synced; use API source when authoritative remote state matters.

flowchart LR
    subgraph READ ["READ (local, ~1ms)"]
        direction TB
        R1[craft docs ls] --> SQLite[(SQLite FTS5)]
        R2[craft docs search] --> SQLite
        R3[craft diff] --> JSON[(PlainTextSearch JSON)]
    end

    subgraph WRITE ["WRITE (API, ~500ms)"]
        direction TB
        W1[craft blocks update] --> API[Craft REST API]
        W2[craft patch] --> API
        W3[craft undo] --> API
    end

    API -->|"syncs ~1s"| Craft[Craft App]
    Craft -->|derives| SQLite
    Craft -->|derives| JSON

    WRITE --> Journal[(Journal SQLite)]
    Journal --> READ

    style READ fill:#1a3a1a,stroke:#4a8a4a
    style WRITE fill:#3a1a1a,stroke:#8a4a4a

On average, local reads are ~3,600x faster than API calls. A 10-step AI workflow that takes ~25s via API completes in ~200ms locally.

Benchmarks (1ar vault, ~1,200 docs, ~46,000 blocks)

Operation REST API Craft MCP craft-cli (local) craft-cli (API fallback) Speedup
Search vault for a term 2,271ms 2,271ms + MCP hop 1.3ms 2,271ms 1,700x
Read document content 4,561ms 4,561ms + MCP hop 0.7ms 4,561ms 6,300x
Check if doc changed 3,247ms (full fetch) 3,247ms + MCP hop 0.5ms (contentHash) 3,247ms 6,600x
List all documents 1,489ms 1,489ms + MCP hop 184ms 1,489ms 8x

Methodology: 5 iterations each, wall-clock time, same machine. API = Craft REST API via HTTP. MCP = same API + MCP protocol overhead. Local = bun:sqlite FTS5 queries + JSON file reads. See docs/local-performance-results.md for raw data.

Why not just use the API?

The API is the only write path and the authoritative source for block hierarchy. But for reads:

Dimension REST API / MCP craft-cli hybrid
Read latency 150ms - 4.5s per call <1ms - 184ms (local SQLite + JSON)
Search reliability regexps mode misses short terms FTS5 finds them (unicode61 tokenizer)
Change detection must fetch full doc to compare contentHash field, single JSON read
Offline reads no yes (local data stores)
Rate limits yes (though generous) no (local reads are free)
Context window cost verbose JSON responses compact markdown or --json on demand
Backlinks not supported natively faked via title search + block:// filter
Mutation history none SQLite journal with diff/undo/log

Why not just use local files (Obsidian-style)?

Local markdown vaults (Obsidian, etc.) are the gold standard for AI file editing - instant read/write, grep, git diff. But Craft’s block model doesn’t map cleanly to flat markdown: nested pages, cards, collections, styled blocks, tasks with scheduling all lose structure. Mirroring to files creates a cache invalidation nightmare without webhooks.

craft-cli takes a different approach: read from Craft’s own local data stores (which Craft keeps in sync), write through the API. No mirroring, no sync to manage, no structure loss.

Dimension Local MD (Obsidian) craft-cli
Read speed <1ms <1ms (local), 150ms-4.5s (API fallback)
Write speed <1ms 200-800ms (API, server-validated)
Rich content (cards, tasks, collections) no yes (full block model)
Sync to Craft app none instant (API writes sync, local reads from Craft’s DB)
Change detection git diff contentHash + journal-based craft diff
Undo git checkout craft undo (journal-based, read-before-write safety)
Surgical edit Edit tool (line-based) craft patch (block-based, same find-and-replace pattern)

Sync timing

Both local data stores (SQLite FTS5 and PlainTextSearch JSON) update within 1 second of a write via the API or the Craft app. Verified by appending a marker block via API and polling local file modification times. Craft app must be running for sync to occur.

Architecture

How Craft stores data locally

Based on reverse-engineering Craft’s local data stores (2026-04-06). This is not official documentation - Craft can change any of this without notice.

graph TD
    subgraph "Craft App"
        APP[Craft macOS App]
    end

    subgraph "Server"
        SYNC[Craft Cloud Sync]
        API[REST APIconnect.craft.do/api/v1]
    end

    subgraph "Local disk (~/Library/Containers/com.lukilabs.lukiapp/...)"
        REALM[(Realm binary DBsource of truthfull block tree, hierarchy,folders, metadata, styles)]
        SQLITE[(SQLite FTS5search indexplain text only, flat)]
        JSON[PlainTextSearch JSON1 file per docfull markdown, tags,contentHash, timestamps]
    end

    APP -->|writes| REALM
    REALM -->|derives| SQLITE
    REALM -->|derives| JSON
    APP |syncs| SYNC
    SYNC -->|serves| API

    subgraph "craft-cli"
        CLI[CLI binary]
        LIB[CraftClient lib]
        LOCAL[Local DB reader]
        JOURNAL[(Journal SQLite~/.cache/craft-cli/journal.db)]
    end

    CLI --> LIB
    CLI --> LOCAL
    CLI --> JOURNAL
    LIB -->|reads + writes| API
    LOCAL -->|reads only| SQLITE
    LOCAL -->|reads only| JSON
    JOURNAL -->|tracks mutations| JOURNAL

Where data lives

Data Realm (binary) SQLite FTS5 PlainTextSearch JSON REST API
block hierarchy (parent-child tree) yes no (flat) no (flat markdown) yes
block markdown with formatting yes no (plain text) yes (full markdown) yes
block IDs yes yes no yes
document title yes yes (content column) yes yes
document tags yes no yes (tags[]) no (parse from content)
isDailyNote flag yes no yes no
modification timestamp yes no yes (NSDate) yes (fetchMetadata)
last viewed timestamp yes no yes no
contentHash (change detection) yes no yes no
folder structure yes no no yes
collection/database schema yes no no yes
block styles (color, font, list) yes no no yes
full-text search index no yes (FTS5) no yes (RE2 regex)
queryable from CLI no (binary) yes (bun:sqlite) yes (JSON.parse) yes (HTTP)
writable from CLI no no no yes (only path)

How craft-cli uses this

Source auto (default): unfiltered docs ls and simple docs search read from local SQLite + PlainTextSearch JSON when available. Unsupported reads and all writes go through the REST API. It falls back automatically when local data is absent or the query needs API-only filters.

Source api: run craft source api once to persist the setting (stored in ~/.config/craft-cli/config.json). Use this on Linux or any host where Craft is not installed. Journal, undo, log, and diff keep working — they use ~/.cache/craft-cli/journal.db, which is cross-platform. Flip back with craft source auto, check current state with craft source.

Source local: require the local store for local-capable listing/search commands and fail if their query is unsupported locally. Commands that inherently require REST still use REST.

craft source              # show current source
craft source api          # persist api-only (Linux / headless)
craft source auto         # persist local-first with API fallback
craft source local        # require local for eligible list/search reads
CRAFT_SOURCE=api craft ...  # runtime override, one invocation
craft docs ls --source api  # per-command override
craft docs ls --api         # legacy shortcut for --source api

Precedence (highest wins): --source flag → --api shortcut → CRAFT_SOURCE env → legacy CRAFT_MODE env → persisted config.source → legacy config.mode → auto default.

Mutation journal: every write command (blocks append/insert/update/rm/mv, tasks add/update/rm, patch) records pre/post state to ~/.cache/craft-cli/journal.db. Enables craft diff, craft undo, and craft log.

Library usage

import { CraftClient } from "@1ar/craft-cli/lib";

const craft = new CraftClient({ url: process.env.CRAFT_URL!, key: process.env.CRAFT_KEY! });
const hits = await craft.documents.search({ regexps: "LTM|memory" });
const doc = await craft.blocks.get(hits.items[0]!.documentId, { format: "markdown" });

Downstream consumers

  • Raycast extension - imports CraftClient from this library for a native macOS Raycast UI
  • Agent skill, registered in the user’s canonical skills folder or the active harness’s user-skill directory - teaches AI agents to use the CLI

License

MIT

Skills from Craft collections

Choose a Craft document as the canonical skills library with craft lib setup --document ID --collection ID. It holds a skills collection and optional guide. craft lib pick "request" --published --max-output 5 --json selects skills using keywords, or optional --jev. Load only selected bodies and resources.

Scoped snapshots provide API/local parity and offline reads after lib refresh. Export full folders with references, scripts and assets. Optional lib sync --out ~/dev/generated-skills manages a downstream folder; GitHub delivery can use a pull request or direct commit to an explicit target. Codex and Claude Code prompt-hook adapters share the picker.

See the collection and sync contract for setup, authoring, source modes, ownership and hook configuration.

Read performance and design

Aim for common local actions under 100 ms. Paired warm-cache measurements on one Mac put full Markdown reads at 58–86 ms; plain file reads remain around 3–4 ms. This is a target, not a guarantee for API calls, cold caches, or every document.

Craft owns its cache and document structures. The CLI reads that cache as-is, uses API fallback when content is unavailable. Desktop sync can lag after writes; request --source api for immediate remote confirmation. Measurements and method.

Canonical skill libraries additionally use scoped snapshots and explicit downstream mirrors, as described above.

View the full README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add pa1ar/craft-cli
New to MCP? Start with the mcpskill.org guide →