gritzko/bee

Beagle-bee, Beagle SCM niceties ported for git repos

5

stars

116

commits

JavaScript

primary language

Aug 26, 2026

updated

README

Bee: CRDT magic for git repos

syntax-highlighted source with permalinks in the comments

This project implements some of Beagle SCM's functionality for regular git repos. The work is ongoing; so far it can do:

  1. CRDT merges (use bee install to add a hook to a git repo),
  2. TUI code browser (use bee ./myfile to open),
  3. HTML code browser (use bee http --port 8888 to run, then open http://localhost:8888/myproject in a browser),
  4. token-level diffs (may start with bee log or http://localhost:8888/myproject/log then click around),
  5. permalink support, version-aware links that survive edits (e.g. index/perma.js:6:ur, see them in the pager/browser, bee see for CLI use, commit hook or be mint to create)

Reproducing Beagle's smooth worktree workflows has proven difficult, as git submodule and git worktree implementations are rather abrasive.

Build

Bundle with the quickjab runtime for a standalone binary. That carries dog/ (libdog), dog/abc/ (libabc) and test/ (the jab-shared API suite) as submodules:

cmake -S <quickjab> -B build -GNinja -DCMAKE_BUILD_TYPE=Release \
    -DJAB_JSRC=<js bundle tree> -DQUICKJAB_JSRC_PACK=ON -DJAB_BIN=bee
ninja -C build quickjab
ctest --test-dir build -R '^JSRC'

That leaves the binary at build/bin/bee and runs this tree's own test/*/run.sh suites against it, exactly as CI does (.github/build-bee.sh). Tagging X.Y.Z releases the same build: a source tarball plus bee for linux x86_64/arm64, macOS arm64 and a fully static linux x86_64.

Usage

The standalone (bundled) bee binary call patterns:

  • bee -- in a git repo: index it, open the list browser;
  • bee <path>... -- page files/dirs off the filesystem
  • bee //<name> <verb> [args] -- the CONTEXT slot (BEE-023): the verb runs in the repo <name>, and //<name>/<rel> is a path inside it wherever an arg is a path (bee cat //bee-journal/todo/BEE/BEE-023.mkd)
    1. <name> is a basename off ~/.config/bee/repos, else the directory $SRC_ROOT/<name> ($HOME/src by default) when a git repo or a linked worktree sits there
    2. a $SRC_ROOT hit is read-only for that run -- it is never registered; use bee install for that
    3. the slot is POSITIONAL and first: after a verb, //<name> is an argument like any other
    4. bare bee //<name> is the zero-arg story in that repo: index it, open the list browser
  • bee index [<repo>] -- build/refresh <repo>/.git/be/; lazy, fully derived; ALL THREE passes, the commit walk, the tip-blob link scan and the kv meta sweep, each off its own mark, all on the one summary line
    1. every initialized submodule is indexed too, depth-first; an uninitialised one is skipped in words
    2. inside a linked worktree it registers the original -- one repository is one line, and it says which
    3. the tail kv: N files, M pairs is the keyed lane (BEE-024): the .md/.mkd files this worktree carries and the live meta pairs they hold
  • bee lindex [<target>] -- backlink suspects; lazy, tip blobs only
    1. bare: bring the link rows up to the tip, this repo only
    2. <target>: the paths that may link to a file or a ticket code, looked up in every repo of ~/.config/bee/repos (read-only) and printed repo-qualified, the local repo first
  • bee sym [--paths] <ident> [.ext|<dir>]... -- where a symbol is mentioned (BEE-063, BEE-066); one prefix scan of the SYM rows the tip-blob pass mints
    1. every hit is a canonical hunk: uri the address <path>:<line>, text the raw mention line with a line of context either side, the file's own tags with the mention marked; two windows merge where they overlap
    2. the rows are suspects and store no positions, so each file is OPENED and one that no longer carries the symbol prints nothing at all
    3. the bytes are the worktree file when there is one, else the tip blob; a binary or over-cap blob prints its path bare
    4. every repo of ~/.config/bee/repos answers (read-only), the local first
    5. the symbol is hashed VERBATIM, so u8bFeed and U8BFeed are two queries
    6. past ~200 suspects a repo prints the count and asks for a narrower query
    7. a further word narrows: .c an extension, dog/abc/ a SEGMENT-ALIGNED path run at any depth (never catalog/abc); alike words OR, the two kinds AND, and a path run prunes the rows before the cap is weighed
    8. --paths: one repo-qualified path per line and no file opened, for scripts
  • bee log [<n>] [<hex>|<path>][?<rev>] -- commit log, newest first; off-mainline rows grey
    1. bare: the checked-out tip's ancestry; <n> caps the rows, 0 = all
    2. <hex>: that commit's ancestry
    3. <path>: the file's revisions
    4. ?<rev>: a branch, tag or hexlet names the tip -- log ?main, log f.c?v2
    5. <sub>/<path>: the submodule's own history, its rows opening in the sub
    6. a ticket code in a summary is a link, like any other reference
  • bee commit [<hex>] -- one commit, header + message; tree/parent hashes are links
  • bee diff [<hex> [<hex>]|<path>] -- the token-level diff: one weave per path, seeded at the two revisions' merge base, every token naming its own commit
    1. bare: worktree vs HEAD
    2. <hex>: that commit vs its first parent
    3. <hex> <hex>: any two revisions, rooted at their merge base
    4. <path>: that file/dir only
  • bee status — the quad: one row per path that is not quiet, four chars wide, upstream HEAD index worktree, . same, x removed, o created, v advanced, ! conflicted; position is authoritative
  • bee list [<path>][?<rev>] -- dir browser: marker, name, last commit, age
  • bee cat <path>[?<rev>] -- the file's own bytes, syntax-painted
  • bee tree [<hex>|<path>][?<rev>] -- raw git-tree rows, .. row when descended
  • bee blob <hexlet> -- a blob by object name
  • bee see [-C<n>] <ref>... -- the chunk each reference names, two lines of context each way; the only CLI door onto a permalink
    1. every shape door.js resolves: permalink, path, partial, ticket code, pocket page, another registered repo
    2. one hunk per ref, in the order given; a miss names the repos searched, an ambiguity shows the chooser, neither ends the batch
  • bee cite <path>[?<rev>] -- the file with every anchored reference on it already quoted, one line above the landing and two below
    1. f.c:12 and the permalink f.c:12:aB alike, resolved through the same door see asks; a bare filename or a ticket code is left alone
    2. a miss, an ambiguity or a target quoted higher up adds nothing, so the page still reads as the file
  • bee cited <path> -- the mirror of cite: every reference ELSEWHERE that points at this file, quoted under the line it lands on
    1. the backlink rows only narrow the search -- each suspect is opened, lexed once and resolved in its own repo, so a same-named file in another tree quotes nothing
    2. the quote is the CITING lines, banded with the carrier's path and line, so its header opens the page that cited; a bare name or a ticket code sits in a block above the file
  • bee todo [TOPIC] [Key:Value]... -- the ticket board: <●> KEY ┄ title per open ticket, the row a click onto bee see KEY
    1. bare, the board of the context repo's todo/ -- topic headers with counts, Sub: families on dotted rails; when that repo (or, with no context, the local one) has no todo/, every registered repo's, newest first, each row prefixed by its repo
    2. TOPIC is one topic's list, Key:Value a filter -- keys AND, one key repeated ORs, Key:* is presence and Key: absence; Sev: paints the bullet CRIT red, HIGH orange, LOW dim
    3. open is Now: OPEN, or no Now: and no closed header mark; todo/done/ never lists and the answer comes off the BEE-024 lane, one query per repo per run
    4. a ticket that has a $SRC_ROOT worktree carries the two BEE-027 frames; a worktree whose tail is no ticket code lists in a trailing worktrees block
  • bee http [--port <n>] -- the same views over HTTP on localhost:8034, read-only, every registered repo, links to known files resolved and clickable
    1. every URL is /<repo>/<verb>/<arg> -- /<repo>/cat/<path> the file (a .md rendered), /<repo>/list/<dir>/ the browser; a verb-less /<repo>/<path> 301s to that form, and a segment that spells a verb (todo/) IS the verb (BEE-028)
    2. /<repo>/raw/<path> is the highlighted source,
    3. /<repo>/bytes/<path>[?<rev>] ships the file's raw bytes,
    4. submodule is served through its parent (/quickjab/cat/dog/abc/TCP.c), never under a name of its own
  • bee fork //repo-TKT-123 -- a ticket worktree of a registered repo at $SRC_ROOT/repo-TKT-123, on a branch named by the tail
    1. the split takes the LONGEST registry name followed by - (//bee-journal-BEE-023 is the journal's); the tail is free text, usually a ticket code
    2. every submodule the HEAD records gets a worktree of its own repo at the same relative path, detached at the recorded commit, recursively
    3. an existing branch is checked out, not re-created; the tree is not registered, and git worktree remove is how it goes away
  • bee wts -- the ticket worktrees under $SRC_ROOT, one line each: <name>-<tail> then the two board frames
    1. found by NAMING CONVENTION alone -- a dir <registry name>-<tail> holding a repo; git worktree list is never consulted and nothing is registered
    2. the file frame [v3 v1 v2 v5 !1] is the BEE-022 quad's column counts -- upstream, head, stage, worktree, conflicted, position authoritative
    3. the commit frame [o2 o1 a1b2c] is commits ahead, commits behind and the current tip
    4. nothing is stored: the bee todo board (BEE-025) is what spends these rows
  • bee install [<repo>] -- wire bee as the repo's git merge driver and its pre- and post-commit hooks, list it in ~/.config/bee/repos, index it
    1. a linked worktree is refused: a repo is known by its path (bee index redirects to the original instead)
    2. a submodule is an ordinary repo: each initialised one is registered, indexed and lindexed too
    3. the post-commit hook runs bee hook --post on the commit just made, quietly; git ignores its status, so it can never block or undo a commit
  • bee hook [<repo>] -- the pre-commit pass: fresh file:line(:col) refs in the staged text become file:line:hash permalinks, re-staged
    1. bee hook --post [<repo>] is the post-commit pass: index's three passes over the commit just made, quiet, and it never writes ~/.config/bee/repos -- install is the one verb that registers a repo
  • bee mint [--dry-run] <file>... -- the same upgrade for refs that ALREADY landed transient, in the working copy only; the hook cannot reach those
    1. the file list names the carriers: refs written in them, pointing anywhere; a target not listed must be clean, or it is refused
    2. everything left alone is reported, one line per ref -- unresolved, ambiguous, past the end, on a link cycle
  • bee merge <base> <ours> <theirs> [-o <out>] [-p <path>] -- CRDT 3-way file merge
    1. clean: merged bytes over <ours> (or <out>), exit 0
    2. conflict: markerless weave, exit 1
    3. binary/oversize: falls back to git merge-file
  • bee mark <file.mkd> -- that page rendered to ONE HTML document on stdout (.mkd, .md, .rst), the writing left to the shell
    1. a .mkd/.md/.rst link takes the .html name rendered beside it, so a dumped tree links up; every other destination rides verbatim
    2. the head links the site's own /assets/css/style.css, and the body carries no bee chrome
  • bee chat [dir] [outdir] -- Claude session logs to StrictMark pages, append-only reruns

Contributors

gritzko

116 commits

gritzko/bee

Beagle-bee, Beagle SCM niceties ported for git repos

5

stars

116

commits

JavaScript

primary language

Aug 26, 2026

updated

README

Bee: CRDT magic for git repos

syntax-highlighted source with permalinks in the comments

This project implements some of Beagle SCM's functionality for regular git repos. The work is ongoing; so far it can do:

  1. CRDT merges (use bee install to add a hook to a git repo),
  2. TUI code browser (use bee ./myfile to open),
  3. HTML code browser (use bee http --port 8888 to run, then open http://localhost:8888/myproject in a browser),
  4. token-level diffs (may start with bee log or http://localhost:8888/myproject/log then click around),
  5. permalink support, version-aware links that survive edits (e.g. index/perma.js:6:ur, see them in the pager/browser, bee see for CLI use, commit hook or be mint to create)

Reproducing Beagle's smooth worktree workflows has proven difficult, as git submodule and git worktree implementations are rather abrasive.

Build

Bundle with the quickjab runtime for a standalone binary. That carries dog/ (libdog), dog/abc/ (libabc) and test/ (the jab-shared API suite) as submodules:

cmake -S <quickjab> -B build -GNinja -DCMAKE_BUILD_TYPE=Release \
    -DJAB_JSRC=<js bundle tree> -DQUICKJAB_JSRC_PACK=ON -DJAB_BIN=bee
ninja -C build quickjab
ctest --test-dir build -R '^JSRC'

That leaves the binary at build/bin/bee and runs this tree's own test/*/run.sh suites against it, exactly as CI does (.github/build-bee.sh). Tagging X.Y.Z releases the same build: a source tarball plus bee for linux x86_64/arm64, macOS arm64 and a fully static linux x86_64.

Usage

The standalone (bundled) bee binary call patterns:

  • bee -- in a git repo: index it, open the list browser;
  • bee <path>... -- page files/dirs off the filesystem
  • bee //<name> <verb> [args] -- the CONTEXT slot (BEE-023): the verb runs in the repo <name>, and //<name>/<rel> is a path inside it wherever an arg is a path (bee cat //bee-journal/todo/BEE/BEE-023.mkd)
    1. <name> is a basename off ~/.config/bee/repos, else the directory $SRC_ROOT/<name> ($HOME/src by default) when a git repo or a linked worktree sits there
    2. a $SRC_ROOT hit is read-only for that run -- it is never registered; use bee install for that
    3. the slot is POSITIONAL and first: after a verb, //<name> is an argument like any other
    4. bare bee //<name> is the zero-arg story in that repo: index it, open the list browser
  • bee index [<repo>] -- build/refresh <repo>/.git/be/; lazy, fully derived; ALL THREE passes, the commit walk, the tip-blob link scan and the kv meta sweep, each off its own mark, all on the one summary line
    1. every initialized submodule is indexed too, depth-first; an uninitialised one is skipped in words
    2. inside a linked worktree it registers the original -- one repository is one line, and it says which
    3. the tail kv: N files, M pairs is the keyed lane (BEE-024): the .md/.mkd files this worktree carries and the live meta pairs they hold
  • bee lindex [<target>] -- backlink suspects; lazy, tip blobs only
    1. bare: bring the link rows up to the tip, this repo only
    2. <target>: the paths that may link to a file or a ticket code, looked up in every repo of ~/.config/bee/repos (read-only) and printed repo-qualified, the local repo first
  • bee sym [--paths] <ident> [.ext|<dir>]... -- where a symbol is mentioned (BEE-063, BEE-066); one prefix scan of the SYM rows the tip-blob pass mints
    1. every hit is a canonical hunk: uri the address <path>:<line>, text the raw mention line with a line of context either side, the file's own tags with the mention marked; two windows merge where they overlap
    2. the rows are suspects and store no positions, so each file is OPENED and one that no longer carries the symbol prints nothing at all
    3. the bytes are the worktree file when there is one, else the tip blob; a binary or over-cap blob prints its path bare
    4. every repo of ~/.config/bee/repos answers (read-only), the local first
    5. the symbol is hashed VERBATIM, so u8bFeed and U8BFeed are two queries
    6. past ~200 suspects a repo prints the count and asks for a narrower query
    7. a further word narrows: .c an extension, dog/abc/ a SEGMENT-ALIGNED path run at any depth (never catalog/abc); alike words OR, the two kinds AND, and a path run prunes the rows before the cap is weighed
    8. --paths: one repo-qualified path per line and no file opened, for scripts
  • bee log [<n>] [<hex>|<path>][?<rev>] -- commit log, newest first; off-mainline rows grey
    1. bare: the checked-out tip's ancestry; <n> caps the rows, 0 = all
    2. <hex>: that commit's ancestry
    3. <path>: the file's revisions
    4. ?<rev>: a branch, tag or hexlet names the tip -- log ?main, log f.c?v2
    5. <sub>/<path>: the submodule's own history, its rows opening in the sub
    6. a ticket code in a summary is a link, like any other reference
  • bee commit [<hex>] -- one commit, header + message; tree/parent hashes are links
  • bee diff [<hex> [<hex>]|<path>] -- the token-level diff: one weave per path, seeded at the two revisions' merge base, every token naming its own commit
    1. bare: worktree vs HEAD
    2. <hex>: that commit vs its first parent
    3. <hex> <hex>: any two revisions, rooted at their merge base
    4. <path>: that file/dir only
  • bee status — the quad: one row per path that is not quiet, four chars wide, upstream HEAD index worktree, . same, x removed, o created, v advanced, ! conflicted; position is authoritative
  • bee list [<path>][?<rev>] -- dir browser: marker, name, last commit, age
  • bee cat <path>[?<rev>] -- the file's own bytes, syntax-painted
  • bee tree [<hex>|<path>][?<rev>] -- raw git-tree rows, .. row when descended
  • bee blob <hexlet> -- a blob by object name
  • bee see [-C<n>] <ref>... -- the chunk each reference names, two lines of context each way; the only CLI door onto a permalink
    1. every shape door.js resolves: permalink, path, partial, ticket code, pocket page, another registered repo
    2. one hunk per ref, in the order given; a miss names the repos searched, an ambiguity shows the chooser, neither ends the batch
  • bee cite <path>[?<rev>] -- the file with every anchored reference on it already quoted, one line above the landing and two below
    1. f.c:12 and the permalink f.c:12:aB alike, resolved through the same door see asks; a bare filename or a ticket code is left alone
    2. a miss, an ambiguity or a target quoted higher up adds nothing, so the page still reads as the file
  • bee cited <path> -- the mirror of cite: every reference ELSEWHERE that points at this file, quoted under the line it lands on
    1. the backlink rows only narrow the search -- each suspect is opened, lexed once and resolved in its own repo, so a same-named file in another tree quotes nothing
    2. the quote is the CITING lines, banded with the carrier's path and line, so its header opens the page that cited; a bare name or a ticket code sits in a block above the file
  • bee todo [TOPIC] [Key:Value]... -- the ticket board: <●> KEY ┄ title per open ticket, the row a click onto bee see KEY
    1. bare, the board of the context repo's todo/ -- topic headers with counts, Sub: families on dotted rails; when that repo (or, with no context, the local one) has no todo/, every registered repo's, newest first, each row prefixed by its repo
    2. TOPIC is one topic's list, Key:Value a filter -- keys AND, one key repeated ORs, Key:* is presence and Key: absence; Sev: paints the bullet CRIT red, HIGH orange, LOW dim
    3. open is Now: OPEN, or no Now: and no closed header mark; todo/done/ never lists and the answer comes off the BEE-024 lane, one query per repo per run
    4. a ticket that has a $SRC_ROOT worktree carries the two BEE-027 frames; a worktree whose tail is no ticket code lists in a trailing worktrees block
  • bee http [--port <n>] -- the same views over HTTP on localhost:8034, read-only, every registered repo, links to known files resolved and clickable
    1. every URL is /<repo>/<verb>/<arg> -- /<repo>/cat/<path> the file (a .md rendered), /<repo>/list/<dir>/ the browser; a verb-less /<repo>/<path> 301s to that form, and a segment that spells a verb (todo/) IS the verb (BEE-028)
    2. /<repo>/raw/<path> is the highlighted source,
    3. /<repo>/bytes/<path>[?<rev>] ships the file's raw bytes,
    4. submodule is served through its parent (/quickjab/cat/dog/abc/TCP.c), never under a name of its own
  • bee fork //repo-TKT-123 -- a ticket worktree of a registered repo at $SRC_ROOT/repo-TKT-123, on a branch named by the tail
    1. the split takes the LONGEST registry name followed by - (//bee-journal-BEE-023 is the journal's); the tail is free text, usually a ticket code
    2. every submodule the HEAD records gets a worktree of its own repo at the same relative path, detached at the recorded commit, recursively
    3. an existing branch is checked out, not re-created; the tree is not registered, and git worktree remove is how it goes away
  • bee wts -- the ticket worktrees under $SRC_ROOT, one line each: <name>-<tail> then the two board frames
    1. found by NAMING CONVENTION alone -- a dir <registry name>-<tail> holding a repo; git worktree list is never consulted and nothing is registered
    2. the file frame [v3 v1 v2 v5 !1] is the BEE-022 quad's column counts -- upstream, head, stage, worktree, conflicted, position authoritative
    3. the commit frame [o2 o1 a1b2c] is commits ahead, commits behind and the current tip
    4. nothing is stored: the bee todo board (BEE-025) is what spends these rows
  • bee install [<repo>] -- wire bee as the repo's git merge driver and its pre- and post-commit hooks, list it in ~/.config/bee/repos, index it
    1. a linked worktree is refused: a repo is known by its path (bee index redirects to the original instead)
    2. a submodule is an ordinary repo: each initialised one is registered, indexed and lindexed too
    3. the post-commit hook runs bee hook --post on the commit just made, quietly; git ignores its status, so it can never block or undo a commit
  • bee hook [<repo>] -- the pre-commit pass: fresh file:line(:col) refs in the staged text become file:line:hash permalinks, re-staged
    1. bee hook --post [<repo>] is the post-commit pass: index's three passes over the commit just made, quiet, and it never writes ~/.config/bee/repos -- install is the one verb that registers a repo
  • bee mint [--dry-run] <file>... -- the same upgrade for refs that ALREADY landed transient, in the working copy only; the hook cannot reach those
    1. the file list names the carriers: refs written in them, pointing anywhere; a target not listed must be clean, or it is refused
    2. everything left alone is reported, one line per ref -- unresolved, ambiguous, past the end, on a link cycle
  • bee merge <base> <ours> <theirs> [-o <out>] [-p <path>] -- CRDT 3-way file merge
    1. clean: merged bytes over <ours> (or <out>), exit 0
    2. conflict: markerless weave, exit 1
    3. binary/oversize: falls back to git merge-file
  • bee mark <file.mkd> -- that page rendered to ONE HTML document on stdout (.mkd, .md, .rst), the writing left to the shell
    1. a .mkd/.md/.rst link takes the .html name rendered beside it, so a dumped tree links up; every other destination rides verbatim
    2. the head links the site's own /assets/css/style.css, and the body carries no bee chrome
  • bee chat [dir] [outdir] -- Claude session logs to StrictMark pages, append-only reruns

See what people are saying

Contributors

gritzko

116 commits

Languages

JavaScript

62.3%

Shell

37.5%