SKILL.md

At 62577fb

62577fbHistory

Preview

Text · 15.7 KiB

name: mkit description: > Drive the mkit CLI — a content-addressed version control tool with BLAKE3 object IDs, Ed25519-signed commits, and native in-toto/DSSE attestation. Use this when working in a .mkit/ repository, making signed commits, managing signing keys, producing or verifying supply-chain attestations, inspecting content-addressed objects, or syncing over mkit+ssh/https/s3/file transports. mkit mirrors git's CLI/UX, so git muscle memory applies — this skill focuses on the parts that are not like git.

mkit

mkit is a git-like CLI that produces signed, content-addressed objects. Every commit is Ed25519-signed and named by its BLAKE3 hash, so object chains are self-verifying independent of where they're stored, and any commit can carry attestations (in-toto v1 Statements in DSSE envelopes) that downstream services verify against a trust-roots registry.

If you already know git, you can drive add/commit/log/branch/merge/ rebase/stash/diff/status etc. by reflex. Spend your attention on the four differences below and the differentiator commands — that's where mkit is not git.

Setup

# Install the CLI from crates.io (the public channel). The binary is `mkit`.
cargo install mkit-cli
#   ^ NOTE: install `mkit-cli`, NOT `mkit` — `mkit` on crates.io is a
#     different, unrelated crate. Do not run `cargo install mkit`.
#   (Signed release archives are also published on GitHub Releases; crates.io
#    is simply the most convenient install channel.)

mkit --version          # must print exactly: mkit <X.Y.Z>

# Start a repo and make your first signed commit:
mkit init               # creates .mkit/ in the current directory
mkit keygen             # generate an Ed25519 signing key (signing_key path; default .mkit/keys/default.key)
echo hello > hi.txt
mkit add hi.txt
mkit commit -m "first commit"   # commits are always Ed25519-signed

A signing key is mandatory: commit/tag -s/attest need one. If you skip keygen, commits fail. commit opens an editor ($GIT_EDITOR, then $EDITOR, then $VISUAL, falling back to vi/notepad) when -m/-F is omitted — so in a headless or agent context, always pass -m or -F so it never blocks on an editor.

Mental model: like git, with four differences that matter

  1. Object IDs are 64-hex BLAKE3, not 40-hex SHA-1. A git SHA pasted into mkit will never resolve. Use --short[=N] / rev-parse --short for abbreviations. Short-prefix lookups work like git.
  2. The repo marker is .mkit/, not .git/. Layout is parallel: .mkit/objects/, .mkit/refs/, .mkit/HEAD, .mkit/config.
  3. Safety guards over git's destructive defaults. Data-losing operations refuse without -f, and most accept -n/--dry-run to preview: reset --hard, clean, restore, branch -D (still refuses the current branch), push --force (prefer --force-with-lease), gc.
  4. Authorship is cryptographic. The signed author defaults to your signing key's public key (an ed25519:<hex> identity) — no config needed. user.identity overrides it (ed25519:<hex> / mid:<N>); user.name / user.email are accepted as git-compat aliases but are non-authoritative — they never set who signed.
  • Partial and full disclosure (no git counterpart): mkit prove, mkit verify-proof, mkit closure export, and mkit closure verify prove a path/chunk/range or a full object set against a commit id, for an external party with no repository access (a DA provider, a light client, a browser). See docs/VERIFY.md for the verifier-facing guide; docs/CLI.md for flags.

