Add device pairing command and voice mute toggle

Introduce `paseo daemon pair` CLI command that generates a local pairing offer URL with QR code without requiring a running daemon. Update message input to support mute/unmute toggle during realtime voice sessions with visual feedback (MicOff icon). Fix android:prod build script to use prebuild + assembleRelease flow.
This commit is contained in:
Mohamed Boudra
2026-02-07 15:56:14 +07:00
parent 902a415911
commit f9ebf6b523
8 changed files with 164 additions and 21 deletions
+1
View File
@@ -4,6 +4,7 @@ export { loadConfig, type CliConfigOverrides } from "./config.js";
export { resolvePaseoHome } from "./paseo-home.js";
export { createRootLogger, type LogLevel, type LogFormat } from "./logger.js";
export { loadPersistedConfig, type PersistedConfig } from "./persisted-config.js";
export { generateLocalPairingOffer, type LocalPairingOffer } from "./pairing-offer.js";
export { DaemonClient, type DaemonClientConfig, type ConnectionState, type DaemonEvent } from "../client/daemon-client.js";
// Agent SDK types for CLI commands
@@ -0,0 +1,64 @@
import type { Logger } from "pino";
import { createConnectionOfferV2, encodeOfferToFragmentUrl } from "./connection-offer.js";
import { loadOrCreateDaemonKeyPair } from "./daemon-keypair.js";
import { renderPairingQr } from "./pairing-qr.js";
import { getOrCreateServerId } from "./server-id.js";
export type LocalPairingOffer = {
relayEnabled: boolean;
url: string | null;
qr: string | null;
};
export async function generateLocalPairingOffer(args: {
paseoHome: string;
relayEnabled?: boolean;
relayEndpoint?: string;
relayPublicEndpoint?: string;
appBaseUrl?: string;
includeQr?: boolean;
logger?: Logger;
}): Promise<LocalPairingOffer> {
const relayEnabled = args.relayEnabled ?? true;
if (!relayEnabled) {
return {
relayEnabled: false,
url: null,
qr: null,
};
}
const relayEndpoint = args.relayEndpoint ?? "relay.paseo.sh:443";
const relayPublicEndpoint = args.relayPublicEndpoint ?? relayEndpoint;
const appBaseUrl = args.appBaseUrl ?? "https://app.paseo.sh";
const serverId = getOrCreateServerId(args.paseoHome, { logger: args.logger });
const daemonKeyPair = await loadOrCreateDaemonKeyPair(args.paseoHome, args.logger);
const offer = await createConnectionOfferV2({
serverId,
daemonPublicKeyB64: daemonKeyPair.publicKeyB64,
relay: { endpoint: relayPublicEndpoint },
});
const url = encodeOfferToFragmentUrl({ offer, appBaseUrl });
if (args.includeQr === false) {
return {
relayEnabled: true,
url,
qr: null,
};
}
let qr: string | null = null;
try {
qr = await renderPairingQr(url);
} catch (error) {
args.logger?.debug({ error }, "Failed to render pairing QR");
}
return {
relayEnabled: true,
url,
qr,
};
}
+2 -2
View File
@@ -16,7 +16,7 @@ function shouldPrintPairingQr(): boolean {
return Boolean(process.stdout.isTTY);
}
async function renderQr(url: string): Promise<string> {
export async function renderPairingQr(url: string): Promise<string> {
const terminalOptions: QRCodeToStringOptionsTerminal = {
type: "terminal",
small: true,
@@ -39,7 +39,7 @@ export async function printPairingQrIfEnabled(args: {
}): Promise<void> {
if (!shouldPrintPairingQr()) return;
const qr = await renderQr(args.url);
const qr = await renderPairingQr(args.url);
const out = `\nScan to pair:\n${qr}\n${args.url}\n`;
try {