feat: BLOSSOM (#75)

* Add Blossom blob storage integration

- Add blossom-client-sdk dependency for blob storage operations
- Create blossom.ts service with upload, list, check, mirror, delete primitives
- Add kind 10063 server list fetching and parsing
- Create blossom-parser.ts for command argument parsing with subcommands
- Add BLOSSOM command to man.ts with subcommands:
  - servers: Show configured Blossom servers
  - check: Check server health
  - upload: Upload files to user's servers
  - list: List blobs for a user
  - mirror: Mirror blobs between servers
  - delete: Delete blobs from servers
- Create BlossomViewer component with views for each subcommand
- Wire up BlossomViewer in WindowRenderer
- Add Blossom servers dropdown to ProfileViewer header
- Upload primitives can be called programmatically for use in other components

* Enhance Blossom viewer with server selection and blob details

- Add server selection checkboxes to upload view for choosing target servers
- Add BlobDetailView with media preview (image/video/audio) and metadata display
- Add 'blob' subcommand to view individual blob details
- Remove unused 'check' subcommand

* Add Blossom upload dialog with chat integration

- Create BlossomUploadDialog component with file picker, server selection, and preview
- Create useBlossomUpload hook for easy integration in any component
- Add insertText method to MentionEditor for programmatic text insertion
- Integrate upload button (paperclip icon) in chat composer
- Supports image, video, and audio uploads with drag-and-drop

* Add rich blob attachments with imeta tags for chat

- Add BlobAttachment TipTap extension with inline preview (thumbnail for images, icons for video/audio)
- Store full blob metadata (sha256, url, mimeType, size, server) in editor nodes
- Convert blob nodes to URLs in content with NIP-92 imeta tags when sending
- Add insertBlob method to MentionEditor for programmatic blob insertion
- Update NIP-29 and NIP-53 adapters to include imeta tags with blob metadata
- Pass blob attachments through entire send flow (editor -> ChatViewer -> adapter)

* Add fallback public Blossom servers for users without server list

- Add well-known public servers as fallbacks (blossom.primal.net, nostr.download, files.v0l.io)
- Use fallbacks when user has no kind 10063 server list configured
- Show "Public Servers" label with Globe icon when using fallbacks
- Inform user that no server list was found
- Select first fallback server by default (vs all user servers)

* Fix: Don't show fallback servers when not logged in

Blossom uploads require signed auth events, so users must be logged in.
The 'Account required' message is already shown in this case.

* Remove files.v0l.io from fallback servers

* Add rich renderer for kind 10063 Blossom server list

- Create BlossomServerListRenderer.tsx with feed and detail views
- Show user's configured Blossom servers with clickable links
- Clicking a server opens the Blossom window with server info
- Register renderers for kind 10063 (BUD-03)
- Fix lint error by renaming useFallbackServers to applyFallbackServers

* Add individual server view and NIP-05 support for blossom commands

- Add 'server' subcommand to view info about a specific Blossom server
- Update BlossomServerListRenderer to open server view on click
- Make blossom parser async to support NIP-05 resolution in 'list' command
- Add kind 10063 (Blossom Server List) to EVENT_KINDS constants with BUD-03 reference
- Update command examples with NIP-05 identifier support

* Add comprehensive tests for blossom-parser

- 34 test cases covering all subcommands (servers, server, upload, list, blob, mirror, delete)
- Tests for NIP-05 resolution, npub/nprofile decoding, $me alias
- Tests for error handling and input validation
- Tests for case insensitivity and command aliases (ls, view, rm)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Alejandro
2026-01-13 17:16:31 +01:00
committed by GitHub
parent ed6f2fd856
commit 9ef1fefd3d
20 changed files with 3418 additions and 20 deletions

View File

@@ -19,6 +19,7 @@ export type AppId =
| "chat"
| "spells"
| "spellbooks"
| "blossom"
| "win";
export interface WindowInstance {

View File

@@ -6,6 +6,7 @@ import { parseProfileCommand } from "@/lib/profile-parser";
import { parseRelayCommand } from "@/lib/relay-parser";
import { resolveNip05Batch } from "@/lib/nip05";
import { parseChatCommand } from "@/lib/chat-parser";
import { parseBlossomCommand } from "@/lib/blossom-parser";
export interface ManPageEntry {
name: string;
@@ -515,4 +516,63 @@ export const manPages: Record<string, ManPageEntry> = {
category: "Nostr",
defaultProps: {},
},
blossom: {
name: "blossom",
section: "1",
synopsis: "blossom <subcommand> [options]",
description:
"Manage blob storage on Blossom servers. Upload, list, and manage media files using the Blossom protocol (BUD specs). Your Blossom server list is stored in a kind 10063 event.",
options: [
{
flag: "servers",
description:
"Show your configured Blossom servers from kind 10063 event",
},
{
flag: "server <url>",
description: "View info about a specific Blossom server",
},
{
flag: "upload",
description:
"Open file upload dialog to upload files to your Blossom servers",
},
{
flag: "list [pubkey]",
description:
"List blobs uploaded by a user. Supports npub, hex, NIP-05 (user@domain.com), or $me",
},
{
flag: "blob <sha256> [server]",
description:
"View details and preview of a specific blob by its SHA256 hash",
},
{
flag: "mirror <url> <server>",
description: "Mirror a blob from a URL to another Blossom server",
},
{
flag: "delete <sha256> <server>",
description: "Delete a blob from a Blossom server",
},
],
examples: [
"blossom Show your Blossom servers",
"blossom servers Show your Blossom servers",
"blossom server blossom.primal.net View specific server info",
"blossom upload Open file upload dialog",
"blossom list List your uploaded blobs",
"blossom list fiatjaf.com List blobs for a NIP-05 user",
"blossom list npub1... List blobs for another user",
"blossom blob abc123... View blob details",
"blossom mirror https://... cdn.example.com Mirror blob to server",
],
seeAlso: ["profile"],
appId: "blossom",
category: "Nostr",
argParser: async (args: string[], activeAccountPubkey?: string) => {
return await parseBlossomCommand(args, activeAccountPubkey);
},
defaultProps: { subcommand: "servers" },
},
};