Accepted-but-no-op / out of scope (so you don't wait on them): log --graph is accepted but does nothing; submodules, hooks, git notes, and .git/-format interop are explicit non-goals. Linked working trees ARE supported: mkit worktree add/list/remove/prune (linked trees share the object store and refs; each tree keeps its own HEAD/index/stash, and a branch can be checked out in at most one tree).

Signing keys

Commits and tags are always Ed25519-signed. The commit/tag signing key is Ed25519 and lives at .mkit/keys/default.key:

mkit keygen [--algorithm ed25519|secp256k1|p256] [--force] [--print-pubkey]
                                         # Ed25519 -> .mkit/keys/default.key
mkit verify <rev>                        # check the signature on a commit, remix, or signed tag
mkit tag -s <name> -m "msg"              # signed tag (always pass -m; no -m opens an editor)

Instead of the repo-local file, an Ed25519 key in the OS keystore can sign (custody that persists across repos — Keychain / libsecret / systemd-creds / YubiKey):

mkit key generate            # also: list | import | export | delete

Grants, epochs and visibility (private repos and delegated writes)

An owner can let another key write to (or read a private) repository with a signed grant; the grantee stores it and mkit push/clone present it automatically.

mkit grant create --cap read,write --grantee <ed25519-pubkey-hex> --repo site \
    --refs 'refs/heads/*=cuf' --ttl 7d > grant.txt   # owner: prints the grant header
mkit grant add grant.txt                             # grantee: verify + store it
mkit grant list [--check]                            # what you hold (--check: stale/future epoch)
mkit epoch show <remote>                             # the namespace's current epoch
mkit grant revoke <remote> [--prune]                 # owner: epoch bump, revokes older grants
mkit visibility set <remote> private                 # owner: public|private (--statement for a wallet/passkey owner)
  • --cap is read, read,write or write; writes need --refs pattern=flags (flags from cufd). A server that can't check fast-forwards (every current deployment) applies an update only with f, so use cuf.
  • The owner signs with the mkit key by default; a wallet or passkey signs by --print-statement, then --statement-file f --signature HEX (or --webauthn-assertion, which needs grant.webauthn_rp in the user config).
  • Among usable grants the higher epoch wins; a grant issued for a future epoch outranks a live one until the owner bumps the epoch. A write-only grant never lets you read a private repository.
  • mkit grant add (your store) is the client-side grant store; it does not register grants on a server.

The signed author is auto-derived from the signing key (difference #4 above); set user.identity only to pin a different one.

keygen --algorithm secp256k1|p256 does NOT make a commit key. It writes a separate attestation signer key (.mkit/keys/<alg>.key) consumed by attest (below). Generating one and then running commit fails with "no signing key" — run plain mkit keygen for the Ed25519 commit key.

Attestation (in-toto v1 plus DSSE)

Attach signed, verifiable claims (provenance, review, SBOM, …) to a commit:

# Produce a DSSE attestation for HEAD (or --commit <hash>), optionally with a
# predicate document and multiple co-signers (all-or-nothing — any signer
# failure aborts; no partial envelopes are written). Multi-signer is
# shell-only — the MCP tool is single-signer by design:
mkit attest --algorithm ed25519 \
            --additional-signer "algorithm=p256,signer=repo-key" \
            --predicate-type https://example.com/review/v1 \
            --predicate-file review.json

# Verify every attestation on a commit against a trust-roots registry:
mkit verify-attest --commit <hash> --trust-roots ~/.config/mkit/trust-roots.toml

Security gate: verify-attest refuses to use an in-repo trust-roots file unless you pass --trust-roots explicitly — otherwise a hostile clone could ship its own roots and make verification print "ok" against attacker keys. Default roots path is $XDG_CONFIG_HOME/mkit/trust-roots.toml. Exit 0 iff every attestation has ≥1 verified signature, 65 if any failed, 1 if the commit has no attestations.

Content-addressed object inspection

mkit hash <file>                 # hash a file and store it as a blob → prints its id
mkit cat <hash>                  # dump an object by id
mkit cat-file -t <object>        # type | -s size | -p pretty (blob/tree/commit/tag)
mkit cat-file --batch            # stream object ids on stdin
mkit tree                        # snapshot the working dir as a tree object
mkit ls-tree -r <tree-ish>       # list tree entries (-r recurse, -z NUL-terminate)
mkit rev-parse --short <rev>     # resolve a revision to an (abbreviated) id
mkit rev-list --count <rev>      # count commits reachable from <rev> (omit --count to list)
mkit merge-base [--is-ancestor] <a> <b>  # common ancestor (or test ancestry: exit 0/1)

Global flags (before the subcommand, like git)

mkit -C <path> <command>         # run as if started in <path> (repeatable)
mkit -c user.email=ci@x.com <command>  # one-shot config override (inert/allowlisted
                                 # keys only; security-sensitive keys are refused)
mkit --no-pager <command>        # accepted no-op — mkit never paginates

Branch switching

mkit switch <branch>             # switch branches (git switch)
mkit switch -c <new> [<start>]   # create + switch (-C create-or-reset)
mkit checkout -b <new> [<start>] # same, classic spelling (-B create-or-reset)
mkit branch --show-current       # print the current branch name

Remotes and transports

Remote URLs use the strict mkit+<scheme>:// form only (anything else is hard-rejected):

SchemeFormUse
mkit+filemkit+file:///abs/pathlocal filesystem mirror
mkit+httpsmkit+https://host[:port]/pathHTTP gateway
mkit+s3mkit+s3://endpoint/bucket[/prefix]S3-compatible store
mkit+sshmkit+ssh://user@host[:port]:pathSSH with the mkit shell
mkit remote add origin mkit+https://gateway.example/repo
mkit clone [--depth N] [--sparse <pattern>...] mkit+ssh://user@host:path
mkit push [--all] [--force-with-lease] [--dry-run]
mkit pull            # or: mkit fetch  (download without merging)

# Trust an HTTP/S3 remote for ambient env credentials, or tune SSH policy:
mkit config trusted_remote_endpoint <url>
mkit config ssh.strict_host_key_checking <yes|no|accept-new>
mkit config ssh.identity_file <path>

mkit+ssh uses SSH_AUTH_SOCK (standard OpenSSH agent). mkit serve <path> starts the SSH-transport server (internal/host side).

Durability: pack-shards

mkit pack-shard <hash>   # Reed-Solomon erasure-code a stored pack into shards
                         # (requires the `pack-shards` build feature)

Prefer MCP over raw shell (when available)

The CLI ships a local MCP server: mkit mcp [--repository <path>]. If your harness supports MCP, register it and drive repositories through structured tool calls instead of shelling out — inputs are validated, no interactive paths exist, and destructive guards can't be overridden (the server never passes -f):

claude mcp add mkit-repo -- mkit mcp --repository /path/to/repo

Its 21 tools cover the everyday flow (mkit_status, diffs, mkit_log, mkit_show, mkit_branch, mkit_add, mkit_unstage, mkit_commit, create-branch/checkout, mkit_init, mkit_keygen, mkit_cat_object) plus the differentiators (mkit_verify, mkit_attest, mkit_verify_attest, mkit_prove, mkit_verify_proof, mkit_closure_verify).

What the MCP deliberately does NOT expose — use the shell commands in this skill for these: remotes (push/pull/fetch/clone), history surgery (merge/rebase/cherry-pick/revert), tags, destructive worktree ops (reset --hard/clean/rm), and multi-/external-signer attestation (--additional-signer, --signer external).

Two boundaries are stricter via MCP than the shell: an attest predicate file must resolve inside the repo, and a verify_attest trust-roots path must resolve outside it (in-repo roots are always rejected — hostile-clone defense). Signing is also pinned: MCP attestations always sign ed25519 with the repo key unless you explicitly choose otherwise; ambient attest.* config never steers them.

The shell rules below still apply whenever you do run mkit directly.

Rules for agents

  • Run mkit keygen before the first commit (or use an Ed25519 keystore key). Commits/signed tags/attestations always sign; without a key they fail.
  • Never invoke the interactive variants — they block on $EDITOR or stdin and will hang you: plain commit (always pass -m), annotated/signed tag -a/ -s (pass -m), rebase -i, and add -p. Use the non-interactive forms.
  • No pager, ever. log/diff/show/blame print straight to stdout and exit — capture them directly; you don't need --no-pager or to pipe to cat.
  • Parse machine output, not prose. Many commands take --format=json (log, branch, blame, remote, config, reflog) and status takes --porcelain[=v1|v2]. Use -z for NUL-terminated paths. Note several commands put human prose on stderr and reserve stdout for machine output.
  • Branch on exit codes, not stderr text (see table) — distinguish a usage typo (64) from a retryable transient (75) without scraping messages.
  • Preview destructive ops with -n/--dry-run, commit them with -f.
  • Treat ids as 64-hex. Don't hardcode 40-char SHA assumptions.
  • NO_COLOR=1 (or piping) disables ANSI; CLICOLOR_FORCE=1 forces it.

Exit codes (BSD sysexits)

CodeMeaningCodeMeaning
0success69transport could not connect
1general error / no attestations73cannot create output
64wrong args / unknown subcommand75transient — retry is safe
65malformed input (corrupt object/bad hash)76bad URL scheme / server response
66missing / unreadable input77permission denied
78unknown config key / invalid value

Common issues

SymptomCause / fix
cargo install mkit installs the wrong toolInstall mkit-cli; the binary is mkit.
A pasted id won't resolvemkit ids are 64-hex BLAKE3, not git's 40-hex SHA-1.
commit fails complaining about signing/identityRun mkit keygen (or set up a keystore key) first.
"no signing key" right after keygen --algorithm p256/secp256k1Those make attestation keys, not the commit key — run plain mkit keygen (Ed25519).
mkit command appears to hangYou hit an interactive variant opening $EDITOR/a prompt — pass -m, or avoid rebase -i / add -p.
reset --hard / clean / restore "refuses"A safety guard — re-run with -f (use -n to preview).
remote add rejects the URLMust be mkit+file://, mkit+https://, mkit+s3://, or mkit+ssh://.
verify-attest won't use the repo's trust-rootsIntentional — pass --trust-roots <path> explicitly.
commit opens an editor / aborts with no messagePass -m, or set $EDITOR/$VISUAL.

Going deeper

If the mkit MCP is connected (mcp.mkit.sh), prefer its tools for authoritative depth: get_command <name> for a subcommand's full flags, get_spec <NAME> / list_specs for wire and on-disk formats, search_docs / search_code to find behavior, and get_file 'docs/CLI.md' for the complete reference. In a checkout, the same content lives at:

  • Full command reference: docs/CLI.md and man mkit.
  • Git-parity scope and deliberate divergences: docs/PARITY.md.
  • Wire/on-disk formats and subsystems: docs/specs/SPEC-*.md (objects, signing, attestations, transport, packfile, keystore, …).
  • Install channels (release archives, hardware signers, WASM/npm): docs/INSTALL.md.

Files