2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00
2026-07-18 17:29:20 -04:00

Nostr Publish

A Codium / VSCode extension to publish files to Nostr.

Phases

  • Phase 0: Publish raw unsigned Nostr events from a .json file. Generate a skeleton event, fill in the fields, validate, sign, and broadcast.
  • Phase 1: Publish .md files as NIP-23 long-form notes (kind 30023) with YAML front-matter metadata.
  • Phase 2: Delegate signing to a running n_signer process over a Linux abstract Unix socket.

See plans/ for the full design:

Prerequisites

  1. Node.js ≥ 18.

  2. The laantungir fork of nostr-tools must 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.ts
    

    The 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:

  1. Validates the JSON.
  2. Signs the event with your session key (injects pubkey, id, sig).
  3. Shows a confirmation dialog with a preview and relay checkboxes.
  4. Broadcasts to the selected relays via WebSocket.
  5. 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

  • Signer interface: reuses nostr-tools' Signer (nostr-tools/signer.ts). LocalSigner wraps PlainKeySigner; Phase 2's NsignerBackend will implement the same interface.
  • LongFormArticle = 30023 constant from nostr-tools/kinds (Phase 1).
  • Relay publishing: ws WebSocket, ["EVENT", signed], waits for ["OK", id, true|false, reason].
  • Bundling: esbuild → single dist/extension.js, vscode external.

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

  1. Build and run n_signer (see its README for build instructions):

    nsigner --listen unix --socket-name nsigner
    

    Enter your mnemonic at the prompt. n_signer binds the abstract socket @nsigner and shows its status TUI.

  2. In the extension, run Nostr: Select Signer Backend (command palette):

    • Pick n_signer (remote signing).
    • The extension discovers running n_signer sockets via /proc/net/unix and lists them. Pick @nsigner (or enter a custom socket name).
    • The extension calls get_public_key to verify reachability and shows your npub. If it can't connect, it shows an error with the command to start n_signer.
  3. Publish as usual — the extension calls sign_event on n_signer for each publish. The first sign from a new caller may prompt for approval at the n_signer terminal (per its deny-by-default policy).

Wire contract

  • Transport: Node net.createConnection({ path: "\u0000nsigner" }) (abstract socket, the \u0000 prefix 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_signer backend holds the key material instead).
  • No image upload — image URLs in front-matter must be hosted externally (future Blossom/NIP-96 phase).
  • n_signer backend is Linux-only (abstract Unix sockets are a Linux primitive).
  • The published_at workspace-state map is per-workspace; switching workspaces loses the first-publish timestamps.
S
Description
No description provided
Readme MIT
400 KiB
Languages
TypeScript 97.8%
Shell 1.9%
JavaScript 0.3%