@coder/mux-md-client 0.1.0-main.25 → 0.1.0-main.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/ssh-agent.d.cts +1 -1
- package/dist/ssh-agent.d.ts +1 -1
- package/dist/types-DOs5tRco.d.cts +75 -0
- package/dist/types-DOs5tRco.d.ts +75 -0
- package/package.json +1 -1
- package/dist/types-BZMDMbOR.d.cts +0 -40
- package/dist/types-BZMDMbOR.d.ts +0 -40
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/crypto.ts","../src/signing.ts","../src/client.ts"],"sourcesContent":["/**\n * @coder/mux-md-client - Client library for mux.md encrypted file sharing\n *\n * @example\n * ```typescript\n * import { upload, download } from '@coder/mux-md-client';\n *\n * // Upload with optional signing (library handles signature creation)\n * const content = new TextEncoder().encode('# Hello World');\n * const result = await upload(\n * content,\n * { name: 'msg.md', type: 'text/markdown', size: content.length },\n * {\n * sign: {\n * privateKey, // Uint8Array\n * publicKey, // SSH format string, e.g. \"ssh-ed25519 AAAA...\"\n * githubUser: 'username', // optional attribution\n * }\n * }\n * );\n *\n * // Download and verify signature\n * const { data, info, signature } = await download(result.url);\n * if (signature) {\n * // signature.publicKey contains the signer's public key\n * // signature.githubUser contains claimed GitHub username (if provided)\n * }\n * ```\n */\n\n// High-level client operations\nexport {\n buildUrl,\n type DownloadResult,\n deleteFile,\n download,\n getMeta,\n parseUrl,\n type SetExpirationOptions,\n type SetExpirationResult,\n type SignOptions,\n setExpiration,\n type UploadOptions,\n type UploadResult,\n upload,\n} from './client';\n// Low-level crypto (for advanced use)\nexport {\n base64Decode,\n base64Encode,\n base64UrlDecode,\n base64UrlEncode,\n decrypt,\n deriveKey,\n encrypt,\n generateId,\n generateIV,\n generateKey,\n generateMutateKey,\n generateSalt,\n} from './crypto';\n// Signing & verification\nexport {\n createSignatureEnvelope,\n computeFingerprint,\n formatFingerprint,\n type KeyType,\n type ParsedPublicKey,\n parsePublicKey,\n signECDSA,\n signEd25519,\n verifySignature,\n} from './signing';\n\n// Types\nexport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n","/**\n * Cryptographic utilities for mux.md\n *\n * Security parameters:\n * - Key: 80 bits entropy (14 chars base64url)\n * - ID: 30 bits entropy (5 chars base62) - not security critical\n * - Salt: 128 bits (16 bytes) - HKDF salt\n * - IV: 96 bits (12 bytes) for AES-GCM\n */\nconst SALT_BYTES = 16;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 10; // 80 bits\nconst ID_BYTES = 4; // 32 bits, we'll use 30\nconst MUTATE_KEY_BYTES = 16; // 128 bits for mutate key\n\n// Base62 alphabet for URL-safe IDs (no special chars)\nconst BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/**\n * Generate a random file ID (30 bits, 5 chars base62)\n */\nexport function generateId(): string {\n const bytes = new Uint8Array(ID_BYTES);\n crypto.getRandomValues(bytes);\n\n // Convert to number (32 bits), then encode as base62\n const num = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n\n // Use modulo to get 5 base62 characters (covers ~30 bits)\n let result = '';\n let n = num >>> 0; // Ensure unsigned\n for (let i = 0; i < 5; i++) {\n result = BASE62[n % 62] + result;\n n = Math.floor(n / 62);\n }\n\n return result;\n}\n\n/**\n * Generate encryption key material (80 bits, 14 chars base64url)\n */\nexport function generateKey(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate mutate key (128 bits, 22 chars base64url)\n * Used for mutation operations: delete, set expiration\n */\nexport function generateMutateKey(): string {\n const bytes = new Uint8Array(MUTATE_KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate random salt for HKDF (128 bits)\n */\nexport function generateSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_BYTES);\n crypto.getRandomValues(salt);\n return salt;\n}\n\n/**\n * Generate random IV for AES-GCM (96 bits)\n */\nexport function generateIV(): Uint8Array {\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n return iv;\n}\n\n/**\n * Derive AES-256 key from key material using HKDF\n *\n * HKDF is the correct choice for deriving keys from high-entropy\n * random key material. Unlike PBKDF2, it doesn't need iterations\n * since we're not stretching a weak password.\n */\nexport async function deriveKey(\n keyMaterial: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n // Import the raw key material\n const rawKey = base64UrlDecode(keyMaterial);\n const baseKey = await crypto.subtle.importKey(\n 'raw',\n rawKey.buffer as ArrayBuffer,\n 'HKDF',\n false,\n ['deriveKey'],\n );\n\n // Derive AES-256 key using HKDF\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n salt: salt.buffer as ArrayBuffer,\n info: new Uint8Array(0), // No additional context needed\n hash: 'SHA-256',\n },\n baseKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n/**\n * Encrypt data using AES-256-GCM\n */\nexport async function encrypt(\n data: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n data.buffer as ArrayBuffer,\n );\n return new Uint8Array(ciphertext);\n}\n\n/**\n * Decrypt data using AES-256-GCM\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n ciphertext.buffer as ArrayBuffer,\n );\n return new Uint8Array(plaintext);\n}\n\n/**\n * Base64url encode (URL-safe, no padding)\n */\nexport function base64UrlEncode(data: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...data));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Base64url decode\n */\nexport function base64UrlDecode(str: string): Uint8Array {\n // Restore standard base64\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Standard base64 encode\n */\nexport function base64Encode(data: Uint8Array): string {\n return btoa(String.fromCharCode(...data));\n}\n\n/**\n * Standard base64 decode\n */\nexport function base64Decode(str: string): Uint8Array {\n const binary = atob(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * Signing and verification utilities for mux.md\n *\n * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.\n */\n\nimport { p256, p384, p521 } from '@noble/curves/nist.js';\nimport * as ed from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport type { SignatureEnvelope } from './types';\n\n// Configure @noble/ed25519 to use sha512 from @noble/hashes\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ed.etc as any).sha512Sync = (...m: Uint8Array[]) =>\n sha512(ed.etc.concatBytes(...m));\n\n// ----- Types -----\n\n/** Supported key types */\nexport type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';\n\n/** Parsed public key with type and raw bytes */\nexport interface ParsedPublicKey {\n type: KeyType;\n keyBytes: Uint8Array;\n}\n\n// SSH key type identifiers\nconst SSH_KEY_TYPES: Record<string, KeyType> = {\n 'ssh-ed25519': 'ed25519',\n 'ecdsa-sha2-nistp256': 'ecdsa-p256',\n 'ecdsa-sha2-nistp384': 'ecdsa-p384',\n 'ecdsa-sha2-nistp521': 'ecdsa-p521',\n};\n\n// ----- SSH Key Parsing -----\n\n/**\n * Read a length-prefixed string from SSH wire format\n */\nfunction readSSHString(\n data: Uint8Array,\n offset: number,\n): { value: Uint8Array; nextOffset: number } {\n const view = new DataView(data.buffer, data.byteOffset);\n const len = view.getUint32(offset);\n const value = data.slice(offset + 4, offset + 4 + len);\n return { value, nextOffset: offset + 4 + len };\n}\n\n/**\n * Decode standard base64 string to Uint8Array\n */\nfunction base64Decode(str: string): Uint8Array {\n // Handle base64url as well\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Parse an SSH public key and extract the key bytes and type.\n * Supports formats:\n * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]\n * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]\n * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]\n * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]\n * - Raw base64 (32 bytes when decoded = Ed25519)\n */\nexport function parsePublicKey(keyString: string): ParsedPublicKey {\n const trimmed = keyString.trim();\n\n // Check for SSH format by looking for known key type prefixes\n for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {\n if (trimmed.startsWith(`${sshType} `)) {\n const parts = trimmed.split(' ');\n if (parts.length < 2) {\n throw new Error('Invalid SSH key format');\n }\n const keyData = base64Decode(parts[1]);\n\n // SSH key format: [4 byte len][type string][4 byte len][key data...]\n // For ECDSA: [4 byte len][type][4 byte len][curve name][4 byte len][point]\n const { value: typeBytes, nextOffset: afterType } = readSSHString(\n keyData,\n 0,\n );\n const typeStr = new TextDecoder().decode(typeBytes);\n\n if (typeStr !== sshType) {\n throw new Error(\n `Key type mismatch: expected ${sshType}, got ${typeStr}`,\n );\n }\n\n if (keyType === 'ed25519') {\n // Ed25519: [type][32 byte key]\n const { value: rawKey } = readSSHString(keyData, afterType);\n if (rawKey.length !== 32) {\n throw new Error('Invalid Ed25519 key length');\n }\n return { type: 'ed25519', keyBytes: rawKey };\n }\n // ECDSA: [type][curve name][point]\n const { nextOffset: afterCurve } = readSSHString(keyData, afterType);\n const { value: point } = readSSHString(keyData, afterCurve);\n return { type: keyType, keyBytes: point };\n }\n }\n\n // Try raw base64\n const decoded = base64Decode(trimmed);\n if (decoded.length === 32) {\n return { type: 'ed25519', keyBytes: decoded };\n }\n\n throw new Error('Unsupported public key format');\n}\n\n// ----- Signing -----\n\n/**\n * Sign a message with Ed25519 private key.\n * @param message - The message bytes to sign\n * @param privateKey - 32-byte Ed25519 private key\n * @returns Base64-encoded signature (64 bytes)\n */\nexport async function signEd25519(\n message: Uint8Array,\n privateKey: Uint8Array,\n): Promise<string> {\n const sig = await ed.signAsync(message, privateKey);\n return btoa(String.fromCharCode(...sig));\n}\n\n/**\n * Sign a message with ECDSA private key (P-256/384/521).\n * @param message - The message bytes to sign (will be hashed)\n * @param privateKey - ECDSA private key bytes\n * @param curve - Which curve to use\n * @returns Base64-encoded signature\n */\nexport function signECDSA(\n message: Uint8Array,\n privateKey: Uint8Array,\n curve: 'p256' | 'p384' | 'p521',\n): string {\n const curves = { p256, p384, p521 };\n // In @noble/curves v2, sign() returns a Uint8Array directly (compact r||s format)\n const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });\n return btoa(String.fromCharCode(...sigBytes));\n}\n\n/**\n * Helper: Create a SignatureEnvelope from content + private key.\n * This is the high-level API for signing before upload.\n *\n * @param content - The content bytes to sign\n * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)\n * @param publicKey - SSH format public key string (e.g., \"ssh-ed25519 AAAA...\")\n * @param options - Optional GitHub username for attribution\n * @returns SignatureEnvelope ready for upload\n */\nexport async function createSignatureEnvelope(\n content: Uint8Array,\n privateKey: Uint8Array,\n publicKey: string,\n options?: { githubUser?: string },\n): Promise<SignatureEnvelope> {\n const parsed = parsePublicKey(publicKey);\n let sig: string;\n\n if (parsed.type === 'ed25519') {\n sig = await signEd25519(content, privateKey);\n } else {\n const curve = parsed.type.replace('ecdsa-', '') as 'p256' | 'p384' | 'p521';\n sig = signECDSA(content, privateKey, curve);\n }\n\n return {\n sig,\n publicKey,\n githubUser: options?.githubUser,\n };\n}\n\n// ----- Verification -----\n\n/**\n * Verify a signature using the appropriate algorithm based on key type.\n * For Ed25519: signature is raw 64 bytes\n * For ECDSA: signature is DER-encoded or raw r||s format\n *\n * @param parsedKey - Parsed public key (from parsePublicKey)\n * @param message - Original message bytes\n * @param signature - Signature bytes (not base64)\n * @returns true if signature is valid\n */\nexport async function verifySignature(\n parsedKey: ParsedPublicKey,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n try {\n switch (parsedKey.type) {\n case 'ed25519':\n return await ed.verifyAsync(signature, message, parsedKey.keyBytes);\n\n case 'ecdsa-p256':\n return p256.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p384':\n return p384.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p521':\n return p521.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n// ----- Fingerprint -----\n\n/**\n * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)\n * @param publicKey - Raw public key bytes\n * @returns Fingerprint string like \"SHA256:abc123...\"\n */\nexport async function computeFingerprint(\n publicKey: Uint8Array,\n): Promise<string> {\n const hash = await crypto.subtle.digest(\n 'SHA-256',\n publicKey.buffer as ArrayBuffer,\n );\n const hashArray = new Uint8Array(hash);\n const base64 = btoa(String.fromCharCode(...hashArray));\n return `SHA256:${base64.replace(/=+$/, '')}`;\n}\n\n/**\n * Format a fingerprint for nice display.\n * Converts base64 fingerprint to uppercase hex groups like \"DEAD BEEF 1234 5678\"\n */\nexport function formatFingerprint(fingerprint: string): string {\n // Remove \"SHA256:\" prefix if present\n const base64Part = fingerprint.startsWith('SHA256:')\n ? fingerprint.slice(7)\n : fingerprint;\n\n // Decode base64 to bytes, then to hex\n try {\n const binary = atob(base64Part);\n const hex = Array.from(binary)\n .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join('')\n .toUpperCase();\n\n // Take first 16 chars (8 bytes) and format as \"DEAD BEEF 1234 5678\"\n const short = hex.slice(0, 16);\n return short.match(/.{4}/g)?.join(' ') || short;\n } catch {\n // Fallback: just show first part of the fingerprint\n return fingerprint.slice(0, 16).toUpperCase();\n }\n}\n","/**\n * mux.md Client Library\n *\n * Reference implementation for encrypting, uploading, downloading, and decrypting files.\n * Works in both Node.js (with webcrypto) and browser environments.\n */\n\nimport {\n base64Decode,\n base64Encode,\n decrypt,\n deriveKey,\n encrypt,\n generateIV,\n generateKey,\n generateSalt,\n} from './crypto';\nimport { createSignatureEnvelope } from './signing';\nimport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n\n// Internal types (not exported from package)\ninterface UploadResponse {\n id: string;\n url: string;\n mutateKey: string;\n expiresAt?: number;\n}\n\ninterface MetaResponse {\n salt: string;\n iv: string;\n encryptedMeta: string;\n size: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://mux.md';\n\n// ----- Internal helpers -----\n\nfunction assert(condition: unknown, message: string): asserts condition {\n if (!condition) {\n throw new Error(message);\n }\n}\n\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction decodeUtf8Strict(data: Uint8Array): string {\n try {\n // We embed signed payloads as JSON with `content: string`, so we must be able to\n // losslessly decode the original bytes.\n const decoded = new TextDecoder('utf-8', {\n fatal: true,\n ignoreBOM: true,\n }).decode(data);\n\n // Defensive: ensure round-trip matches original bytes.\n const reencoded = new TextEncoder().encode(decoded);\n assert(\n bytesEqual(data, reencoded),\n 'Signed uploads require UTF-8 text content',\n );\n\n return decoded;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message === 'Signed uploads require UTF-8 text content'\n ) {\n throw error;\n }\n throw new Error('Signed uploads require UTF-8 text content');\n }\n}\n\nfunction assertSignatureEnvelope(\n value: unknown,\n): asserts value is SignatureEnvelope {\n if (!value || typeof value !== 'object') {\n throw new Error('Invalid SignatureEnvelope');\n }\n\n const env = value as Partial<SignatureEnvelope>;\n if (typeof env.sig !== 'string' || env.sig.length === 0) {\n throw new Error('Invalid SignatureEnvelope.sig');\n }\n\n if (typeof env.publicKey !== 'string' || env.publicKey.length === 0) {\n throw new Error('Invalid SignatureEnvelope.publicKey');\n }\n\n if (env.githubUser !== undefined && typeof env.githubUser !== 'string') {\n throw new Error('Invalid SignatureEnvelope.githubUser');\n }\n}\n\n/** Options for signing content during upload */\nexport type SignOptions =\n | {\n /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */\n privateKey: Uint8Array;\n /** SSH format public key string (e.g., \"ssh-ed25519 AAAA...\") */\n publicKey: string;\n /** Optional GitHub username for attribution */\n githubUser?: string;\n }\n | {\n /**\n * Custom signer function.\n * Useful when the private key lives outside this process (e.g., an SSH agent).\n */\n signer: (data: Uint8Array) => Promise<SignatureEnvelope>;\n };\n\nexport interface UploadOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n /** Expiration time (unix timestamp ms, ISO date string, or Date object) */\n expiresAt?: number | string | Date;\n /**\n * Precomputed signature envelope to embed in the encrypted payload.\n * Takes precedence over `sign`.\n */\n signature?: SignatureEnvelope;\n /**\n * Sign the content.\n *\n * When provided, the decrypted blob becomes JSON (SignedPayload) containing both\n * the content string and the signature envelope.\n */\n sign?: SignOptions;\n}\n\nexport interface UploadResult {\n /** Full URL with encryption key in fragment */\n url: string;\n /** File ID (without key) */\n id: string;\n /** Encryption key (base64url) */\n key: string;\n /** Mutate key (base64url) - store this to mutate (delete, change expiration) the file later */\n mutateKey: string;\n /** Expiration timestamp (ms), if set */\n expiresAt?: number;\n}\n\nexport interface DownloadResult {\n /** Decrypted file content */\n data: Uint8Array;\n /** Original file info (name, type, size) */\n info: FileInfo;\n /** Decrypted signature envelope (if present) */\n signature?: SignatureEnvelope;\n}\n\n/**\n * Encrypt and upload a file to mux.md\n *\n * @param data - File contents as Uint8Array\n * @param fileInfo - Original file metadata (name, type, size)\n * @param options - Upload options (including optional signature)\n * @returns Upload result with URL containing encryption key\n */\nexport async function upload(\n data: Uint8Array,\n fileInfo: FileInfo,\n options: UploadOptions = {},\n): Promise<UploadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Generate encryption parameters\n const keyMaterial = generateKey();\n const salt = generateSalt();\n const iv = generateIV();\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const wantsSignature =\n options.signature !== undefined || options.sign !== undefined;\n\n // Signed uploads embed plaintext as JSON with `content: string`.\n // That requires the original bytes to be valid UTF-8.\n const signedContent = wantsSignature ? decodeUtf8Strict(data) : undefined;\n\n // Resolve the signature envelope.\n let signatureEnvelope: SignatureEnvelope | undefined;\n\n if (options.signature !== undefined) {\n assertSignatureEnvelope(options.signature);\n signatureEnvelope = options.signature;\n } else if (options.sign) {\n if ('signer' in options.sign) {\n const envelope = await options.sign.signer(data);\n assertSignatureEnvelope(envelope);\n signatureEnvelope = envelope;\n } else {\n signatureEnvelope = await createSignatureEnvelope(\n data,\n options.sign.privateKey,\n options.sign.publicKey,\n { githubUser: options.sign.githubUser },\n );\n assertSignatureEnvelope(signatureEnvelope);\n }\n }\n\n if (wantsSignature) {\n assert(\n signatureEnvelope !== undefined,\n 'Signature requested but no signature envelope was produced',\n );\n }\n\n // Build plaintext - either signed (JSON) or raw content\n let plaintext: Uint8Array;\n if (signatureEnvelope) {\n assert(signedContent !== undefined, 'Signed content string missing');\n\n // Signed format: JSON with content + signature\n const signed: SignedPayload = {\n content: signedContent,\n sig: signatureEnvelope,\n };\n plaintext = new TextEncoder().encode(JSON.stringify(signed));\n } else {\n // Unsigned: raw content bytes\n plaintext = data;\n }\n\n // Single encryption for the payload\n const payload = await encrypt(plaintext, cryptoKey, iv);\n\n // Encrypt file metadata (always the same way)\n const metaJson = JSON.stringify(fileInfo);\n const metaBytes = new TextEncoder().encode(metaJson);\n const metaIv = generateIV();\n const encryptedMeta = await encrypt(metaBytes, cryptoKey, metaIv);\n\n // Prepare upload metadata\n const uploadMeta: UploadMeta = {\n salt: base64Encode(salt),\n iv: base64Encode(iv),\n encryptedMeta: base64Encode(new Uint8Array([...metaIv, ...encryptedMeta])),\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Mux-Meta': btoa(JSON.stringify(uploadMeta)),\n };\n\n // Add expiration header if specified (convert to ISO 8601)\n if (options.expiresAt !== undefined) {\n let expiresDate: Date;\n if (options.expiresAt instanceof Date) {\n expiresDate = options.expiresAt;\n } else if (typeof options.expiresAt === 'string') {\n expiresDate = new Date(options.expiresAt);\n } else {\n expiresDate = new Date(options.expiresAt);\n }\n headers['X-Mux-Expires'] = expiresDate.toISOString();\n }\n\n // Upload to server\n const response = await fetch(`${baseUrl}/`, {\n method: 'POST',\n headers,\n body: payload.buffer as ArrayBuffer,\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Upload failed' }));\n throw new Error((error as { error: string }).error || 'Upload failed');\n }\n\n const result: UploadResponse = await response.json();\n\n return {\n url: `${baseUrl}/${result.id}#${keyMaterial}`,\n id: result.id,\n key: keyMaterial,\n mutateKey: result.mutateKey,\n ...(result.expiresAt && { expiresAt: result.expiresAt }),\n };\n}\n\n/**\n * Download and decrypt a file from mux.md\n *\n * @param url - Full URL with encryption key in fragment, or just the ID\n * @param key - Encryption key (required if url doesn't contain fragment)\n * @param options - Download options\n * @returns Decrypted file data and metadata\n */\nexport async function download(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<DownloadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n // Full URL with fragment\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1); // Remove leading /\n keyMaterial = urlObj.hash.slice(1); // Remove leading #\n } else if (url.includes('/')) {\n // URL path without fragment\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n // Just the ID\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Download encrypted file\n const response = await fetch(`${baseUrl}/${id}`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Download failed' }));\n throw new Error((error as { error: string }).error || 'Download failed');\n }\n\n // Parse metadata header\n const metaHeader = response.headers.get('X-Mux-Meta');\n if (!metaHeader) {\n throw new Error('Missing metadata header');\n }\n\n const uploadMeta: UploadMeta = JSON.parse(atob(metaHeader));\n\n // Get encrypted data\n const encryptedData = new Uint8Array(await response.arrayBuffer());\n\n // Derive decryption key\n const salt = base64Decode(uploadMeta.salt);\n const iv = base64Decode(uploadMeta.iv);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Decrypt metadata (same for both formats)\n const encryptedMetaWithIv = base64Decode(uploadMeta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n // Decrypt the payload\n const decrypted = await decrypt(encryptedData, cryptoKey, iv);\n\n // Check if decrypted content is signed (JSON with content + sig fields)\n if (decrypted[0] === 0x7b) {\n // '{' character - might be signed JSON\n try {\n const jsonStr = new TextDecoder().decode(decrypted);\n const parsed = JSON.parse(jsonStr);\n\n if (typeof parsed.content === 'string' && parsed.sig) {\n // Signed format\n const data = new TextEncoder().encode(parsed.content);\n const signature: SignatureEnvelope = parsed.sig;\n return { data, info, signature };\n }\n } catch {\n // Not valid JSON - treat as raw content\n }\n }\n\n // Unsigned content (raw bytes)\n return { data: decrypted, info };\n}\n\n/**\n * Get file metadata without downloading the full file\n *\n * @param url - Full URL or ID\n * @param key - Encryption key (required to decrypt metadata)\n * @param options - Request options\n * @returns Decrypted file info and server metadata\n */\nexport async function getMeta(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<{ info: FileInfo; size: number }> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1);\n keyMaterial = urlObj.hash.slice(1);\n } else if (url.includes('/')) {\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Fetch metadata\n const response = await fetch(`${baseUrl}/${id}/meta`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Request failed' }));\n throw new Error((error as { error: string }).error || 'Request failed');\n }\n\n const meta: MetaResponse = await response.json();\n\n // Derive key and decrypt metadata\n const salt = base64Decode(meta.salt);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const encryptedMetaWithIv = base64Decode(meta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n return {\n info,\n size: meta.size,\n };\n}\n\n/**\n * Parse a mux.md URL into its components\n */\nexport function parseUrl(url: string): { id: string; key: string } | null {\n try {\n const urlObj = new URL(url);\n if (!urlObj.hash) return null;\n\n const id = urlObj.pathname.slice(1);\n const key = urlObj.hash.slice(1);\n\n if (!id || !key) return null;\n\n return { id, key };\n } catch {\n return null;\n }\n}\n\n/**\n * Build a mux.md URL from components\n */\nexport function buildUrl(\n id: string,\n key: string,\n baseUrl = DEFAULT_BASE_URL,\n): string {\n return `${baseUrl}/${id}#${key}`;\n}\n\n/**\n * Delete a file from mux.md using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param options - Request options\n */\nexport async function deleteFile(\n id: string,\n mutateKey: string,\n options: { baseUrl?: string } = {},\n): Promise<void> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'DELETE',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Delete failed' }));\n throw new Error((error as { error: string }).error || 'Delete failed');\n }\n}\n\nexport interface SetExpirationOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n}\n\nexport interface SetExpirationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** File ID */\n id: string;\n /** New expiration timestamp (ms), or undefined if expiration was removed */\n expiresAt?: number;\n}\n\n/**\n * Set or remove the expiration of a file using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param expiresAt - New expiration time (unix timestamp ms, ISO date string, Date object, or \"never\" to remove expiration)\n * @param options - Request options\n * @returns Result with new expiration info\n */\nexport async function setExpiration(\n id: string,\n mutateKey: string,\n expiresAt: number | string | Date | 'never',\n options: SetExpirationOptions = {},\n): Promise<SetExpirationResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Convert expiration to ISO 8601 string\n let expiresHeader: string;\n if (expiresAt === 'never') {\n expiresHeader = 'never';\n } else if (expiresAt instanceof Date) {\n expiresHeader = expiresAt.toISOString();\n } else if (typeof expiresAt === 'string') {\n expiresHeader = new Date(expiresAt).toISOString();\n } else {\n expiresHeader = new Date(expiresAt).toISOString();\n }\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'PATCH',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n 'X-Mux-Expires': expiresHeader,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Set expiration failed' }));\n throw new Error(\n (error as { error: string }).error || 'Set expiration failed',\n );\n }\n\n return response.json();\n}\n"],"mappings":"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","names":["base64Decode"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/crypto.ts","../src/signing.ts","../src/client.ts"],"sourcesContent":["/**\n * @coder/mux-md-client - Client library for mux.md encrypted file sharing\n *\n * @example\n * ```typescript\n * import { upload, download } from '@coder/mux-md-client';\n *\n * // Upload with optional signing (library handles signature creation)\n * const content = new TextEncoder().encode('# Hello World');\n * const result = await upload(\n * content,\n * { name: 'msg.md', type: 'text/markdown', size: content.length },\n * {\n * sign: {\n * privateKey, // Uint8Array\n * publicKey, // SSH format string, e.g. \"ssh-ed25519 AAAA...\"\n * githubUser: 'username', // optional attribution\n * }\n * }\n * );\n *\n * // Download and verify signature\n * const { data, info, signature } = await download(result.url);\n * if (signature) {\n * // signature.publicKey contains the signer's public key\n * // signature.githubUser contains claimed GitHub username (if provided)\n * }\n * ```\n */\n\n// High-level client operations\nexport {\n buildUrl,\n type DownloadResult,\n deleteFile,\n download,\n getMeta,\n parseUrl,\n type SetExpirationOptions,\n type SetExpirationResult,\n type SignOptions,\n setExpiration,\n type UploadOptions,\n type UploadResult,\n upload,\n} from './client';\n// Low-level crypto (for advanced use)\nexport {\n base64Decode,\n base64Encode,\n base64UrlDecode,\n base64UrlEncode,\n decrypt,\n deriveKey,\n encrypt,\n generateId,\n generateIV,\n generateKey,\n generateMutateKey,\n generateSalt,\n} from './crypto';\n// Signing & verification\nexport {\n createSignatureEnvelope,\n computeFingerprint,\n formatFingerprint,\n type KeyType,\n type ParsedPublicKey,\n parsePublicKey,\n signECDSA,\n signEd25519,\n verifySignature,\n} from './signing';\n\n// Types\nexport type {\n ConvoSummary,\n FileInfo,\n LocSummary,\n MuxClientMode,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n","/**\n * Cryptographic utilities for mux.md\n *\n * Security parameters:\n * - Key: 80 bits entropy (14 chars base64url)\n * - ID: 30 bits entropy (5 chars base62) - not security critical\n * - Salt: 128 bits (16 bytes) - HKDF salt\n * - IV: 96 bits (12 bytes) for AES-GCM\n */\nconst SALT_BYTES = 16;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 10; // 80 bits\nconst ID_BYTES = 4; // 32 bits, we'll use 30\nconst MUTATE_KEY_BYTES = 16; // 128 bits for mutate key\n\n// Base62 alphabet for URL-safe IDs (no special chars)\nconst BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/**\n * Generate a random file ID (30 bits, 5 chars base62)\n */\nexport function generateId(): string {\n const bytes = new Uint8Array(ID_BYTES);\n crypto.getRandomValues(bytes);\n\n // Convert to number (32 bits), then encode as base62\n const num = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n\n // Use modulo to get 5 base62 characters (covers ~30 bits)\n let result = '';\n let n = num >>> 0; // Ensure unsigned\n for (let i = 0; i < 5; i++) {\n result = BASE62[n % 62] + result;\n n = Math.floor(n / 62);\n }\n\n return result;\n}\n\n/**\n * Generate encryption key material (80 bits, 14 chars base64url)\n */\nexport function generateKey(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate mutate key (128 bits, 22 chars base64url)\n * Used for mutation operations: delete, set expiration\n */\nexport function generateMutateKey(): string {\n const bytes = new Uint8Array(MUTATE_KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate random salt for HKDF (128 bits)\n */\nexport function generateSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_BYTES);\n crypto.getRandomValues(salt);\n return salt;\n}\n\n/**\n * Generate random IV for AES-GCM (96 bits)\n */\nexport function generateIV(): Uint8Array {\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n return iv;\n}\n\n/**\n * Derive AES-256 key from key material using HKDF\n *\n * HKDF is the correct choice for deriving keys from high-entropy\n * random key material. Unlike PBKDF2, it doesn't need iterations\n * since we're not stretching a weak password.\n */\nexport async function deriveKey(\n keyMaterial: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n // Import the raw key material\n const rawKey = base64UrlDecode(keyMaterial);\n const baseKey = await crypto.subtle.importKey(\n 'raw',\n rawKey.buffer as ArrayBuffer,\n 'HKDF',\n false,\n ['deriveKey'],\n );\n\n // Derive AES-256 key using HKDF\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n salt: salt.buffer as ArrayBuffer,\n info: new Uint8Array(0), // No additional context needed\n hash: 'SHA-256',\n },\n baseKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n/**\n * Encrypt data using AES-256-GCM\n */\nexport async function encrypt(\n data: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n data.buffer as ArrayBuffer,\n );\n return new Uint8Array(ciphertext);\n}\n\n/**\n * Decrypt data using AES-256-GCM\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n ciphertext.buffer as ArrayBuffer,\n );\n return new Uint8Array(plaintext);\n}\n\n/**\n * Base64url encode (URL-safe, no padding)\n */\nexport function base64UrlEncode(data: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...data));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Base64url decode\n */\nexport function base64UrlDecode(str: string): Uint8Array {\n // Restore standard base64\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Standard base64 encode\n */\nexport function base64Encode(data: Uint8Array): string {\n return btoa(String.fromCharCode(...data));\n}\n\n/**\n * Standard base64 decode\n */\nexport function base64Decode(str: string): Uint8Array {\n const binary = atob(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * Signing and verification utilities for mux.md\n *\n * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.\n */\n\nimport { p256, p384, p521 } from '@noble/curves/nist.js';\nimport * as ed from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport type { SignatureEnvelope } from './types';\n\n// Configure @noble/ed25519 to use sha512 from @noble/hashes\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ed.etc as any).sha512Sync = (...m: Uint8Array[]) =>\n sha512(ed.etc.concatBytes(...m));\n\n// ----- Types -----\n\n/** Supported key types */\nexport type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';\n\n/** Parsed public key with type and raw bytes */\nexport interface ParsedPublicKey {\n type: KeyType;\n keyBytes: Uint8Array;\n}\n\n// SSH key type identifiers\nconst SSH_KEY_TYPES: Record<string, KeyType> = {\n 'ssh-ed25519': 'ed25519',\n 'ecdsa-sha2-nistp256': 'ecdsa-p256',\n 'ecdsa-sha2-nistp384': 'ecdsa-p384',\n 'ecdsa-sha2-nistp521': 'ecdsa-p521',\n};\n\n// ----- SSH Key Parsing -----\n\n/**\n * Read a length-prefixed string from SSH wire format\n */\nfunction readSSHString(\n data: Uint8Array,\n offset: number,\n): { value: Uint8Array; nextOffset: number } {\n const view = new DataView(data.buffer, data.byteOffset);\n const len = view.getUint32(offset);\n const value = data.slice(offset + 4, offset + 4 + len);\n return { value, nextOffset: offset + 4 + len };\n}\n\n/**\n * Decode standard base64 string to Uint8Array\n */\nfunction base64Decode(str: string): Uint8Array {\n // Handle base64url as well\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Parse an SSH public key and extract the key bytes and type.\n * Supports formats:\n * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]\n * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]\n * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]\n * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]\n * - Raw base64 (32 bytes when decoded = Ed25519)\n */\nexport function parsePublicKey(keyString: string): ParsedPublicKey {\n const trimmed = keyString.trim();\n\n // Check for SSH format by looking for known key type prefixes\n for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {\n if (trimmed.startsWith(`${sshType} `)) {\n const parts = trimmed.split(' ');\n if (parts.length < 2) {\n throw new Error('Invalid SSH key format');\n }\n const keyData = base64Decode(parts[1]);\n\n // SSH key format: [4 byte len][type string][4 byte len][key data...]\n // For ECDSA: [4 byte len][type][4 byte len][curve name][4 byte len][point]\n const { value: typeBytes, nextOffset: afterType } = readSSHString(\n keyData,\n 0,\n );\n const typeStr = new TextDecoder().decode(typeBytes);\n\n if (typeStr !== sshType) {\n throw new Error(\n `Key type mismatch: expected ${sshType}, got ${typeStr}`,\n );\n }\n\n if (keyType === 'ed25519') {\n // Ed25519: [type][32 byte key]\n const { value: rawKey } = readSSHString(keyData, afterType);\n if (rawKey.length !== 32) {\n throw new Error('Invalid Ed25519 key length');\n }\n return { type: 'ed25519', keyBytes: rawKey };\n }\n // ECDSA: [type][curve name][point]\n const { nextOffset: afterCurve } = readSSHString(keyData, afterType);\n const { value: point } = readSSHString(keyData, afterCurve);\n return { type: keyType, keyBytes: point };\n }\n }\n\n // Try raw base64\n const decoded = base64Decode(trimmed);\n if (decoded.length === 32) {\n return { type: 'ed25519', keyBytes: decoded };\n }\n\n throw new Error('Unsupported public key format');\n}\n\n// ----- Signing -----\n\n/**\n * Sign a message with Ed25519 private key.\n * @param message - The message bytes to sign\n * @param privateKey - 32-byte Ed25519 private key\n * @returns Base64-encoded signature (64 bytes)\n */\nexport async function signEd25519(\n message: Uint8Array,\n privateKey: Uint8Array,\n): Promise<string> {\n const sig = await ed.signAsync(message, privateKey);\n return btoa(String.fromCharCode(...sig));\n}\n\n/**\n * Sign a message with ECDSA private key (P-256/384/521).\n * @param message - The message bytes to sign (will be hashed)\n * @param privateKey - ECDSA private key bytes\n * @param curve - Which curve to use\n * @returns Base64-encoded signature\n */\nexport function signECDSA(\n message: Uint8Array,\n privateKey: Uint8Array,\n curve: 'p256' | 'p384' | 'p521',\n): string {\n const curves = { p256, p384, p521 };\n // In @noble/curves v2, sign() returns a Uint8Array directly (compact r||s format)\n const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });\n return btoa(String.fromCharCode(...sigBytes));\n}\n\n/**\n * Helper: Create a SignatureEnvelope from content + private key.\n * This is the high-level API for signing before upload.\n *\n * @param content - The content bytes to sign\n * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)\n * @param publicKey - SSH format public key string (e.g., \"ssh-ed25519 AAAA...\")\n * @param options - Optional GitHub username for attribution\n * @returns SignatureEnvelope ready for upload\n */\nexport async function createSignatureEnvelope(\n content: Uint8Array,\n privateKey: Uint8Array,\n publicKey: string,\n options?: { githubUser?: string },\n): Promise<SignatureEnvelope> {\n const parsed = parsePublicKey(publicKey);\n let sig: string;\n\n if (parsed.type === 'ed25519') {\n sig = await signEd25519(content, privateKey);\n } else {\n const curve = parsed.type.replace('ecdsa-', '') as 'p256' | 'p384' | 'p521';\n sig = signECDSA(content, privateKey, curve);\n }\n\n return {\n sig,\n publicKey,\n githubUser: options?.githubUser,\n };\n}\n\n// ----- Verification -----\n\n/**\n * Verify a signature using the appropriate algorithm based on key type.\n * For Ed25519: signature is raw 64 bytes\n * For ECDSA: signature is DER-encoded or raw r||s format\n *\n * @param parsedKey - Parsed public key (from parsePublicKey)\n * @param message - Original message bytes\n * @param signature - Signature bytes (not base64)\n * @returns true if signature is valid\n */\nexport async function verifySignature(\n parsedKey: ParsedPublicKey,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n try {\n switch (parsedKey.type) {\n case 'ed25519':\n return await ed.verifyAsync(signature, message, parsedKey.keyBytes);\n\n case 'ecdsa-p256':\n return p256.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p384':\n return p384.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p521':\n return p521.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n// ----- Fingerprint -----\n\n/**\n * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)\n * @param publicKey - Raw public key bytes\n * @returns Fingerprint string like \"SHA256:abc123...\"\n */\nexport async function computeFingerprint(\n publicKey: Uint8Array,\n): Promise<string> {\n const hash = await crypto.subtle.digest(\n 'SHA-256',\n publicKey.buffer as ArrayBuffer,\n );\n const hashArray = new Uint8Array(hash);\n const base64 = btoa(String.fromCharCode(...hashArray));\n return `SHA256:${base64.replace(/=+$/, '')}`;\n}\n\n/**\n * Format a fingerprint for nice display.\n * Converts base64 fingerprint to uppercase hex groups like \"DEAD BEEF 1234 5678\"\n */\nexport function formatFingerprint(fingerprint: string): string {\n // Remove \"SHA256:\" prefix if present\n const base64Part = fingerprint.startsWith('SHA256:')\n ? fingerprint.slice(7)\n : fingerprint;\n\n // Decode base64 to bytes, then to hex\n try {\n const binary = atob(base64Part);\n const hex = Array.from(binary)\n .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join('')\n .toUpperCase();\n\n // Take first 16 chars (8 bytes) and format as \"DEAD BEEF 1234 5678\"\n const short = hex.slice(0, 16);\n return short.match(/.{4}/g)?.join(' ') || short;\n } catch {\n // Fallback: just show first part of the fingerprint\n return fingerprint.slice(0, 16).toUpperCase();\n }\n}\n","/**\n * mux.md Client Library\n *\n * Reference implementation for encrypting, uploading, downloading, and decrypting files.\n * Works in both Node.js (with webcrypto) and browser environments.\n */\n\nimport {\n base64Decode,\n base64Encode,\n decrypt,\n deriveKey,\n encrypt,\n generateIV,\n generateKey,\n generateSalt,\n} from './crypto';\nimport { createSignatureEnvelope } from './signing';\nimport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n\n// Internal types (not exported from package)\ninterface UploadResponse {\n id: string;\n url: string;\n mutateKey: string;\n expiresAt?: number;\n}\n\ninterface MetaResponse {\n salt: string;\n iv: string;\n encryptedMeta: string;\n size: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://mux.md';\n\n// ----- Internal helpers -----\n\nfunction assert(condition: unknown, message: string): asserts condition {\n if (!condition) {\n throw new Error(message);\n }\n}\n\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction decodeUtf8Strict(data: Uint8Array): string {\n try {\n // We embed signed payloads as JSON with `content: string`, so we must be able to\n // losslessly decode the original bytes.\n const decoded = new TextDecoder('utf-8', {\n fatal: true,\n ignoreBOM: true,\n }).decode(data);\n\n // Defensive: ensure round-trip matches original bytes.\n const reencoded = new TextEncoder().encode(decoded);\n assert(\n bytesEqual(data, reencoded),\n 'Signed uploads require UTF-8 text content',\n );\n\n return decoded;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message === 'Signed uploads require UTF-8 text content'\n ) {\n throw error;\n }\n throw new Error('Signed uploads require UTF-8 text content');\n }\n}\n\nfunction assertSignatureEnvelope(\n value: unknown,\n): asserts value is SignatureEnvelope {\n if (!value || typeof value !== 'object') {\n throw new Error('Invalid SignatureEnvelope');\n }\n\n const env = value as Partial<SignatureEnvelope>;\n if (typeof env.sig !== 'string' || env.sig.length === 0) {\n throw new Error('Invalid SignatureEnvelope.sig');\n }\n\n if (typeof env.publicKey !== 'string' || env.publicKey.length === 0) {\n throw new Error('Invalid SignatureEnvelope.publicKey');\n }\n\n if (env.githubUser !== undefined && typeof env.githubUser !== 'string') {\n throw new Error('Invalid SignatureEnvelope.githubUser');\n }\n}\n\n/** Options for signing content during upload */\nexport type SignOptions =\n | {\n /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */\n privateKey: Uint8Array;\n /** SSH format public key string (e.g., \"ssh-ed25519 AAAA...\") */\n publicKey: string;\n /** Optional GitHub username for attribution */\n githubUser?: string;\n }\n | {\n /**\n * Custom signer function.\n * Useful when the private key lives outside this process (e.g., an SSH agent).\n */\n signer: (data: Uint8Array) => Promise<SignatureEnvelope>;\n };\n\nexport interface UploadOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n /** Expiration time (unix timestamp ms, ISO date string, or Date object) */\n expiresAt?: number | string | Date;\n /**\n * Precomputed signature envelope to embed in the encrypted payload.\n * Takes precedence over `sign`.\n */\n signature?: SignatureEnvelope;\n /**\n * Sign the content.\n *\n * When provided, the decrypted blob becomes JSON (SignedPayload) containing both\n * the content string and the signature envelope.\n */\n sign?: SignOptions;\n}\n\nexport interface UploadResult {\n /** Full URL with encryption key in fragment */\n url: string;\n /** File ID (without key) */\n id: string;\n /** Encryption key (base64url) */\n key: string;\n /** Mutate key (base64url) - store this to mutate (delete, change expiration) the file later */\n mutateKey: string;\n /** Expiration timestamp (ms), if set */\n expiresAt?: number;\n}\n\nexport interface DownloadResult {\n /** Decrypted file content */\n data: Uint8Array;\n /** Original file info (name, type, size) */\n info: FileInfo;\n /** Decrypted signature envelope (if present) */\n signature?: SignatureEnvelope;\n}\n\n/**\n * Encrypt and upload a file to mux.md\n *\n * @param data - File contents as Uint8Array\n * @param fileInfo - Original file metadata (name, type, size)\n * @param options - Upload options (including optional signature)\n * @returns Upload result with URL containing encryption key\n */\nexport async function upload(\n data: Uint8Array,\n fileInfo: FileInfo,\n options: UploadOptions = {},\n): Promise<UploadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Generate encryption parameters\n const keyMaterial = generateKey();\n const salt = generateSalt();\n const iv = generateIV();\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const wantsSignature =\n options.signature !== undefined || options.sign !== undefined;\n\n // Signed uploads embed plaintext as JSON with `content: string`.\n // That requires the original bytes to be valid UTF-8.\n const signedContent = wantsSignature ? decodeUtf8Strict(data) : undefined;\n\n // Resolve the signature envelope.\n let signatureEnvelope: SignatureEnvelope | undefined;\n\n if (options.signature !== undefined) {\n assertSignatureEnvelope(options.signature);\n signatureEnvelope = options.signature;\n } else if (options.sign) {\n if ('signer' in options.sign) {\n const envelope = await options.sign.signer(data);\n assertSignatureEnvelope(envelope);\n signatureEnvelope = envelope;\n } else {\n signatureEnvelope = await createSignatureEnvelope(\n data,\n options.sign.privateKey,\n options.sign.publicKey,\n { githubUser: options.sign.githubUser },\n );\n assertSignatureEnvelope(signatureEnvelope);\n }\n }\n\n if (wantsSignature) {\n assert(\n signatureEnvelope !== undefined,\n 'Signature requested but no signature envelope was produced',\n );\n }\n\n // Build plaintext - either signed (JSON) or raw content\n let plaintext: Uint8Array;\n if (signatureEnvelope) {\n assert(signedContent !== undefined, 'Signed content string missing');\n\n // Signed format: JSON with content + signature\n const signed: SignedPayload = {\n content: signedContent,\n sig: signatureEnvelope,\n };\n plaintext = new TextEncoder().encode(JSON.stringify(signed));\n } else {\n // Unsigned: raw content bytes\n plaintext = data;\n }\n\n // Single encryption for the payload\n const payload = await encrypt(plaintext, cryptoKey, iv);\n\n // Encrypt file metadata (always the same way)\n const metaJson = JSON.stringify(fileInfo);\n const metaBytes = new TextEncoder().encode(metaJson);\n const metaIv = generateIV();\n const encryptedMeta = await encrypt(metaBytes, cryptoKey, metaIv);\n\n // Prepare upload metadata\n const uploadMeta: UploadMeta = {\n salt: base64Encode(salt),\n iv: base64Encode(iv),\n encryptedMeta: base64Encode(new Uint8Array([...metaIv, ...encryptedMeta])),\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Mux-Meta': btoa(JSON.stringify(uploadMeta)),\n };\n\n // Add expiration header if specified (convert to ISO 8601)\n if (options.expiresAt !== undefined) {\n let expiresDate: Date;\n if (options.expiresAt instanceof Date) {\n expiresDate = options.expiresAt;\n } else if (typeof options.expiresAt === 'string') {\n expiresDate = new Date(options.expiresAt);\n } else {\n expiresDate = new Date(options.expiresAt);\n }\n headers['X-Mux-Expires'] = expiresDate.toISOString();\n }\n\n // Upload to server\n const response = await fetch(`${baseUrl}/`, {\n method: 'POST',\n headers,\n body: payload.buffer as ArrayBuffer,\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Upload failed' }));\n throw new Error((error as { error: string }).error || 'Upload failed');\n }\n\n const result: UploadResponse = await response.json();\n\n return {\n url: `${baseUrl}/${result.id}#${keyMaterial}`,\n id: result.id,\n key: keyMaterial,\n mutateKey: result.mutateKey,\n ...(result.expiresAt && { expiresAt: result.expiresAt }),\n };\n}\n\n/**\n * Download and decrypt a file from mux.md\n *\n * @param url - Full URL with encryption key in fragment, or just the ID\n * @param key - Encryption key (required if url doesn't contain fragment)\n * @param options - Download options\n * @returns Decrypted file data and metadata\n */\nexport async function download(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<DownloadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n // Full URL with fragment\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1); // Remove leading /\n keyMaterial = urlObj.hash.slice(1); // Remove leading #\n } else if (url.includes('/')) {\n // URL path without fragment\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n // Just the ID\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Download encrypted file\n const response = await fetch(`${baseUrl}/${id}`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Download failed' }));\n throw new Error((error as { error: string }).error || 'Download failed');\n }\n\n // Parse metadata header\n const metaHeader = response.headers.get('X-Mux-Meta');\n if (!metaHeader) {\n throw new Error('Missing metadata header');\n }\n\n const uploadMeta: UploadMeta = JSON.parse(atob(metaHeader));\n\n // Get encrypted data\n const encryptedData = new Uint8Array(await response.arrayBuffer());\n\n // Derive decryption key\n const salt = base64Decode(uploadMeta.salt);\n const iv = base64Decode(uploadMeta.iv);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Decrypt metadata (same for both formats)\n const encryptedMetaWithIv = base64Decode(uploadMeta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n // Decrypt the payload\n const decrypted = await decrypt(encryptedData, cryptoKey, iv);\n\n // Check if decrypted content is signed (JSON with content + sig fields)\n if (decrypted[0] === 0x7b) {\n // '{' character - might be signed JSON\n try {\n const jsonStr = new TextDecoder().decode(decrypted);\n const parsed = JSON.parse(jsonStr);\n\n if (typeof parsed.content === 'string' && parsed.sig) {\n // Signed format\n const data = new TextEncoder().encode(parsed.content);\n const signature: SignatureEnvelope = parsed.sig;\n return { data, info, signature };\n }\n } catch {\n // Not valid JSON - treat as raw content\n }\n }\n\n // Unsigned content (raw bytes)\n return { data: decrypted, info };\n}\n\n/**\n * Get file metadata without downloading the full file\n *\n * @param url - Full URL or ID\n * @param key - Encryption key (required to decrypt metadata)\n * @param options - Request options\n * @returns Decrypted file info and server metadata\n */\nexport async function getMeta(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<{ info: FileInfo; size: number }> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1);\n keyMaterial = urlObj.hash.slice(1);\n } else if (url.includes('/')) {\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Fetch metadata\n const response = await fetch(`${baseUrl}/${id}/meta`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Request failed' }));\n throw new Error((error as { error: string }).error || 'Request failed');\n }\n\n const meta: MetaResponse = await response.json();\n\n // Derive key and decrypt metadata\n const salt = base64Decode(meta.salt);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const encryptedMetaWithIv = base64Decode(meta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n return {\n info,\n size: meta.size,\n };\n}\n\n/**\n * Parse a mux.md URL into its components\n */\nexport function parseUrl(url: string): { id: string; key: string } | null {\n try {\n const urlObj = new URL(url);\n if (!urlObj.hash) return null;\n\n const id = urlObj.pathname.slice(1);\n const key = urlObj.hash.slice(1);\n\n if (!id || !key) return null;\n\n return { id, key };\n } catch {\n return null;\n }\n}\n\n/**\n * Build a mux.md URL from components\n */\nexport function buildUrl(\n id: string,\n key: string,\n baseUrl = DEFAULT_BASE_URL,\n): string {\n return `${baseUrl}/${id}#${key}`;\n}\n\n/**\n * Delete a file from mux.md using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param options - Request options\n */\nexport async function deleteFile(\n id: string,\n mutateKey: string,\n options: { baseUrl?: string } = {},\n): Promise<void> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'DELETE',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Delete failed' }));\n throw new Error((error as { error: string }).error || 'Delete failed');\n }\n}\n\nexport interface SetExpirationOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n}\n\nexport interface SetExpirationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** File ID */\n id: string;\n /** New expiration timestamp (ms), or undefined if expiration was removed */\n expiresAt?: number;\n}\n\n/**\n * Set or remove the expiration of a file using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param expiresAt - New expiration time (unix timestamp ms, ISO date string, Date object, or \"never\" to remove expiration)\n * @param options - Request options\n * @returns Result with new expiration info\n */\nexport async function setExpiration(\n id: string,\n mutateKey: string,\n expiresAt: number | string | Date | 'never',\n options: SetExpirationOptions = {},\n): Promise<SetExpirationResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Convert expiration to ISO 8601 string\n let expiresHeader: string;\n if (expiresAt === 'never') {\n expiresHeader = 'never';\n } else if (expiresAt instanceof Date) {\n expiresHeader = expiresAt.toISOString();\n } else if (typeof expiresAt === 'string') {\n expiresHeader = new Date(expiresAt).toISOString();\n } else {\n expiresHeader = new Date(expiresAt).toISOString();\n }\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'PATCH',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n 'X-Mux-Expires': expiresHeader,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Set expiration failed' }));\n throw new Error(\n (error as { error: string }).error || 'Set expiration failed',\n );\n }\n\n return response.json();\n}\n"],"mappings":"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","names":["base64Decode"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FileInfo, S as SignatureEnvelope } from './types-
|
|
2
|
-
export { a as SignedPayload, U as UploadMeta } from './types-
|
|
1
|
+
import { F as FileInfo, S as SignatureEnvelope } from './types-DOs5tRco.cjs';
|
|
2
|
+
export { C as ConvoSummary, L as LocSummary, M as MuxClientMode, a as SignedPayload, U as UploadMeta } from './types-DOs5tRco.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* mux.md Client Library
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FileInfo, S as SignatureEnvelope } from './types-
|
|
2
|
-
export { a as SignedPayload, U as UploadMeta } from './types-
|
|
1
|
+
import { F as FileInfo, S as SignatureEnvelope } from './types-DOs5tRco.js';
|
|
2
|
+
export { C as ConvoSummary, L as LocSummary, M as MuxClientMode, a as SignedPayload, U as UploadMeta } from './types-DOs5tRco.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* mux.md Client Library
|
package/dist/ssh-agent.d.cts
CHANGED
package/dist/ssh-agent.d.ts
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mux client "mode" / environment.
|
|
3
|
+
*/
|
|
4
|
+
type MuxClientMode = 'desktop' | 'server' | 'cli';
|
|
5
|
+
/**
|
|
6
|
+
* Line-of-code diff summary.
|
|
7
|
+
*/
|
|
8
|
+
interface LocSummary {
|
|
9
|
+
added: number;
|
|
10
|
+
removed: number;
|
|
11
|
+
/**
|
|
12
|
+
* Optional: number of modified lines.
|
|
13
|
+
*
|
|
14
|
+
* Not all diff tools provide this directly, and it can be derived differently.
|
|
15
|
+
*/
|
|
16
|
+
modified?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Optional conversation summary metadata.
|
|
20
|
+
*
|
|
21
|
+
* Encrypted client-side and never seen by the server in plaintext.
|
|
22
|
+
*/
|
|
23
|
+
interface ConvoSummary {
|
|
24
|
+
/** Repository (e.g. "coder/mux") */
|
|
25
|
+
repo?: string;
|
|
26
|
+
/** Client mode (e.g. "desktop", "server", "cli") */
|
|
27
|
+
clientMode?: MuxClientMode;
|
|
28
|
+
/** Number of user prompts in the conversation */
|
|
29
|
+
userPromptCount?: number;
|
|
30
|
+
/** Number of files modified in the conversation */
|
|
31
|
+
filesModifiedCount?: number;
|
|
32
|
+
/** LOC diff summary */
|
|
33
|
+
loc?: LocSummary;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* File info encrypted client-side (never seen by server in plaintext)
|
|
37
|
+
*/
|
|
38
|
+
interface FileInfo {
|
|
39
|
+
name: string;
|
|
40
|
+
type: string;
|
|
41
|
+
size: number;
|
|
42
|
+
model?: string;
|
|
43
|
+
thinking?: string;
|
|
44
|
+
convo?: ConvoSummary;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Metadata header sent with upload (X-Mux-Meta)
|
|
48
|
+
* Also used in response header with createdAt added
|
|
49
|
+
*/
|
|
50
|
+
interface UploadMeta {
|
|
51
|
+
salt: string;
|
|
52
|
+
iv: string;
|
|
53
|
+
encryptedMeta: string;
|
|
54
|
+
createdAt?: string;
|
|
55
|
+
expiresAt?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Signature envelope - contains all signature-related data.
|
|
59
|
+
*/
|
|
60
|
+
interface SignatureEnvelope {
|
|
61
|
+
sig: string;
|
|
62
|
+
publicKey: string;
|
|
63
|
+
githubUser?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Signed content payload (decrypted).
|
|
67
|
+
* When a signature is present, the decrypted blob is JSON with this structure.
|
|
68
|
+
* Legacy (unsigned) content decrypts directly to the raw content bytes.
|
|
69
|
+
*/
|
|
70
|
+
interface SignedPayload {
|
|
71
|
+
content: string;
|
|
72
|
+
sig: SignatureEnvelope;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type { ConvoSummary as C, FileInfo as F, LocSummary as L, MuxClientMode as M, SignatureEnvelope as S, UploadMeta as U, SignedPayload as a };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mux client "mode" / environment.
|
|
3
|
+
*/
|
|
4
|
+
type MuxClientMode = 'desktop' | 'server' | 'cli';
|
|
5
|
+
/**
|
|
6
|
+
* Line-of-code diff summary.
|
|
7
|
+
*/
|
|
8
|
+
interface LocSummary {
|
|
9
|
+
added: number;
|
|
10
|
+
removed: number;
|
|
11
|
+
/**
|
|
12
|
+
* Optional: number of modified lines.
|
|
13
|
+
*
|
|
14
|
+
* Not all diff tools provide this directly, and it can be derived differently.
|
|
15
|
+
*/
|
|
16
|
+
modified?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Optional conversation summary metadata.
|
|
20
|
+
*
|
|
21
|
+
* Encrypted client-side and never seen by the server in plaintext.
|
|
22
|
+
*/
|
|
23
|
+
interface ConvoSummary {
|
|
24
|
+
/** Repository (e.g. "coder/mux") */
|
|
25
|
+
repo?: string;
|
|
26
|
+
/** Client mode (e.g. "desktop", "server", "cli") */
|
|
27
|
+
clientMode?: MuxClientMode;
|
|
28
|
+
/** Number of user prompts in the conversation */
|
|
29
|
+
userPromptCount?: number;
|
|
30
|
+
/** Number of files modified in the conversation */
|
|
31
|
+
filesModifiedCount?: number;
|
|
32
|
+
/** LOC diff summary */
|
|
33
|
+
loc?: LocSummary;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* File info encrypted client-side (never seen by server in plaintext)
|
|
37
|
+
*/
|
|
38
|
+
interface FileInfo {
|
|
39
|
+
name: string;
|
|
40
|
+
type: string;
|
|
41
|
+
size: number;
|
|
42
|
+
model?: string;
|
|
43
|
+
thinking?: string;
|
|
44
|
+
convo?: ConvoSummary;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Metadata header sent with upload (X-Mux-Meta)
|
|
48
|
+
* Also used in response header with createdAt added
|
|
49
|
+
*/
|
|
50
|
+
interface UploadMeta {
|
|
51
|
+
salt: string;
|
|
52
|
+
iv: string;
|
|
53
|
+
encryptedMeta: string;
|
|
54
|
+
createdAt?: string;
|
|
55
|
+
expiresAt?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Signature envelope - contains all signature-related data.
|
|
59
|
+
*/
|
|
60
|
+
interface SignatureEnvelope {
|
|
61
|
+
sig: string;
|
|
62
|
+
publicKey: string;
|
|
63
|
+
githubUser?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Signed content payload (decrypted).
|
|
67
|
+
* When a signature is present, the decrypted blob is JSON with this structure.
|
|
68
|
+
* Legacy (unsigned) content decrypts directly to the raw content bytes.
|
|
69
|
+
*/
|
|
70
|
+
interface SignedPayload {
|
|
71
|
+
content: string;
|
|
72
|
+
sig: SignatureEnvelope;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type { ConvoSummary as C, FileInfo as F, LocSummary as L, MuxClientMode as M, SignatureEnvelope as S, UploadMeta as U, SignedPayload as a };
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File info encrypted client-side (never seen by server in plaintext)
|
|
3
|
-
*/
|
|
4
|
-
interface FileInfo {
|
|
5
|
-
name: string;
|
|
6
|
-
type: string;
|
|
7
|
-
size: number;
|
|
8
|
-
model?: string;
|
|
9
|
-
thinking?: string;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Metadata header sent with upload (X-Mux-Meta)
|
|
13
|
-
* Also used in response header with createdAt added
|
|
14
|
-
*/
|
|
15
|
-
interface UploadMeta {
|
|
16
|
-
salt: string;
|
|
17
|
-
iv: string;
|
|
18
|
-
encryptedMeta: string;
|
|
19
|
-
createdAt?: string;
|
|
20
|
-
expiresAt?: string;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Signature envelope - contains all signature-related data.
|
|
24
|
-
*/
|
|
25
|
-
interface SignatureEnvelope {
|
|
26
|
-
sig: string;
|
|
27
|
-
publicKey: string;
|
|
28
|
-
githubUser?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Signed content payload (decrypted).
|
|
32
|
-
* When a signature is present, the decrypted blob is JSON with this structure.
|
|
33
|
-
* Legacy (unsigned) content decrypts directly to the raw content bytes.
|
|
34
|
-
*/
|
|
35
|
-
interface SignedPayload {
|
|
36
|
-
content: string;
|
|
37
|
-
sig: SignatureEnvelope;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type { FileInfo as F, SignatureEnvelope as S, UploadMeta as U, SignedPayload as a };
|
package/dist/types-BZMDMbOR.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File info encrypted client-side (never seen by server in plaintext)
|
|
3
|
-
*/
|
|
4
|
-
interface FileInfo {
|
|
5
|
-
name: string;
|
|
6
|
-
type: string;
|
|
7
|
-
size: number;
|
|
8
|
-
model?: string;
|
|
9
|
-
thinking?: string;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Metadata header sent with upload (X-Mux-Meta)
|
|
13
|
-
* Also used in response header with createdAt added
|
|
14
|
-
*/
|
|
15
|
-
interface UploadMeta {
|
|
16
|
-
salt: string;
|
|
17
|
-
iv: string;
|
|
18
|
-
encryptedMeta: string;
|
|
19
|
-
createdAt?: string;
|
|
20
|
-
expiresAt?: string;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Signature envelope - contains all signature-related data.
|
|
24
|
-
*/
|
|
25
|
-
interface SignatureEnvelope {
|
|
26
|
-
sig: string;
|
|
27
|
-
publicKey: string;
|
|
28
|
-
githubUser?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Signed content payload (decrypted).
|
|
32
|
-
* When a signature is present, the decrypted blob is JSON with this structure.
|
|
33
|
-
* Legacy (unsigned) content decrypts directly to the raw content bytes.
|
|
34
|
-
*/
|
|
35
|
-
interface SignedPayload {
|
|
36
|
-
content: string;
|
|
37
|
-
sig: SignatureEnvelope;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type { FileInfo as F, SignatureEnvelope as S, UploadMeta as U, SignedPayload as a };
|