Switch to double quotes and reformat codebase with Biome
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from '@clack/prompts'
|
||||
import { Command } from 'commander'
|
||||
import { writeFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
|
||||
import { Command } from "commander";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
generateLocalPairingOffer,
|
||||
loadConfig,
|
||||
loadPersistedConfig,
|
||||
type CliConfigOverrides,
|
||||
type PersistedConfig,
|
||||
} from '@getpaseo/server'
|
||||
} from "@getpaseo/server";
|
||||
import {
|
||||
resolveLocalPaseoHome,
|
||||
resolveLocalDaemonState,
|
||||
@@ -16,88 +16,94 @@ import {
|
||||
startLocalDaemonDetached,
|
||||
tailDaemonLog,
|
||||
type DaemonStartOptions,
|
||||
} from './daemon/local-daemon.js'
|
||||
import { tryConnectToDaemon } from '../utils/client.js'
|
||||
} from "./daemon/local-daemon.js";
|
||||
import { tryConnectToDaemon } from "../utils/client.js";
|
||||
|
||||
interface OnboardOptions extends DaemonStartOptions {
|
||||
timeout?: string
|
||||
voice?: 'ask' | 'enable' | 'disable'
|
||||
timeout?: string;
|
||||
voice?: "ask" | "enable" | "disable";
|
||||
}
|
||||
|
||||
type OnboardPersistedConfig = PersistedConfig & {
|
||||
features?: PersistedConfig['features'] & {
|
||||
dictation?: PersistedConfig['features'] extends { dictation?: infer T }
|
||||
features?: PersistedConfig["features"] & {
|
||||
dictation?: PersistedConfig["features"] extends { dictation?: infer T }
|
||||
? T & { enabled?: boolean }
|
||||
: { enabled?: boolean }
|
||||
voiceMode?: PersistedConfig['features'] extends { voiceMode?: infer T }
|
||||
: { enabled?: boolean };
|
||||
voiceMode?: PersistedConfig["features"] extends { voiceMode?: infer T }
|
||||
? T & { enabled?: boolean }
|
||||
: { enabled?: boolean }
|
||||
}
|
||||
}
|
||||
: { enabled?: boolean };
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1000
|
||||
const DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
|
||||
class OnboardCancelledError extends Error {}
|
||||
|
||||
const plainNoteFormat = (line: string): string => line
|
||||
const plainNoteFormat = (line: string): string => line;
|
||||
|
||||
function renderNote(message: string, title: string): void {
|
||||
note(message, title, { format: plainNoteFormat })
|
||||
note(message, title, { format: plainNoteFormat });
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function parseTimeoutMs(raw: string | undefined): number {
|
||||
if (!raw || raw.trim().length === 0) {
|
||||
return DEFAULT_READY_TIMEOUT_MS
|
||||
return DEFAULT_READY_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
const seconds = Number(raw)
|
||||
const seconds = Number(raw);
|
||||
if (!Number.isFinite(seconds) || seconds <= 0) {
|
||||
throw new Error(`Invalid timeout value: ${raw}`)
|
||||
throw new Error(`Invalid timeout value: ${raw}`);
|
||||
}
|
||||
|
||||
return Math.ceil(seconds * 1000)
|
||||
return Math.ceil(seconds * 1000);
|
||||
}
|
||||
|
||||
function toCliOverrides(options: DaemonStartOptions): CliConfigOverrides {
|
||||
const cliOverrides: CliConfigOverrides = {}
|
||||
const cliOverrides: CliConfigOverrides = {};
|
||||
|
||||
if (options.listen) {
|
||||
cliOverrides.listen = options.listen
|
||||
cliOverrides.listen = options.listen;
|
||||
} else if (options.port) {
|
||||
cliOverrides.listen = `127.0.0.1:${options.port}`
|
||||
cliOverrides.listen = `127.0.0.1:${options.port}`;
|
||||
}
|
||||
|
||||
if (options.relay === false) {
|
||||
cliOverrides.relayEnabled = false
|
||||
cliOverrides.relayEnabled = false;
|
||||
}
|
||||
|
||||
if (options.allowedHosts) {
|
||||
const raw = options.allowedHosts.trim()
|
||||
const raw = options.allowedHosts.trim();
|
||||
cliOverrides.allowedHosts =
|
||||
raw.toLowerCase() === 'true'
|
||||
raw.toLowerCase() === "true"
|
||||
? true
|
||||
: raw.split(',').map(host => host.trim()).filter(Boolean)
|
||||
: raw
|
||||
.split(",")
|
||||
.map((host) => host.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
if (options.mcp === false) {
|
||||
cliOverrides.mcpEnabled = false
|
||||
cliOverrides.mcpEnabled = false;
|
||||
}
|
||||
|
||||
return cliOverrides
|
||||
return cliOverrides;
|
||||
}
|
||||
|
||||
function savePersistedConfig(paseoHome: string, config: OnboardPersistedConfig): void {
|
||||
const configPath = path.join(paseoHome, 'config.json')
|
||||
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`)
|
||||
const configPath = path.join(paseoHome, "config.json");
|
||||
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
||||
}
|
||||
|
||||
function applyVoiceSelection(config: OnboardPersistedConfig, enabled: boolean): OnboardPersistedConfig {
|
||||
function applyVoiceSelection(
|
||||
config: OnboardPersistedConfig,
|
||||
enabled: boolean,
|
||||
): OnboardPersistedConfig {
|
||||
return {
|
||||
...config,
|
||||
features: {
|
||||
@@ -111,321 +117,319 @@ function applyVoiceSelection(config: OnboardPersistedConfig, enabled: boolean):
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePersistedVoiceSelection(config: OnboardPersistedConfig): boolean | null {
|
||||
const voiceModeEnabled = config.features?.voiceMode?.enabled
|
||||
if (typeof voiceModeEnabled === 'boolean') {
|
||||
return voiceModeEnabled
|
||||
const voiceModeEnabled = config.features?.voiceMode?.enabled;
|
||||
if (typeof voiceModeEnabled === "boolean") {
|
||||
return voiceModeEnabled;
|
||||
}
|
||||
|
||||
const dictationEnabled = config.features?.dictation?.enabled
|
||||
if (typeof dictationEnabled === 'boolean') {
|
||||
return dictationEnabled
|
||||
const dictationEnabled = config.features?.dictation?.enabled;
|
||||
if (typeof dictationEnabled === "boolean") {
|
||||
return dictationEnabled;
|
||||
}
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
async function resolveVoiceSelection(mode: OnboardOptions['voice']): Promise<boolean> {
|
||||
if (mode === 'enable') {
|
||||
return true
|
||||
async function resolveVoiceSelection(mode: OnboardOptions["voice"]): Promise<boolean> {
|
||||
if (mode === "enable") {
|
||||
return true;
|
||||
}
|
||||
if (mode === 'disable') {
|
||||
return false
|
||||
if (mode === "disable") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||
log.message('Non-interactive terminal detected; voice setup defaults to disabled.')
|
||||
return false
|
||||
log.message("Non-interactive terminal detected; voice setup defaults to disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const answer = await confirm({
|
||||
message: 'Enable voice features? (downloads local STT/TTS models in background)',
|
||||
active: 'Yes',
|
||||
inactive: 'No',
|
||||
message: "Enable voice features? (downloads local STT/TTS models in background)",
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
initialValue: false,
|
||||
})
|
||||
});
|
||||
|
||||
if (isCancel(answer)) {
|
||||
throw new OnboardCancelledError('Onboarding cancelled by user.')
|
||||
throw new OnboardCancelledError("Onboarding cancelled by user.");
|
||||
}
|
||||
|
||||
return answer
|
||||
return answer;
|
||||
}
|
||||
|
||||
type DownloadProgress = {
|
||||
modelId: string | null
|
||||
pct: number | null
|
||||
}
|
||||
modelId: string | null;
|
||||
pct: number | null;
|
||||
};
|
||||
|
||||
function parseDownloadProgress(logTail: string): DownloadProgress | null {
|
||||
const lines = logTail.split('\n').filter(Boolean)
|
||||
const lines = logTail.split("\n").filter(Boolean);
|
||||
|
||||
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
||||
const line = lines[index]
|
||||
if (!line || !line.includes('Downloading model artifact')) {
|
||||
continue
|
||||
const line = lines[index];
|
||||
if (!line || !line.includes("Downloading model artifact")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pctMatch = line.match(/"pct"\s*:\s*(\d{1,3})|\bpct[=:]\s*(\d{1,3})/)
|
||||
const modelMatch = line.match(
|
||||
/"modelId"\s*:\s*"([^"]+)"|\bmodelId[=:]\s*"?([^\s",}]+)/
|
||||
)
|
||||
const pctMatch = line.match(/"pct"\s*:\s*(\d{1,3})|\bpct[=:]\s*(\d{1,3})/);
|
||||
const modelMatch = line.match(/"modelId"\s*:\s*"([^"]+)"|\bmodelId[=:]\s*"?([^\s",}]+)/);
|
||||
|
||||
return {
|
||||
modelId: modelMatch?.[1] ?? modelMatch?.[2] ?? null,
|
||||
pct: pctMatch ? Number(pctMatch[1] ?? pctMatch[2]) : null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderProgressLine(progress: DownloadProgress): string {
|
||||
const modelSuffix = progress.modelId ? ` (${progress.modelId})` : ''
|
||||
const modelSuffix = progress.modelId ? ` (${progress.modelId})` : "";
|
||||
if (progress.pct === null) {
|
||||
return `Downloading speech model${modelSuffix}...`
|
||||
return `Downloading speech model${modelSuffix}...`;
|
||||
}
|
||||
return `Downloading speech model${modelSuffix}: ${progress.pct}%`
|
||||
return `Downloading speech model${modelSuffix}: ${progress.pct}%`;
|
||||
}
|
||||
|
||||
async function waitForDaemonReady(args: {
|
||||
home: string
|
||||
timeoutMs: number
|
||||
onStatus?: (message: string) => void
|
||||
home: string;
|
||||
timeoutMs: number;
|
||||
onStatus?: (message: string) => void;
|
||||
}): Promise<{ listen: string; host: string | null }> {
|
||||
const deadline = Date.now() + args.timeoutMs
|
||||
let lastStatus = ''
|
||||
let lastPrintedAt = 0
|
||||
const deadline = Date.now() + args.timeoutMs;
|
||||
let lastStatus = "";
|
||||
let lastPrintedAt = 0;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
const state = resolveLocalDaemonState({ home: args.home })
|
||||
const host = resolveTcpHostFromListen(state.listen)
|
||||
const state = resolveLocalDaemonState({ home: args.home });
|
||||
const host = resolveTcpHostFromListen(state.listen);
|
||||
|
||||
if (state.running && host) {
|
||||
const client = await tryConnectToDaemon({ host, timeout: 1200 })
|
||||
const client = await tryConnectToDaemon({ host, timeout: 1200 });
|
||||
if (client) {
|
||||
try {
|
||||
await client.fetchAgents()
|
||||
return { listen: state.listen, host }
|
||||
await client.fetchAgents();
|
||||
return { listen: state.listen, host };
|
||||
} catch {
|
||||
// Daemon process is alive but not API-ready yet.
|
||||
} finally {
|
||||
await client.close().catch(() => {})
|
||||
await client.close().catch(() => {});
|
||||
}
|
||||
}
|
||||
} else if (state.running && !host) {
|
||||
return { listen: state.listen, host: null }
|
||||
return { listen: state.listen, host: null };
|
||||
}
|
||||
|
||||
const progress = parseDownloadProgress(tailDaemonLog(args.home, 120) ?? '')
|
||||
const progressLine = progress ? renderProgressLine(progress) : null
|
||||
const statusMessage = progressLine ?? 'Waiting for daemon to become ready...'
|
||||
const progress = parseDownloadProgress(tailDaemonLog(args.home, 120) ?? "");
|
||||
const progressLine = progress ? renderProgressLine(progress) : null;
|
||||
const statusMessage = progressLine ?? "Waiting for daemon to become ready...";
|
||||
|
||||
if (statusMessage !== lastStatus) {
|
||||
args.onStatus?.(statusMessage)
|
||||
lastStatus = statusMessage
|
||||
lastPrintedAt = Date.now()
|
||||
args.onStatus?.(statusMessage);
|
||||
lastStatus = statusMessage;
|
||||
lastPrintedAt = Date.now();
|
||||
} else if (!args.onStatus && Date.now() - lastPrintedAt >= 3000) {
|
||||
console.log(statusMessage)
|
||||
lastPrintedAt = Date.now()
|
||||
console.log(statusMessage);
|
||||
lastPrintedAt = Date.now();
|
||||
}
|
||||
|
||||
await sleep(200)
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
const recentLogs = tailDaemonLog(args.home, 60)
|
||||
const recentLogs = tailDaemonLog(args.home, 60);
|
||||
throw new Error(
|
||||
[
|
||||
`Timed out after ${Math.ceil(args.timeoutMs / 1000)}s waiting for daemon readiness.`,
|
||||
recentLogs ? `Recent daemon logs:\n${recentLogs}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n\n')
|
||||
)
|
||||
.join("\n\n"),
|
||||
);
|
||||
}
|
||||
|
||||
function printNextSteps(pairingUrl: string | null, paseoHome: string, richUi: boolean): void {
|
||||
const daemonLogPath = path.join(paseoHome, 'daemon.log')
|
||||
const daemonLogPath = path.join(paseoHome, "daemon.log");
|
||||
const nextStepsLines = [
|
||||
pairingUrl
|
||||
? '1. Open Paseo and scan the QR code above, or paste the pairing link.'
|
||||
: '1. Open Paseo and connect to your daemon.',
|
||||
'2. Web app: https://app.paseo.sh',
|
||||
'3. Desktop app: https://github.com/getpaseo/paseo/releases/latest',
|
||||
'4. Docs: https://paseo.sh/docs',
|
||||
? "1. Open Paseo and scan the QR code above, or paste the pairing link."
|
||||
: "1. Open Paseo and connect to your daemon.",
|
||||
"2. Web app: https://app.paseo.sh",
|
||||
"3. Desktop app: https://github.com/getpaseo/paseo/releases/latest",
|
||||
"4. Docs: https://paseo.sh/docs",
|
||||
'5. Example: paseo run --output-schema schema.json "extract fields"',
|
||||
]
|
||||
];
|
||||
const quickReferenceLines = [
|
||||
'1. paseo --help',
|
||||
'2. paseo ls',
|
||||
"1. paseo --help",
|
||||
"2. paseo ls",
|
||||
'3. paseo run "your prompt"',
|
||||
'4. paseo status',
|
||||
"4. paseo status",
|
||||
`5. Daemon logs: ${daemonLogPath}`,
|
||||
]
|
||||
];
|
||||
|
||||
if (!richUi) {
|
||||
console.log('')
|
||||
console.log('Next steps:')
|
||||
console.log("");
|
||||
console.log("Next steps:");
|
||||
for (const line of nextStepsLines) {
|
||||
console.log(line)
|
||||
console.log(line);
|
||||
}
|
||||
console.log('')
|
||||
console.log('CLI quick reference:')
|
||||
console.log("");
|
||||
console.log("CLI quick reference:");
|
||||
for (const line of quickReferenceLines) {
|
||||
console.log(line)
|
||||
console.log(line);
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
renderNote(nextStepsLines.join('\n'), 'Next steps')
|
||||
renderNote(quickReferenceLines.join('\n'), 'CLI quick reference')
|
||||
renderNote(nextStepsLines.join("\n"), "Next steps");
|
||||
renderNote(quickReferenceLines.join("\n"), "CLI quick reference");
|
||||
}
|
||||
|
||||
export function onboardCommand(): Command {
|
||||
return new Command('onboard')
|
||||
.description('Run first-time setup, start daemon, and print pairing instructions')
|
||||
.option('--listen <listen>', 'Listen target (host:port, port, or unix socket path)')
|
||||
.option('--port <port>', 'Port to listen on (default: 6767)')
|
||||
.option('--home <path>', 'Paseo home directory (default: ~/.paseo)')
|
||||
.option('--no-relay', 'Disable relay connection')
|
||||
.option('--no-mcp', 'Disable the Agent MCP HTTP endpoint')
|
||||
return new Command("onboard")
|
||||
.description("Run first-time setup, start daemon, and print pairing instructions")
|
||||
.option("--listen <listen>", "Listen target (host:port, port, or unix socket path)")
|
||||
.option("--port <port>", "Port to listen on (default: 6767)")
|
||||
.option("--home <path>", "Paseo home directory (default: ~/.paseo)")
|
||||
.option("--no-relay", "Disable relay connection")
|
||||
.option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
|
||||
.option(
|
||||
'--allowed-hosts <hosts>',
|
||||
'Comma-separated Host allowlist values (example: "localhost,.example.com" or "true")'
|
||||
"--allowed-hosts <hosts>",
|
||||
'Comma-separated Host allowlist values (example: "localhost,.example.com" or "true")',
|
||||
)
|
||||
.option('--timeout <seconds>', 'Max time to wait for daemon readiness (default: 600)')
|
||||
.option('--voice <mode>', 'Voice setup mode: ask, enable, disable', 'ask')
|
||||
.option("--timeout <seconds>", "Max time to wait for daemon readiness (default: 600)")
|
||||
.option("--voice <mode>", "Voice setup mode: ask, enable, disable", "ask")
|
||||
.action(async (options: OnboardOptions) => {
|
||||
await runOnboard(options)
|
||||
})
|
||||
await runOnboard(options);
|
||||
});
|
||||
}
|
||||
|
||||
export async function runOnboard(options: OnboardOptions): Promise<void> {
|
||||
const richUi = process.stdin.isTTY && process.stdout.isTTY
|
||||
const richUi = process.stdin.isTTY && process.stdout.isTTY;
|
||||
if (richUi) {
|
||||
intro('Welcome to Paseo')
|
||||
intro("Welcome to Paseo");
|
||||
}
|
||||
|
||||
if (options.listen && options.port) {
|
||||
cancel('Cannot use --listen and --port together')
|
||||
process.exit(1)
|
||||
cancel("Cannot use --listen and --port together");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let timeoutMs = DEFAULT_READY_TIMEOUT_MS
|
||||
let timeoutMs = DEFAULT_READY_TIMEOUT_MS;
|
||||
try {
|
||||
timeoutMs = parseTimeoutMs(options.timeout)
|
||||
timeoutMs = parseTimeoutMs(options.timeout);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
cancel(message)
|
||||
process.exit(1)
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
cancel(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const paseoHome = resolveLocalPaseoHome(options.home)
|
||||
const paseoHome = resolveLocalPaseoHome(options.home);
|
||||
if (richUi) {
|
||||
renderNote(paseoHome, 'Paseo home')
|
||||
renderNote(paseoHome, "Paseo home");
|
||||
}
|
||||
|
||||
let persisted = loadPersistedConfig(paseoHome) as OnboardPersistedConfig
|
||||
const persistedVoiceSelection = resolvePersistedVoiceSelection(persisted)
|
||||
const shouldPrompt = options.voice === 'ask' || options.voice === undefined
|
||||
let voiceEnabled: boolean
|
||||
let persisted = loadPersistedConfig(paseoHome) as OnboardPersistedConfig;
|
||||
const persistedVoiceSelection = resolvePersistedVoiceSelection(persisted);
|
||||
const shouldPrompt = options.voice === "ask" || options.voice === undefined;
|
||||
let voiceEnabled: boolean;
|
||||
try {
|
||||
voiceEnabled =
|
||||
shouldPrompt && persistedVoiceSelection !== null
|
||||
? persistedVoiceSelection
|
||||
: await resolveVoiceSelection(options.voice)
|
||||
: await resolveVoiceSelection(options.voice);
|
||||
} catch (error) {
|
||||
if (error instanceof OnboardCancelledError) {
|
||||
cancel('Onboarding cancelled.')
|
||||
process.exit(0)
|
||||
return
|
||||
cancel("Onboarding cancelled.");
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (shouldPrompt && persistedVoiceSelection !== null) {
|
||||
log.message(`Using saved voice setup from config (${voiceEnabled ? 'enabled' : 'disabled'}).`)
|
||||
log.message(`Using saved voice setup from config (${voiceEnabled ? "enabled" : "disabled"}).`);
|
||||
}
|
||||
|
||||
persisted = applyVoiceSelection(persisted, voiceEnabled)
|
||||
savePersistedConfig(paseoHome, persisted)
|
||||
persisted = applyVoiceSelection(persisted, voiceEnabled);
|
||||
savePersistedConfig(paseoHome, persisted);
|
||||
|
||||
const config = loadConfig(paseoHome, { cli: toCliOverrides(options) })
|
||||
const config = loadConfig(paseoHome, { cli: toCliOverrides(options) });
|
||||
|
||||
const voiceStatus = voiceEnabled
|
||||
? 'Voice features enabled. Local speech models will be downloaded automatically if missing.'
|
||||
: 'Voice features disabled. Local speech models will not be downloaded.'
|
||||
log.message(voiceStatus)
|
||||
? "Voice features enabled. Local speech models will be downloaded automatically if missing."
|
||||
: "Voice features disabled. Local speech models will not be downloaded.";
|
||||
log.message(voiceStatus);
|
||||
|
||||
const stateBeforeStart = resolveLocalDaemonState({ home: options.home })
|
||||
const startSpinner = richUi ? spinner() : null
|
||||
const stateBeforeStart = resolveLocalDaemonState({ home: options.home });
|
||||
const startSpinner = richUi ? spinner() : null;
|
||||
|
||||
if (!stateBeforeStart.running) {
|
||||
try {
|
||||
if (startSpinner) {
|
||||
startSpinner.start('Starting daemon...')
|
||||
startSpinner.start("Starting daemon...");
|
||||
} else {
|
||||
log.message('Starting daemon...')
|
||||
log.message("Starting daemon...");
|
||||
}
|
||||
const startup = await startLocalDaemonDetached(options)
|
||||
const startup = await startLocalDaemonDetached(options);
|
||||
if (startSpinner) {
|
||||
startSpinner.stop(`Daemon started (PID ${startup.pid ?? 'unknown'})`)
|
||||
startSpinner.stop(`Daemon started (PID ${startup.pid ?? "unknown"})`);
|
||||
} else {
|
||||
log.message(`Daemon started (PID ${startup.pid ?? 'unknown'})`)
|
||||
log.message(`Daemon started (PID ${startup.pid ?? "unknown"})`);
|
||||
}
|
||||
log.message(`Logs: ${startup.logPath}`)
|
||||
log.message(`Logs: ${startup.logPath}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (startSpinner) {
|
||||
startSpinner.error(message)
|
||||
startSpinner.error(message);
|
||||
} else {
|
||||
log.error(message)
|
||||
log.error(message);
|
||||
}
|
||||
process.exit(1)
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
log.message(`Daemon already running (PID ${stateBeforeStart.pidInfo?.pid ?? 'unknown'}).`)
|
||||
log.message(`Daemon already running (PID ${stateBeforeStart.pidInfo?.pid ?? "unknown"}).`);
|
||||
}
|
||||
|
||||
let readyState: { listen: string; host: string | null }
|
||||
const readySpinner = richUi ? spinner() : null
|
||||
let readyState: { listen: string; host: string | null };
|
||||
const readySpinner = richUi ? spinner() : null;
|
||||
try {
|
||||
if (readySpinner) {
|
||||
readySpinner.start('Waiting for daemon to become ready...')
|
||||
readySpinner.start("Waiting for daemon to become ready...");
|
||||
} else {
|
||||
log.message('Waiting for daemon to become ready...')
|
||||
log.message("Waiting for daemon to become ready...");
|
||||
}
|
||||
readyState = await waitForDaemonReady({
|
||||
home: options.home ?? paseoHome,
|
||||
timeoutMs,
|
||||
onStatus: readySpinner ? (message) => readySpinner.message(message) : undefined,
|
||||
})
|
||||
});
|
||||
if (readySpinner) {
|
||||
readySpinner.stop(`Daemon ready on ${readyState.listen}`)
|
||||
readySpinner.stop(`Daemon ready on ${readyState.listen}`);
|
||||
} else {
|
||||
log.message(`Daemon ready on ${readyState.listen}`)
|
||||
log.message(`Daemon ready on ${readyState.listen}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (readySpinner) {
|
||||
readySpinner.error(message)
|
||||
readySpinner.error(message);
|
||||
} else {
|
||||
log.error(message)
|
||||
log.error(message);
|
||||
}
|
||||
process.exit(1)
|
||||
return
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.relayEnabled === false) {
|
||||
log.warn('Relay is disabled; pairing offer is unavailable for this daemon.')
|
||||
printNextSteps(null, paseoHome, richUi)
|
||||
log.warn("Relay is disabled; pairing offer is unavailable for this daemon.");
|
||||
printNextSteps(null, paseoHome, richUi);
|
||||
if (richUi) {
|
||||
outro('Paseo daemon is running.')
|
||||
outro("Paseo daemon is running.");
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const pairing = await generateLocalPairingOffer({
|
||||
@@ -435,24 +439,24 @@ export async function runOnboard(options: OnboardOptions): Promise<void> {
|
||||
relayPublicEndpoint: config.relayPublicEndpoint,
|
||||
appBaseUrl: config.appBaseUrl,
|
||||
includeQr: true,
|
||||
})
|
||||
});
|
||||
|
||||
if (!pairing.url) {
|
||||
log.warn('Relay pairing URL is unavailable for this daemon configuration.')
|
||||
printNextSteps(null, paseoHome, richUi)
|
||||
log.warn("Relay pairing URL is unavailable for this daemon configuration.");
|
||||
printNextSteps(null, paseoHome, richUi);
|
||||
if (richUi) {
|
||||
outro('Paseo daemon is running.')
|
||||
outro("Paseo daemon is running.");
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
renderNote(
|
||||
pairing.qr ?? 'QR is unavailable in this terminal. Use the pairing link below.',
|
||||
'Scan to pair'
|
||||
)
|
||||
renderNote(pairing.url, 'Pairing link')
|
||||
printNextSteps(pairing.url, paseoHome, richUi)
|
||||
pairing.qr ?? "QR is unavailable in this terminal. Use the pairing link below.",
|
||||
"Scan to pair",
|
||||
);
|
||||
renderNote(pairing.url, "Pairing link");
|
||||
printNextSteps(pairing.url, paseoHome, richUi);
|
||||
if (richUi) {
|
||||
outro('Paseo is ready!')
|
||||
outro("Paseo is ready!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user