feat(desktop): add daemon status dialog to built-in daemon settings

Add a "Full status" button in the desktop daemon section that runs
`paseo daemon status` via Electron IPC and displays the raw output
in a modal.

Also fixes Commander.js argv parsing when the CLI is invoked via
Electron's ELECTRON_RUN_AS_NODE — Commander auto-detects the Electron
environment and skips only 1 argv element instead of 2, causing
"unknown command" errors. Fixed by explicitly passing { from: "node" }.
This commit is contained in:
Mohamed Boudra
2026-04-03 18:49:59 +07:00
parent f924d33076
commit 4ca04fc661
5 changed files with 106 additions and 1 deletions
@@ -14,6 +14,7 @@ import {
Copy,
FileText,
Smartphone,
Activity,
} from "lucide-react-native";
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
import { Button } from "@/components/ui/button";
@@ -22,6 +23,7 @@ import { confirmDialog } from "@/utils/confirm-dialog";
import { openExternalUrl } from "@/utils/open-external-url";
import { getLocalDaemonVersion, isVersionMismatch } from "@/desktop/updates/desktop-updates";
import {
getCliDaemonStatus,
getCliSymlinkInstructions,
getDesktopDaemonLogs,
getDesktopDaemonPairing,
@@ -62,6 +64,9 @@ export function LocalDaemonSection({ appVersion, showLifecycleControls }: LocalD
const [cliSymlinkInstructions, setCliSymlinkInstructions] =
useState<CliSymlinkInstructions | null>(null);
const [pairingStatusMessage, setPairingStatusMessage] = useState<string | null>(null);
const [cliStatusOutput, setCliStatusOutput] = useState<string | null>(null);
const [isCliStatusModalOpen, setIsCliStatusModalOpen] = useState(false);
const [isLoadingCliStatus, setIsLoadingCliStatus] = useState(false);
const loadDaemonData = useCallback(() => {
if (!showSection) {
@@ -315,6 +320,33 @@ export function LocalDaemonSection({ appVersion, showLifecycleControls }: LocalD
});
}, [pairingOffer?.url]);
const handleOpenCliStatus = useCallback(async () => {
setIsLoadingCliStatus(true);
try {
setCliStatusOutput(await getCliDaemonStatus());
setIsCliStatusModalOpen(true);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
setCliStatusOutput(`Failed to fetch daemon status: ${message}`);
setIsCliStatusModalOpen(true);
} finally {
setIsLoadingCliStatus(false);
}
}, []);
const handleCopyCliStatus = useCallback(() => {
if (!cliStatusOutput) {
return;
}
void Clipboard.setStringAsync(cliStatusOutput)
.then(() => {
Alert.alert("Copied", "Status copied to clipboard.");
})
.catch((error) => {
console.error("[Settings] Failed to copy daemon status", error);
});
}, [cliStatusOutput]);
if (!showSection) {
return null;
}
@@ -458,6 +490,23 @@ export function LocalDaemonSection({ appVersion, showLifecycleControls }: LocalD
Pair device
</Button>
</View>
<View style={[styles.row, styles.rowBorder]}>
<View style={styles.rowContent}>
<Text style={styles.rowTitle}>Full status</Text>
<Text style={styles.hintText}>
Runs `paseo daemon status` and shows the output.
</Text>
</View>
<Button
variant="outline"
size="sm"
leftIcon={<Activity size={theme.iconSize.sm} color={theme.colors.foreground} />}
onPress={() => void handleOpenCliStatus()}
disabled={isLoadingCliStatus}
>
{isLoadingCliStatus ? "Loading..." : "View status"}
</Button>
</View>
</View>
{daemonVersionMismatch ? (
@@ -524,6 +573,28 @@ export function LocalDaemonSection({ appVersion, showLifecycleControls }: LocalD
</Text>
</View>
</AdaptiveModalSheet>
<AdaptiveModalSheet
visible={isCliStatusModalOpen}
onClose={() => setIsCliStatusModalOpen(false)}
title="Daemon status"
testID="daemon-cli-status-dialog"
snapPoints={["60%", "85%"]}
>
<View style={styles.modalBody}>
<Text style={styles.logOutput} selectable>
{cliStatusOutput ?? ""}
</Text>
<View style={styles.modalActions}>
<Button variant="outline" size="sm" onPress={() => setIsCliStatusModalOpen(false)}>
Close
</Button>
<Button size="sm" onPress={handleCopyCliStatus}>
Copy
</Button>
</View>
</View>
</AdaptiveModalSheet>
</View>
);
}
@@ -162,6 +162,14 @@ export async function getCliSymlinkInstructions(): Promise<CliSymlinkInstruction
return parseCliSymlinkInstructions(await invokeDesktopCommand("cli_symlink_instructions"));
}
export async function getCliDaemonStatus(): Promise<string> {
const raw = await invokeDesktopCommand<unknown>("cli_daemon_status");
if (typeof raw !== "string") {
throw new Error("Unexpected CLI daemon status response.");
}
return raw;
}
export type LocalTransportEventUnlisten = () => void;
export type LocalTransportEventHandler = (payload: LocalTransportEventPayload) => void;