import { Button, CONTROL_TOOLTIP_DELAY, Icon, InlineCode, LoadingIcon, Tooltip, } from "@yaakapp-internal/ui"; import classNames from "classnames"; import { useAtomValue } from "jotai"; import type { ReactNode } from "react"; import { useDecryptList, useEditDecryptList } from "../hooks/useDecryptList"; import { useRpcMutation } from "../hooks/useRpcMutation"; import { everythingEdit } from "../lib/decrypt"; import { decryptStep } from "../lib/firstRun"; import { encryptingPercent } from "../lib/encrypting"; import { proxyStateAtom } from "../lib/store"; import { useCopyEnvironment, useProxySetup, useSystemProxySwitch } from "./ProxyStatus"; type StepState = "todo" | "next" | "wait" | "done" | "fail"; /** * The list's empty state until traffic arrives: trust the certificate, turn on the system proxy, * choose what to decrypt, then see traffic. Not dismissable and nothing remembered; the first flow replaces it. Copy * environment beside it serves someone who wants one terminal proxied and nothing system-wide. */ export function FirstRun() { const proxyState = useAtomValue(proxyStateAtom); const setup = useProxySetup(); const { caStatus, trust, systemProxy, setSystemProxy } = setup; const systemSwitch = useSystemProxySwitch(setup); const execute = useRpcMutation("execute_action"); const retry = useRpcMutation("take_over"); const copyEnvironment = useCopyEnvironment(); const decryptList = useDecryptList(); const saveDecryptList = useEditDecryptList(); const running = proxyState?.state === "running"; // Changed only by the process that owns the engine, so not from here while the CLI does. const otherOwner = proxyState?.otherOwner != null; const recording = proxyState?.recording !== false; const trusted = caStatus.data?.trusted === true; const systemSupported = systemProxy.data?.supported !== false; const systemOn = systemProxy.data?.enabled === true; const trustState: StepState = trusted ? "done" : trust.isPending ? "wait" : trust.error != null ? "fail" : "next"; const systemState: StepState = setSystemProxy.isPending ? "wait" : systemOn ? "done" : systemSwitch.failed ? "fail" : trusted ? "next" : "todo"; const decrypt = decryptStep(decryptList, trusted); const everything = decrypt.choice === "everything"; const setEverything = (on: boolean) => saveDecryptList.mutate(everythingEdit(on)); const ready = trusted && (systemOn || !systemSupported); const encrypting = !running && !otherOwner ? encryptingPercent(proxyState) : null; const trafficState: StepState = encrypting != null ? "wait" : !running && !otherOwner ? "fail" : ready ? "wait" : "todo"; return (
Set up Yaak Proxy
{systemSupported ? "Four" : "Three"} steps, once. Then every app on this Mac sends its traffic here.
trust.mutate({})} > {trustState === "fail" ? "Try Again" : "Trust Certificate"} ) : null } /> {systemSupported && ( setSystemProxy.mutate({ enabled: true })} > {systemState === "fail" ? "Try Again" : "Turn On"} ) : null } /> )} Everything ) : ( Pick as they appear ) } action={ everything ? ( ) : ( ) } /> retry.mutate({})} > Retry ) : !recording && running ? ( ) : null } />
Only need one terminal or one app? Copy the environment instead.
Working with an agent? yaak mcp gives it the proxy's flows and actions.
Captured traffic is encrypted on disk with a key kept in your keychain, so macOS may ask to let Yaak Proxy use it.
); } function Step({ n, state, title, description, action, }: { n: number; state: StepState; title: string; description: ReactNode; action: ReactNode; }) { return (
{title}
{description}
{action}
); } function StepMark({ n, state }: { n: number; state: StepState }) { const base = "w-[1.4rem] h-[1.4rem] rounded-full grid place-items-center text-xs"; switch (state) { case "done": return ( ); case "fail": return ( ); case "wait": return ( ); case "next": return {n}; case "todo": return {n}; } }