* feat: make relay opt-in for device pairing * fix(relay): keep pairing state authoritative * fix(relay): refresh pairing state across transports * fix(onboard): honor explicit relay opt-out * fix(relay): surface transport startup failures * fix(pairing): gate offers for older daemons * test(cli): rely on real pairing transport coverage * fix(cli): report live relay without a pid file * fix(relay): synchronize config during daemon startup * fix(relay): preserve mutability for ignored overrides * fix(cli): use reachable daemon for pairing * fix(app): redirect invalid pair device routes * fix(pairing): support remote host device pairing Keep Pair Device host-relative through the existing settings and daemon RPC flow. Route CLI output through an injected adapter so workflow tests do not mutate process globals. * fix(pairing): validate pairing destinations Expose the host-relative Pair Device flow on every supported app platform. Reject a reachable daemon whose identity does not match the requested Paseo home before reading or mutating its pairing state. * fix(pairing): render QR codes across platforms Generate SVG text without DOM or canvas and render it through the existing cross-platform SVG stack. * fix(cli): pair from persisted relay state Allow explicit relay pairing while the daemon is stopped when relay is already enabled in persisted configuration.
105 lines
4.1 KiB
TypeScript
105 lines
4.1 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
|
|
import assert from "node:assert";
|
|
import { readFile, mkdtemp, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { $ } from "zx";
|
|
import { getAvailablePort } from "./helpers/network.ts";
|
|
|
|
$.verbose = false;
|
|
|
|
console.log("=== Onboarding Command ===\n");
|
|
|
|
const paseoHome = await mkdtemp(join(tmpdir(), "paseo-onboard-home-"));
|
|
const port = await getAvailablePort();
|
|
|
|
try {
|
|
console.log("Test 1: `paseo` runs blocking onboarding without implicit relay pairing");
|
|
const onboard =
|
|
await $`PASEO_HOME=${paseoHome} PASEO_LISTEN=127.0.0.1:${port} PASEO_PAIRING_QR=0 npx paseo`.nothrow();
|
|
|
|
assert.strictEqual(
|
|
onboard.exitCode,
|
|
0,
|
|
`onboard should succeed:\nstdout:\n${onboard.stdout}\nstderr:\n${onboard.stderr}`,
|
|
);
|
|
assert(!onboard.stdout.includes("Scan to pair"), "onboard output should not include scan header");
|
|
assert(!onboard.stdout.includes("#offer="), "onboard output should not include a pairing offer");
|
|
assert(
|
|
onboard.stdout.includes("Daemon is running with relay off"),
|
|
"onboard output should explain the direct connection path",
|
|
);
|
|
assert(
|
|
onboard.stdout.includes("CLI quick reference"),
|
|
"onboard output should include CLI quick reference",
|
|
);
|
|
assert(onboard.stdout.includes("paseo --help"), "onboard output should include --help shortcut");
|
|
assert(onboard.stdout.includes("paseo ls"), "onboard output should include ls shortcut");
|
|
assert(
|
|
onboard.stdout.includes('paseo run "your prompt"'),
|
|
"onboard output should include run shortcut",
|
|
);
|
|
assert(onboard.stdout.includes("paseo status"), "onboard output should include status shortcut");
|
|
assert(
|
|
onboard.stdout.includes(join(paseoHome, "daemon.log")),
|
|
"onboard output should include daemon log path",
|
|
);
|
|
|
|
const status =
|
|
await $`PASEO_HOME=${paseoHome} npx paseo daemon status --home ${paseoHome}`.nothrow();
|
|
assert.strictEqual(status.exitCode, 0, `daemon status should succeed: ${status.stderr}`);
|
|
assert(status.stdout.includes("running"), "daemon should be running when onboarding exits");
|
|
console.log("✓ onboarding keeps relay disabled and waits for daemon readiness\n");
|
|
|
|
console.log("Test 2: --no-relay suppresses pairing for an already-running daemon");
|
|
const enableRelay =
|
|
await $`PASEO_HOME=${paseoHome} npx paseo daemon pair --home ${paseoHome} --relay`.nothrow();
|
|
assert.strictEqual(enableRelay.exitCode, 0, `relay enable should succeed: ${enableRelay.stderr}`);
|
|
assert(enableRelay.stdout.includes("#offer="), "relay enable should produce a pairing offer");
|
|
|
|
const noRelayOnboard =
|
|
await $`PASEO_HOME=${paseoHome} PASEO_LISTEN=127.0.0.1:${port} npx paseo --no-relay`.nothrow();
|
|
assert.strictEqual(
|
|
noRelayOnboard.exitCode,
|
|
0,
|
|
`--no-relay onboarding should succeed: ${noRelayOnboard.stderr}`,
|
|
);
|
|
assert(
|
|
!noRelayOnboard.stdout.includes("#offer="),
|
|
"--no-relay onboarding should not include a pairing offer",
|
|
);
|
|
console.log("✓ --no-relay suppresses pairing for an already-running daemon\n");
|
|
|
|
console.log("Test 3: non-interactive onboarding persists voice disabled config");
|
|
const configRaw = await readFile(join(paseoHome, "config.json"), "utf-8");
|
|
const config = JSON.parse(configRaw) as {
|
|
features?: {
|
|
dictation?: { enabled?: boolean };
|
|
voiceMode?: { enabled?: boolean };
|
|
};
|
|
};
|
|
|
|
assert.strictEqual(
|
|
config.features?.dictation?.enabled,
|
|
false,
|
|
"dictation.enabled should be false",
|
|
);
|
|
assert.strictEqual(
|
|
config.features?.voiceMode?.enabled,
|
|
false,
|
|
"voiceMode.enabled should be false",
|
|
);
|
|
const daemonLog = await readFile(join(paseoHome, "daemon.log"), "utf-8");
|
|
assert(
|
|
!daemonLog.includes("Ensuring local speech models"),
|
|
"daemon should not attempt local speech model setup when voice is disabled",
|
|
);
|
|
console.log("✓ non-interactive run persisted voice disabled choices\n");
|
|
} finally {
|
|
await $`PASEO_HOME=${paseoHome} npx paseo daemon stop --home ${paseoHome} --force`.nothrow();
|
|
await rm(paseoHome, { recursive: true, force: true });
|
|
}
|
|
|
|
console.log("=== Onboarding tests passed ===");
|