nn-guide

star 0

Complete reference for nn commands, flags, note types, link types, and LLM usage patterns.

jaresty By jaresty schedule Updated 6/12/2026

name: nn-guide description: Complete reference for nn commands, flags, note types, link types, and LLM usage patterns. when_to_use: When you need to look up an nn command, flag, or usage pattern. Invoke with /nn-guide.

nn-guide

Reference for nn commands, flags, and LLM usage patterns.

Global flags (all commands)

--json          Machine-readable JSON output
--no-color      Disable ANSI color
-q, --quiet     Suppress progress/info output
--notebook      Select a non-default notebook (name from config)

nn new

Create a new note.

nn new --title TEXT --type TYPE [--tags TEXT] [--content TEXT] [--no-edit]
       [--link-to ID --annotation TEXT]
       [--from-stdin]
       [--from-file PATH]
       [--expires YYYY-MM-DD]
       [--expires-when "condition text"]
  • --type is required: concept | argument | model | hypothesis | observation | question | protocol
  • --no-edit skips $EDITOR launch (always use in non-TTY/LLM context)
  • --content TEXT sets the note body directly
  • --from-stdin reads the note body from stdin
  • --from-file PATH scaffolds the note body from nn ast output for a source file (sets title to filename if not given)

Choosing a type

The five types cover the epistemic roles a note can play (after Ahrens, How to Take Smart Notes):

Type Use when the note… Example title
concept defines or explains a single idea, term, or principle "The Atomicity Principle"
argument makes a claim and supports it with reasoning "Atomicity enables reuse across contexts"
model describes a framework, pattern, or mental model "The Zettelkasten as a second brain"
hypothesis states an untested conjecture worth investigating "Dense linking predicts note longevity"
observation records a concrete fact, datum, or empirical finding "Luhmann produced 90,000 notes over 40 years"
question poses an open question that the graph should eventually answer "Why did Luhmann avoid hierarchical folders?"
protocol specifies an imperative procedure the LLM should follow in this notebook "When creating a hypothesis, immediately link it to its source observation"

Decision heuristic: if you're not sure, ask — is this a definition (concept), a claim with support (argument), a framework (model), a guess to test (hypothesis), something I witnessed/measured (observation), an open question (question), or an operating instruction for the LLM (protocol)? If none fit cleanly, the note may not be atomic yet.

nn show

Print note content to stdout. Accepts a full ID or a title substring.

nn show <id-or-title> [--depth N] [--json]
nn show --linked-from <id>
nn show --global

--global shows all global protocol notes (type:protocol with no outgoing governs links) in one command, each with the derivation instruction appended. Replaces the two-step nn list --global --json + nn show <id> pattern. Also appends a ## Reminders block listing the body of any non-expired notes tagged reminder.

If the query doesn't match an ID exactly, nn searches note titles case-insensitively. If multiple titles match, the command lists the candidates and exits with an error — use the full ID to disambiguate.

Graph neighborhood in plain-text output: nn show <id> now includes two navigation aids in its plain-text output:

  • Resolved link titles: the ## Links section renders each outgoing link as [[ID|Title]] instead of bare [[ID]], so target note titles are immediately readable without a separate lookup.
  • Backlinks section: a ## Backlinks (N) block is appended listing all notes that link to the shown note, each with its annotation. This replaces the need for a separate nn backlinks <id> call when exploring the local graph.

These additions appear only in plain-text output (--json is unaffected).

--depth N traverses outgoing links from the given note up to N hops, collecting all reachable notes and printing them as a single concatenated Markdown document separated by ---. Useful for loading a coherent subgraph as context for an LLM.

nn show <id> --depth 2                 # root + 2 hops of outgoing links
nn show <id> --depth 1 --json          # JSON array with depth field per note

--json with --depth returns an array of note objects in BFS order, each with an added depth field (0 = origin note, 1 = direct links, etc.).

nn list

List and filter notes.

nn list [--tag TEXT] [--type TYPE] [--status STATUS]
        [--linked-from ID] [--linked-to ID] [--orphan] [--global] [--long]
        [--has-url] [--url-contains STRING]
        [--search TEXT] [--similar ID] [--sort FIELD] [--limit N] [--json]

--search TEXT performs a ranked case-insensitive search across note title and body. Title matches rank above body matches. Notes with more inbound backlinks receive a log-scale centrality boost on top of BM25, so well-linked notes surface above equal-content orphans. The score field in --json output reflects both BM25 relevance and centrality.

