- JavaScript 99.4%
- CSS 0.6%
| .vscode | ||
| docs | ||
| media | ||
| src | ||
| .gitignore | ||
| .vscodeignore | ||
| CHANGELOG.md | ||
| jsconfig.json | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
zettelmesh for VS Code
Configure a zettelmesh workspace from VS Code settings, and run its two editor integrations:
- Language Server (
zettel serve lsp) — title-first link completion (bothlinks:frontmatter and inline[phrase](zettel:…)body links), hover, go-to-definition, backlinks ("Find All References"), workspace symbols (Ctrl/Cmd+Tfinds notes by title), document outline / breadcrumb (shows the note title even for a ULID-named file), titled inlay hints for link staleness, frontmatter diagnostics, and a "Create note" quick fix for dangling links. Works on Markdown files. - MCP server (
zettel serve mcp) — exposed to VS Code's Model Context Protocol integration so chat/agents can search the mesh, read notes, and follow links. Off by default.
The extension does not bundle zettel; it drives the CLI you already
have installed.
Prerequisites
-
VS Code 1.101 or newer (for the MCP integration).
-
The
zettelCLI on yourPATH. It is not on PyPI — install it from the zettelmesh repo, e.g.uv tool install "zettel[openai,tui] @ git+ssh://git@code.hans-aschauer.de:222/hans/zettelmesh.git" uv tool update-shellIf it is not on
PATH, setzettelmesh.zettelPathto an absolute path or a wrapper script — e.g. one containingexec uv run --project /path/to/zettelmesh zettel "$@", which is the usual answer when you work from a zettelmesh checkout. -
A configured
zettelinstallation. The extension uses the CLI's ownzettel.toml, optional siblingzettel_secrets.toml, and environment variable precedence. It does not require a VS Code-specific workspace layout.Creating one, and everything else about setting
zettelup, is covered indocs/getting-started.mdin the zettelmesh repo. There is also a ready-made example workspace you can clone to try things out; run itssetup.shonce to renderconfig/registry.yaml.
Installing
The extension is not on the Marketplace — build the .vsix from this
checkout:
git clone ssh://git@code.hans-aschauer.de:222/hans/zettelmesh-vscode.git
cd zettelmesh-vscode
npm install
npm run package # -> zettelmesh-vscode-0.1.0.vsix
code --install-extension zettelmesh-vscode-0.1.0.vsix
There is no build step — the extension is plain CommonJS JavaScript in
src/, so vsce package just bundles it.
The CLI configuration is used automatically. To select a non-default file, set its path explicitly:
{
"zettelmesh.configFile": "~/my-workspace/zettel.toml",
"zettelmesh.mcp.enabled": true // optional; off by default
}
Reload the window. zettelmesh: Show Output is the first place to look if the language server does not come up — it logs the exact command line and environment it used.
Settings
All settings live under zettelmesh. (Settings UI → search
"zettelmesh").
| Setting | Purpose |
|---|---|
zettelmesh.configFile |
Optional primary zettel.toml; otherwise the CLI default or ZETTEL_CONFIG is used. |
zettelmesh.secretsFile |
Optional zettel_secrets.toml; otherwise the CLI selects the sibling file or ZETTEL_SECRETS_FILE. |
zettelmesh.zettelPath |
The zettel executable. Default zettel. |
zettelmesh.lsp.enabled |
Run the language server. Default true. |
zettelmesh.mcp.enabled |
Offer the MCP server to VS Code. Default false. |
zettelmesh.author |
Author name offered by New Note. |
zettelmesh.newNote.defaultOrgFields |
name=value defaults for a new note's organizational fields. |
All registry, clone, index, embedding, chat, audit, and secret settings
are read by the CLI. The extension obtains the effective paths by calling
zettel config show --json, so its behavior stays aligned with the CLI as
configuration evolves. API keys never pass through VS Code settings.
Changing any setting restarts the language server and refreshes the MCP entry.
Notes view
The zettelmesh activity-bar container has a Notes tree: notes
grouped by repo → first organizational field (department in the default schema), shown by title, click to open. It reads
the same in-memory cache of zettel list --json as Go to Note, and
refreshes on activation, after a sync, and on a settings change. Right-
click a note for Copy Note ID.
The search button in the Notes title bar asks for a query, runs zettel search
(semantic + keyword, up to 50 hits) and replaces the tree with the ranked hits;
the clear button or an empty query brings the full tree back. A search can take
a few seconds because it calls the embedding provider.
Hidden ids
Note ids (repo:ULID) mean nothing to a reader, so the editor hides them
(zettelmesh.display.hideIds, on by default): a links: entry shows the
target's title (the language server's inlay hint) and its #type, and
[text](zettel:...) shows as an underlined [text]. Nothing in the file
changes. Put the cursor (or a selection) on the line to see the raw text
again. Links to notes you cannot read are left visible. Hover a hidden link for
the note card, with the id last. Right-click in a note for Copy Note ID /
Copy Note Link (the link under the cursor, else the note itself); the Notes
and Links views have the same commands on each row.
Markdown preview
In VS Code's built-in Markdown preview, [text](zettel:...) links are
clickable and open the note. The tooltip is the note card; an empty link text
(or the raw url) shows the target's title; links to notes you cannot read are
muted and do nothing. Under each note the preview adds two sections, like
mkdocs: See also (the note's own links:, as type — title) and
Referenced by (notes that link here, with the link text they used in
quotes). The preview redraws when the note cache or link graph changes.
Links view
Under Notes in the same container, Links shows the note you are
looking at: Outgoing (its own links: and inline zettel: links) and
Backlinks (notes that link here), each row with link type, repository and
frontmatter/inline. Click to open. It keeps the last note when you focus a
non-Markdown file. Data comes from one zettel graph load per refresh, so
links to notes you cannot read are absent, and results are as of the last sync.
Commands
- zettelmesh: Go to Note — quick pick over note titles; opens the
file. Backed by an in-memory cache of
zettel list --json, refreshed on activation, after a sync, and when settings change. - zettelmesh: New Note — guided input: repository → title → author
→ one value per organizational field the repository declares → tags,
then runs
zettel newand opens the file. The note is not indexed until it is committed and synced. Defaults come fromzettelmesh.authorandzettelmesh.newNote.defaultOrgFields(name=valueentries). - zettelmesh: Search (
Ctrl/Cmd+KZ) — live quick pick overzettel search(hybrid semantic + keyword when embeddings are configured, keyword-only otherwise). Each pause in typing runs one query; Enter opens the note at the matched passage. - zettelmesh: Search Mesh for Selection — same, seeded from the editor selection (or the word under the cursor). Also on the editor right-click menu.
- zettelmesh: Sync — runs
zettel syncwith a cancelable progress notification (the language server never syncs on its own). - zettelmesh: Refresh Notes — re-run
zettel listand redraw the Notes view. - zettelmesh: Restart Language Server
- zettelmesh: Show Output — the
zettelmeshoutput channel (start command lines, errors).
Adding links in note text
Inline note links use the zettel: URI scheme in ordinary Markdown text:
This builds on [hybrid search](zettel:)
After typing zettel:, trigger completion or type the next character. The
completion list uses note titles as labels and inserts the canonical
repo:ULID destination. Existing partial destinations are replaced cleanly:
This builds on [hybrid search](zettel:handbook:01J...)
This is a separate, phrase-level relation from the note-level links:
frontmatter list. Both forms are supported by the same language server, with
hover, go-to-definition, diagnostics, and backlink support for inline links.
How it maps to the CLI
Before starting integrations, the extension runs zettel config show --json
and then launches the child command with the same inherited environment:
zettel serve lsp
(configuration resolved by zettel.toml, zettel_secrets.toml, and env)
The MCP server is the same, with zettel serve mcp instead of zettel serve lsp.
The language server never runs zettel sync — run that yourself (or
via a terminal / task) to refresh the index.
Developing
npm install
Then press F5 ("Run Extension") to launch an Extension Development
Host. npm run package builds a .vsix (needs @vscode/vsce).
No build step — plain CommonJS JavaScript in src/.