Codium nostr extension
- Nostr Publish
Nostr Publish
A Codium / VSCode extension to publish files to Nostr.
Publish markdown articles as long-form notes, broadcast raw Nostr events from JSON, and sign everything with either a local key or a remote n_signer hardware signer.
Features
- Publish markdown as long-form notes — Write a
.mdfile with YAML front-matter (title, summary, image, tags), click one button, and it’s broadcast as a NIP-23 kind-30023 event to your relays. - Publish raw events from JSON — Create a Nostr event skeleton (text note, relay list, profile, contacts, or custom kind), fill in the fields, and publish.
- Sign events without publishing — Sign an event in the active editor and replace its contents with the signed version (
id,pubkey,sigadded), without broadcasting. Works on plain text (wrapped as a kind 1 text note) or JSON event templates. Re-running the command on an edited signed event re-signs it cleanly. - Multiple signing backends:
- Local — enter your
nsecor hex key; held in memory only, never written to disk. - n_signer (Unix socket) — delegate signing to a local
n_signerprocess over an abstract Unix socket. Your key never leaves the signer. - n_signer (Qubes qrexec) — reach
n_signerin another qube via Qubes OS secure IPC. - n_signer (TCP) — reach
n_signerover TCP (FIPS mesh, remote machine). Requires a caller key for auth envelopes.
- Local — enter your
- Sidebar GUI — sign in, manage relays, create events, and publish — all from the Nostr sidebar in the Activity Bar.
- NIP-65 relay auto-discovery — on sign-in, fetches your kind 10002 relay list and uses your own relays for publishing.
- Avatar display — fetches your kind 0 profile and shows your avatar + name in the sidebar.
published_attracking — first-publish timestamps are stored per-workspace so updates preserve the original timestamp (NIP-23).
Install
Prerequisites
-
Codium or VSCode (≥ 1.85).
-
Node.js ≥ 18 (for building only — not needed at runtime).
-
The laantungir fork of
nostr-toolscloned as a sibling directory and built:git clone git@laantungir.net:laantungir/nostr-tools.git ../nostr-tools cd ../nostr-tools npm install --ignore-scripts rm -rf lib && bun run build.js # or: node build.js npx tsc # produces lib/types/*.d.ts -
For n_signer backends (optional): the
nsignerbinary installed and running. See the n_signer README for build instructions.
Build and install
git clone git@laantungir.net:laantungir/nostr-publish.git
cd nostr-publish
npm install
./install_upgrade.sh
The install_upgrade.sh script compiles the extension, packages it into a .vsix, and installs it into Codium (or VSCode) with --force. Restart your editor after it finishes.
Alternatively, build and install manually:
npm run compile
npx vsce package --no-dependencies
codium --install-extension nostr-publish-0.1.0.vsix --force
Updating after code changes
./install_upgrade.sh
# restart Codium
The script handles everything — rebuild, repackage, reinstall.
Usage
1. Open the Nostr sidebar
Click the Nostr icon (broadcast glyph) in the Activity Bar on the left side of Codium. The sidebar has three sections:
- Actions — create events, sign files, publish files, validate. Action buttons show the name of the active file (first 10 chars of the filename + extension) so it’s clear which file will be acted upon.
- Relays — toggle which relays to publish to, fetch your NIP-65 relay list.
- Identity — sign in, view your avatar/npub, sign out.
2. Sign in
In the Identity section, choose a sign-in method from the dropdown:
-
Local (nsec / hex key) — paste your
nsec1...or 64-char hex secret key. The key is held in memory only for the session — it’s never written to disk. Sign out to clear it. -
n_signer (Unix socket) — enter the abstract socket name (default
nsigner) and key index. Requiresnsigner --listen unix --socket-name nsignerrunning on the same machine. -
n_signer (Qubes qrexec) — enter the target qube name (e.g.
nostr_signer) and key index. Requires thequbes.NsignerRpcservice installed in the target qube and dom0 policy allowing your qube to call it. -
n_signer (TCP) — enter host, port, your caller
nsec(for auth envelopes), and key index. Requiresnsigner --listen tcp:<host>:<port>running on the target machine.
Click Sign In. The extension connects to the signer, fetches your public key, displays your avatar + npub, and auto-loads your NIP-65 relay list.
3. Publish a markdown article
-
Click + New Event → pick 30023 — Long-Form Article. A markdown file opens with a YAML front-matter skeleton:
--- slug: my-article-slug title: My Article Title summary: A one-sentence abstract for feeds. image: https://example.com/cover.png tags: [nostr, writing] --- # My Article Title Write your article in Markdown here. -
Edit the front-matter (metadata) and write your article body in Markdown below the
---line. -
Click Validate Front-Matter to preview the metadata that will be published (dry run — nothing is sent).
-
Click Publish as Long-Form Note. A confirmation dialog shows the event preview (slug, title, summary, image, tags,
published_at, body length, pubkey, relays). Toggle relays on/off, then click Publish.
The front-matter block is stripped before publishing — readers on Nostr see only the markdown body. The file on disk keeps the front-matter for re-publishing.
Re-publishing an update: edit the file and publish again. The same slug (d-tag) means it’s a parameterized-replaceable update — relays replace the old version. The published_at timestamp is preserved from the first publish (tracked per-workspace).
4. Publish a raw event from JSON
- Click + New Event → pick a kind (Short Text Note, Relay List, Profile, Contacts, or Custom).
- A JSON skeleton opens with the expected tags and placeholder content for that kind.
- Fill in the
contentandtagsfields. - Click Publish Event (or right-click in the JSON editor → Nostr: Publish Event from JSON File).
- Confirm in the dialog, then broadcast.
The JSON file is a plain Nostr EventTemplate — interoperable with any other Nostr tooling that accepts EventTemplate JSON.
5. Sign an event without publishing
The Sign as Event action signs the active editor’s contents and replaces them in place with the signed event (adding id, pubkey, and sig), without broadcasting to any relay. This is useful for preparing events to publish via another tool, or for inspecting the signed form.
Two input modes:
-
Plain text — if the active document is not a JSON object with a
kindfield, the raw text is wrapped as a kind 1 (short text note) event with the text ascontent. For example, typeWhat's happening?in an untitled file and click Sign as Event:{ "kind": 1, "created_at": 1785315205, "tags": [], "content": "What's happening?", "id": "...", "pubkey": "...", "sig": "..." } -
JSON event template — if the document is a JSON object with a
kind, it’s treated as an event template. Any signed fields already present (id,pubkey,sig) are stripped and recomputed.
Re-signing after edits: because signed fields are stripped and recomputed on each run, you can take a signed event, change the kind (or content, tags, created_at), and run Sign as Event again — it re-signs cleanly with the new values. The edit is undoable.
6. Manage relays
- The Relays section shows your configured relays with checkboxes. Toggle individual relays on/off for the next publish.
- Click Fetch My Relays (NIP-65) to load your kind 10002 relay list from the network and replace the sidebar’s relay set with it.
- Default relays:
wss://relay.damus.io,wss://relay.primal.net,wss://laantungir.net/relay. Change them in Codium Settings →nostr.relays.
Configuration
| Setting | Default | Description |
|---|---|---|
nostr.relays |
["wss://relay.damus.io", "wss://relay.primal.net", "wss://laantungir.net/relay"] |
Relays to publish to. |
nostr.publish.confirm |
true |
Show a confirmation dialog before broadcasting. |
nostr.publish.timeoutMs |
10000 |
Per-relay publish timeout in milliseconds. |
nostr.signerBackend |
"local" |
"local" or "nsigner". |
nostr.nsigner.transport |
"unix" |
"unix", "qrexec", or "tcp". |
nostr.nsigner.socketName |
"nsigner" |
Abstract socket name (without @). Used when transport=unix. |
nostr.nsigner.nostrIndex |
0 |
Key index (NIP-06 m/44'/1237'/N'/0/0). |
nostr.nsigner.qrexecQube |
"nostr_signer" |
Target qube name. Used when transport=qrexec. |
nostr.nsigner.qrexecService |
"qubes.NsignerRpc" |
Qrexec service name. |
nostr.nsigner.tcpHost |
"127.0.0.1" |
TCP host. Used when transport=tcp. |
nostr.nsigner.tcpPort |
8080 |
TCP port. Used when transport=tcp. |
nostr.nsigner.callerNsec |
"" |
Caller nsec for TCP auth envelopes. Required for transport=tcp if not signed in locally. |
Commands
| Command | Description |
|---|---|
| Nostr: Sign In | Sign in with a local nsec/hex key (InputBox prompt). |
| Nostr: Sign Out | Clear the session key. |
| Nostr: Create Unsigned Event | Generate a kind-specific event skeleton (JSON or markdown). |
| Nostr: Validate Event File | Validate the active JSON file as a Nostr event. |
| Nostr: Sign Event | Sign the active file’s contents and replace them with the signed event (no publish). Plain text → kind 1; JSON with a kind → re-signed event template. |
| Nostr: Publish Event from JSON File | Sign and publish the active JSON file. |
| Nostr: Publish Current File as Long-Form Note | Parse front-matter, build a NIP-23 event, sign, and publish. |
| Nostr: Validate Front-Matter | Dry-run preview of the front-matter metadata. |
| Nostr: Fetch My Relays (NIP-65) | Fetch your kind 10002 relay list. |
| Nostr: Select Signer Backend | Switch between local and n_signer backends (command-palette flow). |
| Nostr: Reset Published Timestamps | Clear the local published_at tracking map. |
n_signer setup
For the n_signer backends, you need a running nsigner process. The extension connects to it; it doesn’t bundle the binary.
Unix socket (local, same machine):
nsigner --listen unix --socket-name nsigner
Qubes qrexec (inter-qube):
- Install the
qubes.NsignerRpcservice in the target qube. - Install dom0 qrexec policy allowing your qube to call it.
- The extension spawns
qrexec-client-vm <qube> qubes.NsignerRpcper call.
TCP (remote / FIPS mesh):
nsigner --listen tcp:[::]:8080
The extension signs a kind-27235 auth envelope per request with your caller key. The first request from a new caller prompts for approval at the n_signer terminal.
See the n_signer README for full build and setup instructions.
Known limitations
- No key persistence for the local backend — the secret key must be re-entered each session (by design; n_signer backends hold the key material instead).
- No image upload —
imageURLs in front-matter must be hosted externally (future Blossom/NIP-96 phase). - n_signer backends are Linux-only (abstract Unix sockets, qrexec, and TCP auth envelopes are Linux/Qubes primitives).
- The
published_atworkspace-state map is per-workspace; switching workspaces loses the first-publish timestamps. - Serial transport (USB CDC) is not supported — needs a native
serialportmodule. Use unix, qrexec, or tcp instead.
Design docs
Detailed design documents are in plans/:
plans/phase0-raw-event-publishing.md— raw event publishing.plans/longform-metadata-strategy.md— YAML front-matter + NIP-23 metadata.plans/implementation-plan.md— full implementation plan with code sketches.
License
MIT
Write a comment