--similar ID ranks all notes by BM25 similarity to the given note's title and body, excluding the note itself. Use for serendipitous discovery — find notes that share vocabulary with a given note but have no explicit link. Composes with --status, --tag, --type, --limit, --json. When --similar is active, --sort is ignored (similarity ranking takes precedence).

nn list --similar <id>                 # notes most similar to <id>
nn list --similar <id> --limit 5       # top 5 most similar
nn list --similar <id> --status permanent --json

--sort FIELD sorts results: title (alphabetical), modified (most-recently-modified first), created (default, most-recently-created first). Applied after filtering and ranking. Ignored when --similar is active.

--global returns protocol notes with no outgoing governs links — protocols that apply universally to the entire notebook rather than governing specific notes. Distinct from --orphan: a global protocol is intentionally universal, not forgotten.

--long filters to notes whose body exceeds the atomicity threshold (2000 chars). Use to find notes that have grown too large to split.

--has-url filters to notes containing at least one http:// or https:// URL. Use to find notes with external references.

--url-contains STRING filters to notes containing a URL that includes the given string. Only matches within actual URLs (requires http:// or https:// prefix) — bare text occurrences are ignored.

nn list --has-url                          # all notes with any URL
nn list --url-contains "github.com"        # notes linking to GitHub
nn list --has-url --search "auth"          # URL-containing notes matching "auth"

--older-than N filters to notes not modified in the last N days (age-based staleness). Uses the same thresholds as nn show freshness: 3 days = aging boundary, 14 days = stale boundary.

nn list --older-than 14            # notes not touched in 14+ days (stale tier)
nn list --older-than 3             # notes not touched in 3+ days (aging + stale)
nn list --older-than 14 --type concept --json

--expired filters to notes with an expires date set and in the past. Use to find notes marked for deletion.

--has-expires filters to notes with an expires date set (any date, past or future). Use to see all time-bounded notes.

Expiration fields: Notes support two complementary expiry mechanisms:

  • expires: YYYY-MM-DD — date-based; automated by --expired filter and review
  • expires_when: "condition" — semantic condition (e.g. "when the auth PR is merged"); surfaces in nn review under Pending conditions as a checklist for the reviewer to evaluate

nn review also surfaces Expiry candidates: observation notes older than 30 days with no expiry set and not permanent — use to identify notes that should have been time-bounded.

nn list --expired                  # notes past their expiration date
nn list --has-expires              # all notes with an expiration date
nn list --has-expires --json       # machine-readable expiring notes

--no-inbound filters to notes with zero inbound links. Stricter than --orphan (zero links in either direction) — use to find notes nothing references that still have outbound links.

--unactioned filters to notes that were accessed via nn show but have had no git commit touching their file since the last access. Advisory — requires access.log. Use to surface notes the LLM read but never updated. (Previously named --stale.)

Filters compose: nn list --search "implicit" --type concept --sort modified works as expected.

nn update-link / nn bulk-update-link

nn update-link <from-id> <to-id> [--annotation TEXT] [--type TYPE] [--status draft|reviewed]
nn bulk-update-link <from-id> --to <id> [--type TYPE] [--annotation TEXT] [--status draft|reviewed] [--to <id> ...]

Update annotation, type, and/or status of existing links in place — no unlink/relink needed. At least one change flag is required. Only specified fields are modified; unspecified fields are preserved.

--status reviewed signs off a draft link as human-endorsed. Use after verifying LLM-suggested links.

nn bulk-update-link applies all updates in a single git commit. --type and --annotation are paired with --to by position; if provided, their counts must match --to. --status applies to all --to targets.

nn link / nn unlink / nn bulk-link

nn link <from-id-or-title> <to-id-or-title> --annotation "relationship description" --type TYPE [--status draft|reviewed]
nn unlink <from-id-or-title> <to-id-or-title> [--type TYPE]
nn bulk-link <from-id> --to <id> --annotation "..." --type TYPE [--status draft|reviewed] [--to <id> --annotation "..." --type TYPE]...

nn link and nn unlink accept title substrings for both arguments. nn bulk-link requires IDs.

nn unlink --type TYPE removes only edges of that type between the pair; without --type, all edges between the pair are removed. Multiple typed edges between the same pair are allowed (uniqueness is (from, to, type)).

[[id]] inline references are presentational only. Writing [[20260423-1234]] in a note's prose body does not create a graph edge — it is not parsed by nn graph, nn backlinks, nn path, or nn links. Use nn link to create edges. This is intentional: the link graph is the authoritative record of relationships, not the prose.

Both --annotation and --type are required. A bare link is a schema violation.

Canonical types: refines, contradicts, source-of, extends, supports, questions, governs.

--status defaults to draft. Pass --status reviewed when a human is explicitly creating and endorsing the link at creation time.

nn bulk-link creates all links in a single git commit. --to, --annotation, and --type are paired by position; counts must match. --status applies to all targets.

nn graph

nn graph [--json]

JSON output: { "nodes": [...], "edges": [...] }

nn graph show (LLM-facing subgraph)

nn graph show --focus <id> [--depth N] [--format text|json]

Renders a subgraph centered on <id>. BFS depth defaults to 2. Use --format json for structured output. Prefer this over nn graph when exploring a note's neighborhood — it scopes the result to the relevant region rather than exporting the whole graph.

nn status

nn status [--json] [--hubs N]

Reports: total notes, orphan count (with IDs/titles), draft count, broken links, draft link count, long notes, hub notes.

  • long notes: notes whose body exceeds 2000 chars — candidates for splitting. Section omitted when none exist.
  • hub notes: top N notes by link degree (inbound + outbound). Only shown when notebook has ≥10 notes. --hubs N overrides the default of 5.
  • draft links: count of links with status: draft — links not yet human-endorsed.

--json output adds: "draft_links": N, "long_notes": [{"id": "...", "title": "...", "body_len": N}], "hub_notes": [{"id": "...", "title": "...", "degree": N}]

nn links

nn links <id> [--type TYPE] [--status draft|reviewed] [--json]

Lists outgoing links from a note with their annotations. --type filters by relationship type. --status filters by link status.

Link status: draft (default for new links — not yet human-endorsed), reviewed (human has verified the relationship). Legacy links without a status field are treated as reviewed for backward compatibility.

Text output: one entry per link — targetID title {status}\n [type] annotation (status and type shown when present)

--json output: [{"id": "...", "title": "...", "annotation": "...", "type": "...", "status": "..."}]

Triage draft links: nn links <id> --status draft shows only unreviewed links for a specific note.

nn path

nn path <id-a> <id-b> [--json]

Find and print the shortest undirected path between two notes via the link graph (BFS). Returns an error when no path exists.

Text output: each note on its own line with an separator between hops.

--json output: [{"id": "...", "title": "..."}] — ordered path from A to B.

nn clusters

nn clusters [--min N] [--singletons] [--json]

Detect topological clusters of notes using label propagation. Each note starts with its own label and iteratively adopts the most common label among its linked neighbours.

  • --min N omits clusters smaller than N notes (default: 2). Notes with no links are singletons and omitted by default.
  • --singletons includes singleton clusters (notes with no links).

Text output: one cluster per block — cluster N (K notes):\n ID Title\n ...

--json output: [{"notes": [{"id": "...", "title": "..."}]}]

nn ast

nn ast <file> [--json] [--trace] [--root DIR]

Print a compact structural outline of a source file (imports, types, functions, constants). Uses gotreesitter (pure Go) to parse the file.

Supported languages: Go, Python, JavaScript, TypeScript, Rust, Java.

Text output:

file: src/backend/gitlocal.go  language: go
imports: fmt, os, path/filepath, ...
type Backend struct {
func (b *Backend) Write(n *note.Note) error {
...

--json output: [{"kind": "...", "name": "...", "signature": "...", "line": N}]

--trace searches for name-match references to every symbol in the outline across the codebase rooted at --root (default: .). Emits one references to "X" section per symbol. Explicitly name-match only — not symbol-resolved, may include false positives.

nn ast src/backend/gitlocal.go --trace --root ./

nn update

nn update <id-or-title> [--title TEXT] [--tags TEXT] [--tags-add TAG] [--tags-remove TAG]
         [--content TEXT] [--stdin] [--append TEXT] [--replace-section HEADING]
         [--type TYPE] [--status STATUS] [--no-edit]

Accepts a note ID or a title substring — if the substring matches exactly one note it is used; multiple matches returns an error. At least one change flag is required. --content/--stdin/--append are mutually exclusive.

Flag Effect
--title TEXT Replace note title
--tags TEXT Replace all tags (comma-separated)
--tags-add TAG Add a tag without touching others (repeatable)
--tags-remove TAG Remove a tag without touching others (repeatable)
--content TEXT Replace note body entirely
--stdin Read replacement body from stdin (heredoc-safe, no shell escaping)
--replace-section HEADING Replace only the named ## Heading section; requires --content or --stdin; errors if heading not found
--append TEXT Append text to note body (double-newline separator)
--status STATUS Set note status: draft, reviewed, or permanent
--expires YYYY-MM-DD Set expiration date; note appears in nn list --expired after this date
--expires-when TEXT Set conditional expiration (plain text condition, e.g. "when the PR is merged")
--no-edit Skip $EDITOR (always use in non-TTY/LLM context)
--since RFC3339 Required. Reject update if note was modified after this timestamp; read modified: from nn show output. Omitting returns an error.

Preferred LLM patterns:

# Update by title substring (no ID lookup needed)
nn update "my note title" --content "new body" --no-edit

# Multiline body without shell escaping
nn update <id-or-title> --stdin --no-edit <<'EOF'
Body with `backticks`, "quotes", and $pecial chars — no escaping needed.
EOF

# Replace a single section, preserve the rest
nn update <id-or-title> --replace-section "Why" --content "New explanation." --no-edit

# Additive tag operations
nn update <id-or-title> --tags-add "zettelkasten" --tags-remove "inbox" --no-edit

# Demote or reset status
nn update <id-or-title> --status draft --no-edit

nn remind

Create a temporary reminder note that surfaces in nn show --global until its expiration date.

nn remind "TEXT" [--for N] [--expires YYYY-MM-DD]
  • Creates an observation note tagged reminder with permanent status
  • Title = first 60 characters of TEXT; body = full TEXT
  • Default expiry: today + 1 day (use --for N for N days, or --expires DATE for a specific date)
  • Appears in the ## Reminders block of nn show --global until expired
  • Expired reminders are silently omitted from --global output
nn remind "Don't merge the auth PR until legal signs off"        # expires tomorrow
nn remind "Check in with mobile team" --for 3                    # expires in 3 days
nn remind "Hold off on deploys" --expires 2026-06-01             # expires on date

nn promote

nn promote <id-or-title> --to reviewed|permanent

Status progression: draftreviewedpermanent. Accepts title substring. For direct status assignment (including demotion), prefer nn update --status.

nn log

Show the git diff history for a single note — all commits that touched its file, with full patch output.

nn log <id-or-title> [--since DATE]
Flag Effect
--since DATE Limit history to commits after this date (e.g. 2025-01-01); passed directly to git log --since=

Output is raw git log -p --follow for the note's filename. Use to audit what changed and when.

nn log <id>                         # full diff history
nn log <id> --since 2026-01-01      # only changes since Jan 2026

nn delete

nn delete <id-or-title> --confirm
nn delete --from-stdin --confirm

--confirm is required. Warns if other notes link to the deleted note.

--from-stdin reads note IDs line-by-line from stdin and deletes each. Compose with nn list for batch deletion:

nn list --no-inbound --status draft --json | jq -r '.[].id' | nn delete --from-stdin --confirm

nn capture

Quickly capture raw material (articles, quotes, observations) as a draft note.

nn capture --title TEXT [--content TEXT] [--type TYPE] [--tags TEXT]
  • Default type: observation. Override with --type concept|argument|...
  • Status is always draft — the note is intended for LLM refinement
  • Prints the created note ID to stdout

Typical flow:

nn capture --title "..." --content "..." # capture → get ID
nn update <id> --content "..." --no-edit  # LLM refines
nn suggest-links <id>                     # discover links
nn suggest-tags <id>                      # discover tags
nn tags                                   # enumerate tag vocabulary

nn suggest-links

Format context for LLM-driven link suggestion. Does not call an LLM — emits a structured block for the LLM session to reason over.

nn suggest-links <id> [--limit N] [--format json]

Output contains:

  • ## Focal note — full body of the focal note
  • ## Candidate notes (N total, M excluded — no term overlap) — BM25-ranked candidates, each with tags and a 200-char summary
  • Notes already linked to the focal note are marked (already linked: <type>)
  • Zero-BM25-score notes are excluded; the count is reported in the header

Default limit: 20. The LLM reads the output and calls nn link or nn bulk-link for accepted suggestions.

nn suggest-tags

Returns tag suggestions for a note based on BM25-similar notes that share tags the target lacks.

nn suggest-tags <id> [--json] [--min-notes N]

Only tags appearing in ≥ --min-notes similar notes (default: 2) are returned. The LLM applies accepted tags via nn update <id> --tags "...".

nn tags

Lists all tags in the notebook with note counts.

nn tags [--json]

JSON output: [{"tag": "...", "count": N, "notes": ["id1", ...]}]. Use before tagging a new note to orient against the existing vocabulary.

nn review

Notebook health report formatted for LLM-driven analysis.

nn review [--format json]

Sections:

  • Growth: total notes, by type, created in last 7/30 days
  • Connectivity: total links, avg links per note, orphan count + IDs, dead-end count + IDs
  • Structural gaps: draft note count + IDs, long note count + IDs (body > 2000 bytes)
  • Aging notes: notes not modified recently, split into two buckets using the same thresholds as nn show freshness — aging (3–14 days) and stale (>14 days), sorted oldest-first within each bucket
  • Friction candidates: unreviewed observation notes tagged friction-candidate
  • Protocol telemetry: protocol session-presence counts from protocol-presence.log
  • Note access: note view counts from access.log

A "dead-end" note has outbound links but no inbound links — it contributes to others but nothing points back to it.

Long notes (body exceeds 2000 bytes) are listed under Structural gaps — candidates for splitting into atomic notes.

Aging notes surface content that may be stale. Notes in the stale (>14 days) bucket should be verified before relying on them; notes in aging (3–14 days) may need a recheck. This mirrors the freshness: line injected by nn show.

--format json keys: growth, connectivity, drafts, long_notes, aging_notes, stale_notes, friction_candidates, protocol_telemetry, note_access

nn gap

Format topic neighborhood context for LLM gap analysis.

nn gap <topic> [--limit N] [--depth N] [--format json]

Loads notes matching <topic> via BM25 search, then expands to their direct neighbors (depth 1 by default) via forward links and backlinks. Output:

  • ## Topic notes (N matching "topic") — ranked by BM25 score
  • ## Neighborhood (N notes, depth D) — linked neighbors not in topic set

The LLM receiving this output identifies what is thoroughly covered, what is shallow, what is absent, and what questions the notes raise but do not answer.

Default limit: 20. Default depth: 1.

nn index

Format topic cluster context for LLM-driven Map of Content creation.

nn index <topic> [--limit N] [--format json]

Loads notes matching <topic> via BM25, then groups them into clusters using link-based label propagation (scoped to the topic subset). Output:

  • ## Topic: "topic" (N notes) — all matching notes with summaries
  • ## Clusters (N) — grouped by link connectivity

The LLM names the clusters, identifies tensions and gaps, and creates an index note via nn new.

Default limit: 30. --format json keys: topic_notes, clusters

nn random

Return a randomly selected note. Optionally filtered.

nn random [--tag TEXT] [--type TYPE] [--status STATUS] [--json]

Returns one note at random from the notebook. All filters from nn list are supported. Use for deliberate serendipity — re-encounter a forgotten note and consider whether it connects to current work.

nn random                         # any note
nn random --status permanent      # a random permanent note
nn random --tag philosophy --json

nn install-skills

nn install-skills [--dest DIR] [--list]

Copies skill directories into ~/.claude/skills/ (or --dest).

Configuration

~/.config/nn/config.toml:

[notebooks]
default = "personal"

[notebooks.personal]
path = "~/notes"
backend = "gitlocal"

Environment overrides:

  • NN_NOTEBOOK — select a named notebook (overrides default)
  • NN_CONFIG_DIR — override config directory (useful for testing)

Note schema

---
id: 20260411120045-3821
title: "The Atomicity Principle"
type: concept
status: draft
tags: [zettelkasten, methodology]
created: 2026-04-11T12:00:45Z
modified: 2026-04-11T12:05:00Z
---

Body text.

## Links

- [[20260411090000-1234]] [extends] {draft} — provides the foundational philosophy this principle implements

Link format in plain-text show output: - [[target-id|Target Title]] [type] {status} — annotation (titles resolved at render time). In the raw file on disk, the format remains - [[target-id]] [type] {status} — annotation.

  • [type] optional: refines, contradicts, source-of, extends, supports, questions, governs
  • {status} optional: draft (default for new links), reviewed (human-endorsed). Absent = reviewed (legacy compat).

LLM usage patterns

Create and link in sequence:

nn new --title "Concept A" --type concept --content "..." --no-edit
# note ID from output: 20260411120045-0001
nn list --json | jq '.[].id'   # find related note IDs
nn link 20260411120045-0001 <related-id> --annotation "extends this concept" --type extends

Load all global protocols at session start:

nn show --global

Find orphans before a review session:

nn list --orphan --json

Export graph for visualisation:

nn graph --json > graph.json

Discover related notes (no known query):

nn list --similar <id> --limit 10

Load a topic cluster as LLM context:

nn show <id> --depth 2

Explore a note's immediate graph neighborhood (single round-trip):

nn show <id>     # shows body + resolved outgoing links + backlinks in one call

Serendipitous re-encounter:

nn random --status permanent
Install via CLI
npx skills add https://github.com/jaresty/nn --skill nn-guide
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator