Capture a half-formed thought in under five seconds. Trace where any idea came from years later.
An idea arrives vague, at random, in whatever field you happen to be thinking about. Nebula gives it a home before it is good enough for anywhere else, then records how it descended into whatever it became. Ideas branch, merge and die, so the structure is a directed acyclic graph rather than a tree, and dead branches are kept forever because they are what stops you re-treading ground.
The v0.2 model is in docs/design/v0.2/1_spec.md; docs/spec.md is the original design record, kept as history.
This repository holds the tool only. It never holds the corpus.
The corpus spans work and personal thinking across unrelated fields, so it lives outside, configured by path, and it stays private regardless of where this repository ends up. Keeping them apart means this code can be published without a decision about the notes, and the notes can be backed up without dragging a build tree along.
Every command finds the corpus through --root, else $NEBULA_ROOT, else the
nearest corpus at or above the current directory (holding nodes/ plus positive
corpus evidence: a config.yaml with a top-level schema_version or
corpus_id key, an unparseable or unreadable config, or legacy inbox/ or
.lock markers; opening refuses a damaged or missing config rather than falling
back, while a lone nodes/ falls through), else the path in
~/.config/nebula/root, else ~/.nebula. Only an existing corpus is found from
the current directory, so neb capture can create one only at an explicit or
configured root, and it says so when it does.
ORBIT_TASK_ID and ORBIT_RUN_ID supply missing provenance for new,
promote, cite and handoff; explicit --task and --run values win.
When ORBIT_RUN_ID is nonempty, writes of new words require an explicit
--by, and sharpen --confirm is reserved for a human outside the run.
Set NEBULA_READ_ONLY=1 to refuse all corpus writes while keeping reads
available. An empty or unset value leaves writes enabled; other values are
refused.
nebula = the CLI, checker and desktop app (this repo)
corpus = nodes/ and inbox/ (elsewhere, private)
Nodes carry free-form tags, normalised to lowercase kebab-case on every
write. There is no declared list; a write that introduces a tag differing from
one in use only by case or a trailing s goes through with a note on stderr
(note: tag physic is close to physics (2 nodes)), and neb check warns on
any such pair, naming the nodes that carry each. Use a second corpus only for a second owner, which
is what keeps work and personal material apart.
neb new "Shear law from scarcity" --tag principia --kill "..."
neb tag shear-law-from-scarcity --add orrery
neb tag list
neb list --tag principia --tag orrery
neb status shear-law-from-scarcity refuted --why "..."
neb completions zsh > ~/.zfunc/_nebNew title-derived IDs are slugged after Unicode NFC normalization: Café
and Café produce the same café ID. Capture duplicate comparisons also
use NFC. Existing IDs and stored titles are left as written; there is no ID
migration, and references to existing IDs continue to work.
A corpus written by v0.1 is brought forward in place with neb migrate.
neb --help lists the shipped verbs. Read commands include show, list,
inbox, near, trace, impact, graph, log, review and check; writes
include capture, promote, drop, new, edit, sharpen, status,
link, tag, note, cite and handoff. Use neb review --short for the
quick glance; neb open is a deprecated alias. Put --no-commit after a
write verb; the before-verb form is deprecated.
Stdout holds the result alone. List-shaped text is an aligned table on a
terminal and headerless TSV when piped; notices and commit hashes go to
stderr. Typed refusals under --json emit {error, code, hint} on stderr;
clap parser errors stay prose. Exit 2 means
an argument no corpus could accept (including clap errors); exit 1 means a
state refusal or an error-level check finding; exit 0 means success.
Unsafe node or inbox IDs such as ../x are unsafe_id argument refusals
(exit 2), on both reads and writes. near accepts arbitrary search text as
well as existing IDs; text that is not an existing ID remains a search query.
not_regular_file, locked, io_at and unknown_revision are state
refusals. The skill's verb reference
lists command flags, JSON shapes and error codes.
With neb config commit on, a changed corpus write commits only corpus
paths, leaving unrelated staged work alone. If no git work tree contains the
corpus, the write succeeds and stderr says it was not committed. If git
refuses a commit, the write remains on disk; resolve the git problem before
making another corpus change.
v0.2: the reduced model — four statuses, five edge kinds, references with notes, tags, and a checker-enforced invariant set. Everything past that is a guess until roughly fifty real nodes exist.
neb is the CLI documented above. The agent skill in
skills/nebula/ teaches a session-directed or
unattended agent the verbs, their --json shapes, the invariants and the two
operating modes — make skill-link symlinks it into ~/.claude/skills. A
desktop app draws the whole corpus as a graph and captures from a global
shortcut on the same nebula-core, as documented in
docs/design/v0.2/. All three read and write the same nodes/ and
inbox/ — there is one corpus underneath, never three.
To run the core and CLI tests without building the desktop app:
cargo test -p nebula-core -p neb --all-targetsmake hostile-env-test runs the same suites under a hostile HOME, with
TMPDIR inside a git repository and with GIT_DIR exported, and checks that
no test touched the host; it includes the desktop crate where WebKitGTK is
installed.
make test runs cargo test --workspace --all-targets, which includes the
Tauri desktop crate. On Linux, building that crate needs the desktop system
development libraries, including GTK, WebKitGTK, and D-Bus.