From 0bbf7341d3cafbc19dbef4226a7fd5971103bb9f Mon Sep 17 00:00:00 2001 From: shmlkv Date: Mon, 26 Jan 2026 02:03:22 -0300 Subject: [PATCH 1/2] feat: add update-profile command to change account name Adds a new CLI command `murmur update-profile` that allows changing the firstName and lastName of an existing account. Usage: murmur update-profile --first-name [--last-name ] Changes: - Added updateProfile method to MurmurEngine - Added update-profile command to CLI - Imports signProfileKey for re-signing the profile --- packages/murmur-cli/src/cli.ts | 9 ++++++ packages/murmur-cli/src/engine/engine.ts | 40 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/murmur-cli/src/cli.ts b/packages/murmur-cli/src/cli.ts index 4316cb7..2db18fd 100644 --- a/packages/murmur-cli/src/cli.ts +++ b/packages/murmur-cli/src/cli.ts @@ -367,6 +367,7 @@ function printUsage(): void { const lines = [ 'Usage:', ' murmur sign-in [--first-name ] [--last-name ]', + ' murmur update-profile --first-name [--last-name ]', ' murmur me', ' murmur delete-account --confirm', ' murmur contacts', @@ -698,6 +699,14 @@ async function run(): Promise { console.log(JSON.stringify(profile, null, 2)) return } + case 'update-profile': { + await requireInitialized(getEngine()) + const firstName = requireStringOption(parsed.options, 'first-name', 'MURMUR_FIRST_NAME') + const lastName = readStringOption(parsed.options, 'last-name', 'MURMUR_LAST_NAME') + const account = await getEngine().updateProfile(firstName, lastName) + printAccountSummary('Profile updated.', account) + return + } case 'delete-account': { await requireInitialized(getEngine()) const confirmed = readBooleanOption(parsed.options, 'confirm', 'MURMUR_CONFIRM_DELETE') diff --git a/packages/murmur-cli/src/engine/engine.ts b/packages/murmur-cli/src/engine/engine.ts index 26c7325..866b4ee 100644 --- a/packages/murmur-cli/src/engine/engine.ts +++ b/packages/murmur-cli/src/engine/engine.ts @@ -47,6 +47,7 @@ import { decryptProfile, createProfileForRegistration, verifyProfileKeySignature, + signProfileKey, type Profile } from './profile.js' import { publicKeyFromPrivate } from '../encryption/crypto/dh.js' @@ -521,6 +522,45 @@ export class MurmurEngine { return this.account } + /** + * Update the current account's profile (firstName, lastName). + */ + async updateProfile(firstName: string, lastName?: string): Promise { + if (!this.agent || !this.account) { + throw new Error('Not initialized') + } + + // Create new encrypted profile + const profile: Profile = { firstName, lastName } + const profileSecretKeyBytes = decodeBase64(this.account.profileSecretKey, 'base64url') + const profilePublicKeyBytes = decodeBase64(this.account.profilePublicKey) + const encryptedProfile = encryptProfile(profile, profileSecretKeyBytes) + const profileKeySignature = signProfileKey( + profilePublicKeyBytes, + this.agent.keyStore.identityKeyPair.privateKey + ) + + // Upload to server + await this.api.updateProfile( + this.account.profilePublicKey, + profileKeySignature, + encryptedProfile + ) + + // Update local account + this.account = { + ...this.account, + firstName, + lastName, + encryptedProfile + } + + // Save to database + this.db.saveAccount(this.account) + + return this.account + } + /** * Delete the current account and local data. */ From 56a555f99e98f9ed171e85a3c36d3e02a0b86f3d Mon Sep 17 00:00:00 2001 From: shmlkv Date: Mon, 26 Jan 2026 02:21:48 -0300 Subject: [PATCH 2/2] feat: include message text in webhook payload Adds 'text' field to WebhookContext so webhooks receive the actual message content without needing a separate sync call. Usage in webhook-body: --webhook-body '{"text": "{{text}}", ...}' --- packages/murmur-cli/src/cli.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/murmur-cli/src/cli.ts b/packages/murmur-cli/src/cli.ts index 2db18fd..3964651 100644 --- a/packages/murmur-cli/src/cli.ts +++ b/packages/murmur-cli/src/cli.ts @@ -520,6 +520,7 @@ type WebhookContext = { senderId: string senderIdentityKey: string senderName: string + text: string receivedAt: number hasAttachments: boolean } @@ -595,6 +596,7 @@ async function emitWebhook( senderId: formatWebhookSenderId(contact, message.conversationId), senderIdentityKey: message.conversationId, senderName: formatName(contact?.firstName, contact?.lastName, 'Unknown'), + text: message.text, receivedAt: message.createdAt, hasAttachments: Boolean(message.attachments && message.attachments.length > 0) }