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 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
.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. - 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
nsecor hex key; held in memory only, never written to disk. - signer (Unix socket) — delegate signing to a local
signerprocess over an abstract Unix socket. Your key never leaves the signer. - signer (Qubes qrexec) — reach
signerin another qube via Qubes OS secure IPC. - signer (TCP) — reach
signerover TCP (FIPS mesh, remote machine). Requires a caller key for auth envelopes when the signer runs--auth required.
- Local — enter your
- 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_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 signer backends (optional): the
signerbinary 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-namessigner01,signer02, …), the role name (defaultmain), and the role path (defaultm/44'/1237'/0'/0/0). Requiressigner --listen unixrunning on the same machine. -
signer (Qubes qrexec) — enter the target qube name (e.g.
nostr_signer), role, and role path. Requires thequbes.SignerRpcservice 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. Requiressigner --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
-
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.
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):
- Open the file you want to upload (any type — markdown, PDF, EPUB, image, …).
- Click Upload File to Blossom in the sidebar (or run Nostr: Upload File to Blossom).
- Confirm the target servers and relays in the dialog.
- 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):
-
Write a
.mdfile 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… -
Click Publish to Blossom (with header) (or run Nostr: Publish to Blossom (with header)).
-
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/plainso browsers render them inline (no mainstream browser displaystext/markdown, and servers sendX-Content-Type-Options: nosniffwhich forbids guessing). The kind 1063 event'smtag still advertisestext/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.blossomServersin 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.authTokenTtlSecseconds (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.SignerRpcservice in the target qube (runssigner bridge --to signer). - Install dom0 qrexec policy allowing your qube to call it.
- The extension spawns
qrexec-client-vm <qube> qubes.SignerRpcper 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_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.plans/blossom-upload.md— Blossom server upload (BUD-01..11, NIP-94 kind 1063) design.
License
MIT