Codium nostr extension

A Codium / VSCode extension to publish files to [Nostr].

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 .md file 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, sig added), 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 nsec or hex key; held in memory only, never written to disk.
    • n_signer (Unix socket) — delegate signing to a local n_signer process over an abstract Unix socket. Your key never leaves the signer.
    • n_signer (Qubes qrexec) — reach n_signer in another qube via Qubes OS secure IPC.
    • n_signer (TCP) — reach n_signer over TCP (FIPS mesh, remote machine). Requires a caller key for auth envelopes.
  • 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_at tracking — 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-tools 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
    rm -rf lib && bun run build.js   # or: node build.js
    npx tsc                          # produces lib/types/*.d.ts
    
  • For n_signer backends (optional): the nsigner binary 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. Requires nsigner --listen unix --socket-name nsigner running on the same machine.

  • n_signer (Qubes qrexec) — enter the target qube name (e.g. nostr_signer) and key index. Requires the qubes.NsignerRpc service 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. Requires nsigner --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

  1. 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.
    
  2. Edit the front-matter (metadata) and write your article body in Markdown below the --- line.

  3. Click Validate Front-Matter to preview the metadata that will be published (dry run — nothing is sent).

  4. 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

  1. Click + New Event → pick a kind (Short Text Note, Relay List, Profile, Contacts, or Custom).
  2. A JSON skeleton opens with the expected tags and placeholder content for that kind.
  3. Fill in the content and tags fields.
  4. Click Publish Event (or right-click in the JSON editor → Nostr: Publish Event from JSON File).
  5. 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 kind field, the raw text is wrapped as a kind 1 (short text note) event with the text as content. For example, type What'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.NsignerRpc service in the target qube.
  • Install dom0 qrexec policy allowing your qube to call it.
  • The extension spawns qrexec-client-vm <qube> qubes.NsignerRpc per 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 — image URLs 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_at workspace-state map is per-workspace; switching workspaces loses the first-publish timestamps.
  • Serial transport (USB CDC) is not supported — needs a native serialport module. Use unix, qrexec, or tcp instead.

Design docs

Detailed design documents are in plans/:

License

MIT


Write a comment