feat(api): add command authentication attempts

This commit is contained in:
Dax Raad 2026-07-15 19:08:14 -04:00
commit b5177adb5b
18 changed files with 1138 additions and 16 deletions

View file

@ -1,6 +1,7 @@
import { TextAttributes } from "@opentui/core"
import type {
ConnectionInfo,
IntegrationCommandConnectOutput,
IntegrationInfo,
IntegrationOauthConnectOutput,
IntegrationOAuthMethod,
@ -28,6 +29,7 @@ const INTEGRATION_PRIORITY: Record<string, number> = {
type ConnectMethod = Exclude<IntegrationInfo["methods"][number], { type: "env" }>
type IntegrationAttempt = IntegrationOauthConnectOutput["data"]
type CommandAttempt = IntegrationCommandConnectOutput["data"]
type OnIntegrationConnected = (providerID?: string) => void
export function integrationOptions(list: IntegrationInfo[]) {
@ -167,9 +169,130 @@ function openMethod(
dialog.replace(() => <KeyMethod integration={integration} method={method} onConnected={onConnected} />)
return
}
if (method.type === "command") {
dialog.replace(() => <CommandStarting integration={integration} method={method} onConnected={onConnected} />)
return
}
void beginOAuth(integration, method, dialog, onConnected)
}
function CommandStarting(props: {
integration: IntegrationInfo
method: Extract<ConnectMethod, { type: "command" }>
onConnected?: OnIntegrationConnected
}) {
const data = useData()
const dialog = useDialog()
const client = useClient()
const toast = useToast()
onMount(() => {
void client.api.integration.command
.connect({
integrationID: props.integration.id,
methodID: props.method.id,
location: location(data),
})
.then((result) =>
dialog.replace(() => (
<CommandPending
integration={props.integration}
title={props.method.label}
attempt={result.data}
onConnected={props.onConnected}
/>
)),
)
.catch((cause) => {
toast.show({ variant: "error", message: message(cause) })
dialog.clear()
})
})
return <CommandView title={props.method.label} output="" message="Starting command..." />
}
function CommandPending(props: {
integration: IntegrationInfo
title: string
attempt: CommandAttempt
onConnected?: OnIntegrationConnected
}) {
const data = useData()
const dialog = useDialog()
const client = useClient()
const toast = useToast()
const [output, setOutput] = createSignal("")
let timer: ReturnType<typeof setTimeout> | undefined
let settled = false
const poll = () => {
void client.api.integration.command
.status({
integrationID: props.integration.id,
attemptID: props.attempt.attemptID,
location: location(data),
})
.then((result) => {
const status = result.data
if (status.status === "pending") {
setOutput(status.message ?? "")
timer = setTimeout(poll, 500)
return
}
settled = true
if (status.status === "complete") {
void connected(props.integration, data, dialog, toast, props.onConnected)
return
}
toast.show({
variant: "error",
message: status.status === "failed" ? status.message : "Authentication expired",
})
dialog.clear()
})
.catch((cause) => {
settled = true
toast.show({ variant: "error", message: message(cause) })
dialog.clear()
})
}
onMount(poll)
onCleanup(() => {
if (timer) clearTimeout(timer)
if (settled) return
void client.api.integration.command.cancel({
integrationID: props.integration.id,
attemptID: props.attempt.attemptID,
location: location(data),
})
})
return <CommandView title={props.title} output={output()} message="Waiting for command to finish..." />
}
function CommandView(props: { title: string; output: string; message: string }) {
const dialog = useDialog()
const { theme } = useTheme()
return (
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={theme.text}>
{props.title}
</text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
esc close
</text>
</box>
<box backgroundColor={theme.backgroundElement} paddingLeft={1} paddingRight={1}>
<text fg={theme.text}>{props.output}</text>
</box>
<text fg={theme.textMuted}>{props.message}</text>
</box>
)
}
function KeyMethod(props: {
integration: IntegrationInfo
method: Extract<ConnectMethod, { type: "key" }>