@@ -207,6 +221,180 @@ export default function DashboardPage() {
)
}
+function ToolOnboardingPanel() {
+ const [tools, setTools] = useState([])
+ const [toolsError, setToolsError] = useState(null)
+ const [toolsBusy, setToolsBusy] = useState(null)
+ const [toolPreview, setToolPreview] = useState(
+ null,
+ )
+ const [loading, setLoading] = useState(true)
+
+ const connectedCount = tools.filter((tool) => tool.connected).length
+ const detectedCount = tools.filter((tool) => tool.detected).length
+
+ const refreshTools = useCallback(async () => {
+ setToolsError(null)
+ setLoading(true)
+ try {
+ setTools(await detectDesktopTools())
+ } catch (err) {
+ setToolsError(formatUnknownError(err, "Could not detect local tools"))
+ } finally {
+ setLoading(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ refreshTools()
+ }, [refreshTools])
+
+ async function previewConnect(toolId: DesktopToolId) {
+ setToolsError(null)
+ setToolsBusy(toolId)
+ try {
+ setToolPreview(await previewConnectDesktopTool(toolId))
+ } catch (err) {
+ setToolsError(formatUnknownError(err, "Could not prepare setup"))
+ } finally {
+ setToolsBusy(null)
+ }
+ }
+
+ async function applyPreview() {
+ if (!toolPreview) return
+
+ setToolsError(null)
+ setToolsBusy(toolPreview.tool.id)
+ try {
+ await connectDesktopTool(toolPreview.tool.id)
+ setToolPreview(null)
+ await refreshTools()
+ } catch (err) {
+ setToolsError(formatUnknownError(err, "Could not connect tool"))
+ } finally {
+ setToolsBusy(null)
+ }
+ }
+
+ return (
+
+
+
+
+
+ Connect AI tools
+
+
+ Detected {detectedCount} local tools. {connectedCount} connected.
+
+
+
+
+
+
+ {tools.map((tool) => {
+ const Icon = toolIcon(tool.id)
+ return (
+
+
+
+
+
+
+
+
+
+ {tool.name}
+
+
+ {toolStatusLabel(tool)}
+
+
+
+ {tool.connected ? (
+
+ ) : null}
+
+
+ {tool.detail}
+
+
+
+
+ )
+ })}
+
+
+ {toolsError ? (
+ {toolsError}
+ ) : null}
+
+ {toolPreview ? (
+
+
+
+
+ Connect {toolPreview.tool.name}
+
+
+ {toolPreview.configPath}
+
+
+
+
+
+
+
+
+ {toolPreview.diff}
+
+
+ ) : null}
+
+ )
+}
+
function ComposerAction({
icon: Icon,
label,
@@ -228,6 +416,31 @@ function ComposerAction({
)
}
+function toolIcon(toolId: DesktopToolId) {
+ switch (toolId) {
+ case "claude-code":
+ return Bot
+ case "codex":
+ return Terminal
+ case "cursor":
+ return Code2
+ }
+}
+
+function toolStatusLabel(tool: DesktopToolStatus) {
+ if (tool.connected) return "Connected"
+ if (tool.detected) return "Detected"
+ return "Not detected"
+}
+
+function formatUnknownError(error: unknown, fallback: string) {
+ return error instanceof Error
+ ? error.message
+ : typeof error === "string"
+ ? error
+ : fallback
+}
+
function RecentMemoryRow({
document,
active,