mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 16:37:06 +02:00
* 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>
229 lines
5.8 KiB
TypeScript
229 lines
5.8 KiB
TypeScript
/**
|
|
* Blossom Command Parser
|
|
*
|
|
* Parses arguments for the blossom command with subcommands:
|
|
* - servers: Show/manage user's Blossom server list
|
|
* - server <url>: View info about a specific Blossom server
|
|
* - upload: Upload a file (handled by UI file picker)
|
|
* - list [pubkey]: List blobs for a user
|
|
* - blob <sha256> [server]: View a specific blob
|
|
* - mirror <url> <server>: Mirror a blob to another server
|
|
* - delete <sha256> <server>: Delete a blob from a server
|
|
*/
|
|
|
|
import { nip19 } from "nostr-tools";
|
|
import { isNip05, resolveNip05 } from "./nip05";
|
|
import { isValidHexPubkey, normalizeHex } from "./nostr-validation";
|
|
|
|
export type BlossomSubcommand =
|
|
| "servers"
|
|
| "server"
|
|
| "upload"
|
|
| "list"
|
|
| "blob"
|
|
| "mirror"
|
|
| "delete";
|
|
|
|
export interface BlossomCommandResult {
|
|
subcommand: BlossomSubcommand;
|
|
// For 'blob' and 'delete' subcommands
|
|
sha256?: string;
|
|
serverUrl?: string;
|
|
// For 'list' subcommand
|
|
pubkey?: string;
|
|
// For 'mirror' subcommand
|
|
sourceUrl?: string;
|
|
targetServer?: string;
|
|
}
|
|
|
|
/**
|
|
* Normalize a server URL (add https:// if missing)
|
|
*/
|
|
function normalizeServerUrl(url: string): string {
|
|
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
return url;
|
|
}
|
|
return `https://${url}`;
|
|
}
|
|
|
|
/**
|
|
* Resolve a pubkey from various formats (npub, nprofile, hex, NIP-05, $me)
|
|
*/
|
|
async function resolvePubkey(
|
|
input: string,
|
|
activeAccountPubkey?: string,
|
|
): Promise<string | undefined> {
|
|
// Handle $me alias
|
|
if (input === "$me") {
|
|
return activeAccountPubkey;
|
|
}
|
|
|
|
// Handle hex pubkey
|
|
if (isValidHexPubkey(input)) {
|
|
return normalizeHex(input);
|
|
}
|
|
|
|
// Handle npub
|
|
if (input.startsWith("npub1")) {
|
|
try {
|
|
const decoded = nip19.decode(input);
|
|
if (decoded.type === "npub") {
|
|
return decoded.data;
|
|
}
|
|
} catch {
|
|
// Invalid npub
|
|
}
|
|
}
|
|
|
|
// Handle nprofile
|
|
if (input.startsWith("nprofile1")) {
|
|
try {
|
|
const decoded = nip19.decode(input);
|
|
if (decoded.type === "nprofile") {
|
|
return decoded.data.pubkey;
|
|
}
|
|
} catch {
|
|
// Invalid nprofile
|
|
}
|
|
}
|
|
|
|
// Handle NIP-05 identifier (user@domain.com or domain.com)
|
|
if (isNip05(input)) {
|
|
const pubkey = await resolveNip05(input);
|
|
if (pubkey) {
|
|
return pubkey;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Parse blossom command arguments
|
|
*
|
|
* Usage:
|
|
* blossom servers - Show your Blossom servers
|
|
* blossom server <url> - View info about a specific server
|
|
* blossom upload - Open upload dialog
|
|
* blossom list [pubkey] - List blobs (defaults to $me)
|
|
* blossom blob <sha256> [server] - View blob details
|
|
* blossom mirror <url> <server> - Mirror blob to server
|
|
* blossom delete <sha256> <server> - Delete blob from server
|
|
*/
|
|
export async function parseBlossomCommand(
|
|
args: string[],
|
|
activeAccountPubkey?: string,
|
|
): Promise<BlossomCommandResult> {
|
|
// Default to 'servers' if no subcommand
|
|
if (args.length === 0) {
|
|
return { subcommand: "servers" };
|
|
}
|
|
|
|
const subcommand = args[0].toLowerCase();
|
|
|
|
switch (subcommand) {
|
|
case "servers":
|
|
return { subcommand: "servers" };
|
|
|
|
case "server": {
|
|
// View info about a specific Blossom server
|
|
if (args.length < 2) {
|
|
throw new Error("Server URL required. Usage: blossom server <url>");
|
|
}
|
|
return {
|
|
subcommand: "server",
|
|
serverUrl: normalizeServerUrl(args[1]),
|
|
};
|
|
}
|
|
|
|
case "upload":
|
|
return { subcommand: "upload" };
|
|
|
|
case "list":
|
|
case "ls": {
|
|
// Default to active account if no pubkey specified
|
|
const pubkeyArg = args[1];
|
|
let pubkey: string | undefined;
|
|
|
|
if (pubkeyArg) {
|
|
pubkey = await resolvePubkey(pubkeyArg, activeAccountPubkey);
|
|
if (!pubkey) {
|
|
throw new Error(
|
|
`Invalid pubkey format: ${pubkeyArg}. Use npub, nprofile, hex, user@domain.com, or $me`,
|
|
);
|
|
}
|
|
} else {
|
|
pubkey = activeAccountPubkey;
|
|
}
|
|
|
|
return {
|
|
subcommand: "list",
|
|
pubkey,
|
|
};
|
|
}
|
|
|
|
case "blob":
|
|
case "view": {
|
|
if (args.length < 2) {
|
|
throw new Error(
|
|
"SHA256 hash required. Usage: blossom blob <sha256> [server]",
|
|
);
|
|
}
|
|
const sha256 = args[1].toLowerCase();
|
|
if (!/^[0-9a-f]{64}$/.test(sha256)) {
|
|
throw new Error("Invalid SHA256 hash. Must be 64 hex characters.");
|
|
}
|
|
return {
|
|
subcommand: "blob",
|
|
sha256,
|
|
serverUrl: args[2] ? normalizeServerUrl(args[2]) : undefined,
|
|
};
|
|
}
|
|
|
|
case "mirror": {
|
|
if (args.length < 3) {
|
|
throw new Error(
|
|
"Source URL and target server required. Usage: blossom mirror <url> <server>",
|
|
);
|
|
}
|
|
return {
|
|
subcommand: "mirror",
|
|
sourceUrl: args[1],
|
|
targetServer: normalizeServerUrl(args[2]),
|
|
};
|
|
}
|
|
|
|
case "delete":
|
|
case "rm": {
|
|
if (args.length < 3) {
|
|
throw new Error(
|
|
"SHA256 hash and server required. Usage: blossom delete <sha256> <server>",
|
|
);
|
|
}
|
|
const sha256 = args[1].toLowerCase();
|
|
if (!/^[0-9a-f]{64}$/.test(sha256)) {
|
|
throw new Error("Invalid SHA256 hash. Must be 64 hex characters.");
|
|
}
|
|
return {
|
|
subcommand: "delete",
|
|
sha256,
|
|
serverUrl: normalizeServerUrl(args[2]),
|
|
};
|
|
}
|
|
|
|
default:
|
|
throw new Error(
|
|
`Unknown subcommand: ${subcommand}
|
|
|
|
Available subcommands:
|
|
servers Show your configured Blossom servers
|
|
server <url> View info about a specific server
|
|
upload Open file upload dialog
|
|
list [pubkey] List blobs (defaults to your account)
|
|
blob <sha256> [server] View blob details
|
|
mirror <url> <server> Mirror a blob to another server
|
|
delete <sha256> <server> Delete a blob from a server`,
|
|
);
|
|
}
|
|
}
|