1import type { DesktopCliCommandState } from "@t3tools/contracts";
2import { useCallback, useEffect, useState } from "react";
3
4import { Button } from "../ui/button";
5import { stackedThreadToast, toastManager } from "../ui/toast";
6import { SettingsRow } from "./settingsLayout";
7import { searchableSetting } from "./settingsSearch";
8
9/**
10 * Settings → `t3` command: puts the desktop app's bundled CLI on PATH, or takes
11 * it off again. Hidden where the desktop build has no launcher to install.
12 */
13export function CliCommandSettingsRow() {
14 const bridge = typeof window === "undefined" ? undefined : window.desktopBridge?.cliCommand;
15 const [state, setState] = useState<DesktopCliCommandState | null>(null);
16 const [pending, setPending] = useState(false);
17
18 useEffect(() => {
19 if (!bridge) return;
20 let cancelled = false;
21 void bridge
22 .getState()
23 .then((next) => {
24 if (!cancelled) setState(next);
25 })
26 .catch(() => undefined);
27 return () => {
28 cancelled = true;
29 };
30 }, [bridge]);
31
32 const change = useCallback(
33 (action: "install" | "uninstall") => {
34 if (!bridge || pending) return;
35 setPending(true);
36 void bridge[action]()
37 .then(setState)
38 .catch((error: unknown) => {
39 toastManager.add(
40 stackedThreadToast({
41 type: "error",
42 title: action === "install" ? "Could not install t3" : "Could not remove t3",
43 description: error instanceof Error ? error.message : "Something went wrong.",
44 }),
45 );
46 })
47 .finally(() => setPending(false));
48 },
49 [bridge, pending],
50 );
51
52 if (!bridge || !state?.supported) return null;
53 const installed = state.installedPath !== null;
54 const description = state.shadowedBy
55 ? `Another t3 at ${state.shadowedBy} runs first in a new terminal. Remove it to use The Knife's.`
56 : !installed
57 ? "Run The Knife's CLI as `t3` from any terminal."
58 : state.onPath
59 ? `Installed at ${state.installedPath}. Open a new terminal to use it.`
60 : `Installed at ${state.installedPath}, which is not on your PATH yet. Add its folder to your PATH to run \`t3\`.`;
61
62 return (
63 <SettingsRow
64 {...searchableSetting("cli-command")}
65 description={description}
66 control={
67 <Button
68 size="sm"
69 variant="outline"
70 disabled={pending}
71 onClick={() => change(installed ? "uninstall" : "install")}
72 >
73 {installed ? "Remove" : "Install"}
74 </Button>
75 }
76 />
77 );
78}