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:49:50 -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

slug, title, summary, image, tags
slug title summary image tags
codium-nostr-extension Codium nostr extension A Codium / VSCode extension to publish files to [Nostr].
nostr
writing
codium
vscode

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 signer hardware signer (the Rust port of n_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.
  • Upload files to Blossom servers — Upload any file (or a markdown book with front-matter) to your Blossom media servers and publish a NIP-94 file-metadata (kind 1063) event linking the blob. Great for posting long documents/books.
  • Multiple signing backends:
    • Local — enter your nsec or hex key; held in memory only, never written to disk.
    • signer (Unix socket) — delegate signing to a local signer process over an abstract Unix socket. Your key never leaves the signer.
    • signer (Qubes qrexec) — reach signer in another qube via Qubes OS secure IPC.
    • signer (TCP) — reach signer over TCP (FIPS mesh, remote machine). Requires a caller key for auth envelopes when the signer runs --auth required.
  • Sidebar GUI — sign in, manage relays and Blossom servers, create events, upload, 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.
  • Blossom server auto-discovery (BUD-03) — on sign-in, fetches your kind 10063 server list and uses your own Blossom servers for uploads.
  • 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 signer backends (optional): the signer binary installed and running. See the 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.

  • signer (Unix socket) — enter the abstract socket name (default signer01; the signer auto-names signer01, signer02, …), the role name (default main), and the role path (default m/44'/1237'/0'/0/0). Requires signer --listen unix running on the same machine.

  • signer (Qubes qrexec) — enter the target qube name (e.g. nostr_signer), role, and role path. Requires the qubes.SignerRpc service installed in the target qube and dom0 policy allowing your qube to call it.

  • signer (TCP) — enter host, port, your caller nsec (for auth envelopes, only when the signer runs --auth required), role, and role path. Requires signer --listen tcp:<host>:<port> running on the target machine.

Key selection: signer selects the Nostr key by role + role_path, not by a numeric index. The role is an access token registered on the signer (in its add-role popup or via --register-role); the role path is the full BIP-44 derivation path. Both must match a role registered on the signer. To use a different Nostr account, edit the account segment of the path (e.g. m/44'/1237'/3'/0/0).

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.

7. Upload a file to Blossom

Blossom is a protocol for storing blobs (files) on media servers using your Nostr key for identity and authorization. This extension uploads files to your Blossom servers and publishes a NIP-94 file-metadata (kind 1063) event so Nostr clients can discover and display the file.

Quick upload (any file):

  1. Open the file you want to upload (any type — markdown, PDF, EPUB, image, …).
  2. Click Upload File to Blossom in the sidebar (or run Nostr: Upload File to Blossom).
  3. Confirm the target servers and relays in the dialog.
  4. The file is uploaded to every checked Blossom server, and a kind 1063 metadata event is signed and published to your relays. The blob URL is offered for copying to the clipboard.

Header-based upload (long documents/books):

  1. Write a .md file with YAML front-matter carrying the book metadata:

    ---
    slug: narrative-of-frederick-douglass
    title: Narrative of the Life of Frederick Douglass
    summary: An 1845 autobiography by Frederick Douglass.
    author: Frederick Douglass
    language: en
    year: 1845
    cover: https://cdn.example.com/douglass-cover.png
    tags: [history, abolition, autobiography]
    ---
    
    # Narrative of the Life of Frederick Douglass
    
    Body text…
    
  2. Click Publish to Blossom (with header) (or run Nostr: Publish to Blossom (with header)).

  3. The front-matter block is stripped, the markdown body is uploaded as a blob, and a kind 1063 event is built from the descriptor + front-matter (title, author, language, year, cover, tags) and published to your relays.

Markdown MIME handling: markdown blobs are uploaded as text/plain so browsers render them inline (no mainstream browser displays text/markdown, and servers send X-Content-Type-Options: nosniff which forbids guessing). The kind 1063 event's m tag still advertises text/markdown, so Nostr clients that render markdown can detect the file's true type.

Blossom servers:

  • The Blossom Servers section shows your servers with checkboxes — exactly like the Relays section. Every checked server receives the upload.
  • Click Fetch My Blossom Servers (BUD-03) to load your kind 10063 server list from the network.
  • Fallback: set nostr.blossomServers in settings (an array of full URLs) if you have no kind 10063 event.
  • Uploads use a signed kind 24242 authorization token (BUD-11) scoped to the server domain and the blob's sha256, valid for nostr.blossom.authTokenTtlSec seconds (default 60).

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 "signer".
nostr.signer.transport "unix" "unix", "qrexec", or "tcp".
nostr.signer.socketName "signer01" Abstract socket name (without @). The signer auto-names signer01, signer02, … Used when transport=unix.
nostr.signer.role "main" signer role name (access token) used to select the Nostr key. Sent as role to the nostr_* verbs.
nostr.signer.rolePath "m/44'/1237'/0'/0/0" Full BIP-44 derivation path for the Nostr key. Sent as role_path. Edit the account segment (m/44'/1237'/N'/0/0) to pick a different account.
nostr.signer.qrexecQube "nostr_signer" Target qube name. Used when transport=qrexec.
nostr.signer.qrexecService "qubes.SignerRpc" Qrexec service name.
nostr.signer.tcpHost "127.0.0.1" TCP host. Used when transport=tcp.
nostr.signer.tcpPort 8080 TCP port. Used when transport=tcp.
nostr.signer.callerNsec "" Caller nsec for TCP auth envelopes (kind 22242). Required for transport=tcp when the signer runs --auth required.
nostr.blossomServers [] Blossom media servers (full URLs) to upload blobs to. Used when no kind 10063 server list is fetched.
nostr.blossom.uploadTimeoutMs 30000 Per-server Blossom upload timeout in milliseconds.
nostr.blossom.authTokenTtlSec 60 Lifetime in seconds of the kind 24242 Blossom authorization token (BUD-11).

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: Fetch My Blossom Servers (BUD-03) Fetch your kind 10063 Blossom server list.
Nostr: Upload File to Blossom Upload the active file to Blossom + publish a kind 1063 metadata event.
Nostr: Publish to Blossom (with header) Upload the active markdown file (front-matter stripped) to Blossom + publish a kind 1063 event with book metadata.
Nostr: Insert Blob Header Insert a YAML front-matter template for Blossom uploads (slug, title, summary, author, language, year, cover, tags, file).
Nostr: Select Signer Backend Switch between local and signer backends (command-palette flow).
Nostr: Reset Published Timestamps Clear the local published_at tracking map.

signer setup

For the signer backends, you need a running signer process (the Rust port of n_signer). The extension connects to it; it doesn't bundle the binary.

Unix socket (local, same machine):

signer --listen unix

The signer auto-picks the first free sequential name (signer01, signer02, …); override with --socket-name.

Qubes qrexec (inter-qube):

  • Run the persistent signer in the signer qube: signer --listen unix --socket-name signer --bridge-source-trusted
  • Install the qubes.SignerRpc service in the target qube (runs signer bridge --to signer).
  • Install dom0 qrexec policy allowing your qube to call it.
  • The extension spawns qrexec-client-vm <qube> qubes.SignerRpc per call.

TCP (remote / FIPS mesh):

signer --listen tcp:[::]:8080

When the signer runs with --auth required, the extension signs a kind-22242 auth envelope per request with your caller key. With --auth off (the default) no caller key is needed.

See the 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; signer backends hold the key material instead).
  • 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

S
Description
No description provided
Readme MIT
400 KiB
Languages
TypeScript 97.8%
Shell 1.9%
JavaScript 0.3%