Nostr Publish
A Codium / VSCode extension to publish files to Nostr.
Phases
- Phase 0: Publish raw unsigned Nostr events from a
.jsonfile. Generate a skeleton event, fill in the fields, validate, sign, and broadcast. - Phase 1: Publish
.mdfiles as NIP-23 long-form notes (kind30023) with YAML front-matter metadata. - Phase 2: Delegate signing to a running
n_signerprocess over a Linux abstract Unix socket.
See plans/ for the full design:
plans/phase0-raw-event-publishing.mdplans/longform-metadata-strategy.mdplans/implementation-plan.md
Prerequisites
-
Node.js ≥ 18.
-
The laantungir fork of
nostr-toolsmust be cloned as a sibling directory and built:git clone git@laantungir.net:laantungir/nostr-tools.git ../nostr-tools cd ../nostr-tools npm install --ignore-scripts # the prepublish hook needs `just`, which we skip rm -rf lib && bun run build.js # or: node build.js npx tsc # produces lib/types/*.d.tsThe extension depends on it via
"nostr-tools": "file:../nostr-tools".
Install (development)
npm install
npm run compile # esbuild -> dist/extension.js
Press F5 in Codium/VSCode to launch an Extension Development Host with the
extension loaded, or package and install:
npx vsce package # produces nostr-publish-0.1.0.vsix
code --install-extension nostr-publish-0.1.0.vsix
Phase 0 usage
1. Sign in
Run Nostr: Sign In from the command palette. Enter your secret key as
nsec1... or 64-char hex. The key is held in memory only for the session —
it is never written to disk. The status bar shows your npub1....
Run Nostr: Sign Out to clear the key (the buffer is zeroed).
2. Create an unsigned event
Run Nostr: Create Unsigned Event. Pick a kind (1 = short text note, 30023 = long-form article, 10002 = relay list, 0 = metadata, 3 = contacts, or Custom…). A skeleton JSON document opens in an untitled editor:
{
"kind": 1,
"created_at": 1721329200,
"tags": [],
"content": ""
}
Fill in tags and content. Save it as a .json file if you want to reuse
it, or publish directly from the untitled buffer.
3. Validate
Run Nostr: Validate Event File (or right-click in a JSON editor →
Nostr: Validate Event File). Checks the shape of the event and reports any
errors or warnings (e.g. unknown fields, or pubkey/id/sig left over
from a previously signed event).
4. Publish
Run Nostr: Publish Event from JSON File (or right-click → Nostr: Publish Event from JSON File). The extension:
- Validates the JSON.
- Signs the event with your session key (injects
pubkey,id,sig). - Shows a confirmation dialog with a preview and relay checkboxes.
- Broadcasts to the selected relays via WebSocket.
- Reports per-relay OK/failed results and the event id.
If the JSON contains pubkey/id/sig, they are stripped and the event is
re-signed with your active key (useful for re-signing an event as yourself).
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. |
File format
The .json file is a plain Nostr EventTemplate — no extension-specific
wrapper. It is interoperable with any other Nostr tooling that accepts
EventTemplate JSON:
{
"kind": 30023,
"created_at": 1721329200,
"tags": [
["d", "my-post"],
["title", "My Post"]
],
"content": "# My Post\n\nBody text..."
}
Architecture
Signerinterface: reusesnostr-tools'Signer(nostr-tools/signer.ts).LocalSignerwrapsPlainKeySigner; Phase 2'sNsignerBackendwill implement the same interface.LongFormArticle = 30023constant fromnostr-tools/kinds(Phase 1).- Relay publishing:
wsWebSocket,["EVENT", signed], waits for["OK", id, true|false, reason]. - Bundling: esbuild → single
dist/extension.js,vscodeexternal.
Phase 2: n_signer signing backend
Phase 2 delegates signing to a running
n_signer process over
a Linux abstract Unix socket. The key material stays in n_signer's
mlock'd RAM — the extension never sees it.
Setup
-
Build and run
n_signer(see its README for build instructions):nsigner --listen unix --socket-name nsignerEnter your mnemonic at the prompt.
n_signerbinds the abstract socket@nsignerand shows its status TUI. -
In the extension, run Nostr: Select Signer Backend (command palette):
- Pick n_signer (remote signing).
- The extension discovers running
n_signersockets via/proc/net/unixand lists them. Pick@nsigner(or enter a custom socket name). - The extension calls
get_public_keyto verify reachability and shows your npub. If it can't connect, it shows an error with the command to startn_signer.
-
Publish as usual — the extension calls
sign_eventonn_signerfor each publish. The first sign from a new caller may prompt for approval at then_signerterminal (per its deny-by-default policy).
Wire contract
- Transport: Node
net.createConnection({ path: "\u0000nsigner" })(abstract socket, the\u0000prefix is the Linux abstract-namespace marker). - Framing: 4-byte big-endian length prefix + UTF-8 JSON payload.
- Verbs:
get_public_key(params[{nostr_index: <n>}]),sign_event(params[JSON.stringify(event), {nostr_index: <n>}]). - No auth envelope needed for unix sockets — identity is UID-based via
SO_PEERCRED.
Configuration
| Setting | Default | Description |
|---|---|---|
nostr.signerBackend |
"local" |
"local" or "nsigner". |
nostr.nsigner.socketName |
"nsigner" |
Abstract socket name (without @). |
nostr.nsigner.nostrIndex |
0 |
nostr_index for key derivation. |
Sidebar
The Nostr sidebar (Activity Bar icon) shows:
- Identity: signed-in state + npub + signer backend label + Sign In/Out.
- Relays: checkboxes to toggle which relays to publish to, plus a Fetch My Relays (NIP-65) button that loads your kind 10002 relay list.
- Actions: + New Event, Publish Current File as Long-Form Note, Validate Front-Matter, Publish Current JSON File, Validate Current File.
Buttons enable/disable based on the active editor's language (markdown vs JSON). The sidebar refreshes on sign-in/out and when switching editors.
Known limitations
- No key persistence for the local backend — the secret key must be
re-entered each session (by design; Phase 2's
n_signerbackend holds the key material instead). - No image upload —
imageURLs in front-matter must be hosted externally (future Blossom/NIP-96 phase). n_signerbackend is Linux-only (abstract Unix sockets are a Linux primitive).- The
published_atworkspace-state map is per-workspace; switching workspaces loses the first-publish timestamps.