From 03fc685f72f6cb8355a0e83d9aa5f0e979ba8dc2 Mon Sep 17 00:00:00 2001 From: decolua Date: Tue, 3 Mar 2026 10:10:03 +0700 Subject: [PATCH] Add OpenCode CLI --- public/providers/opencode.png | Bin 0 -> 16378 bytes .../dashboard/cli-tools/CLIToolsPageClient.js | 7 +- .../components/AntigravityToolCard.js | 2 +- .../cli-tools/components/ClaudeToolCard.js | 2 +- .../cli-tools/components/CodexToolCard.js | 2 +- .../cli-tools/components/DefaultToolCard.js | 11 +- .../cli-tools/components/DroidToolCard.js | 2 +- .../cli-tools/components/OpenClawToolCard.js | 2 +- .../cli-tools/components/OpenCodeToolCard.js | 308 ++++++++++++++++++ .../dashboard/cli-tools/components/index.js | 1 + .../api/cli-tools/opencode-settings/route.js | 153 +++++++++ src/shared/constants/cliTools.js | 28 +- 12 files changed, 496 insertions(+), 22 deletions(-) create mode 100644 public/providers/opencode.png create mode 100644 src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js create mode 100644 src/app/api/cli-tools/opencode-settings/route.js diff --git a/public/providers/opencode.png b/public/providers/opencode.png new file mode 100644 index 0000000000000000000000000000000000000000..2e709e1c49db5064be4dc445a3d6e3fb4407c1da GIT binary patch literal 16378 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 n*Aliz+N; + case "opencode": + return ; case "droid": return ; case "openclaw": @@ -206,7 +209,7 @@ export default function CLIToolsPageClient({ machineId }) { case "antigravity": return ; default: - return ; + return ; } }; diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js index c9322880..d2114993 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js @@ -209,7 +209,7 @@ export default function AntigravityToolCard({ const isRunning = status?.running; return ( - +
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js index 6d448413..2db68c98 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js @@ -206,7 +206,7 @@ export default function ClaudeToolCard({ }; return ( - +
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js index aac93462..edfe98c2 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js @@ -169,7 +169,7 @@ wire_api = "responses" }; return ( - +
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js index 6d643ff5..5f00e3f0 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js @@ -4,7 +4,7 @@ import { useState } from "react"; import { Card, ModelSelectModal } from "@/shared/components"; import Image from "next/image"; -export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, baseUrl, apiKeys, activeProviders = [], cloudEnabled = false }) { +export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, baseUrl, apiKeys, activeProviders = [], cloudEnabled = false, tunnelEnabled = false }) { const [copiedField, setCopiedField] = useState(null); const [showModelModal, setShowModelModal] = useState(false); const [modelValue, setModelValue] = useState(""); @@ -125,11 +125,11 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba return (
{tool.notes.map((note, index) => { - // Skip cloudCheck note if cloud is enabled - if (note.type === "cloudCheck" && cloudEnabled) return null; + // Skip cloudCheck note if tunnel or cloud is enabled + if (note.type === "cloudCheck" && (cloudEnabled || tunnelEnabled)) return null; const isWarning = note.type === "warning"; - const isError = note.type === "cloudCheck" && !cloudEnabled; + const isError = note.type === "cloudCheck" && !cloudEnabled && !tunnelEnabled; let bgClass = "bg-blue-500/10 border-blue-500/30"; let textClass = "text-blue-600 dark:text-blue-400"; @@ -160,6 +160,7 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba }; const canShowGuide = () => { + if (tool.requiresExternalUrl && !cloudEnabled && !tunnelEnabled) return false; if (tool.requiresCloud && !cloudEnabled) return false; return true; }; @@ -258,7 +259,7 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba }; return ( - +
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js index 799f44f0..6282ba2c 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js @@ -200,7 +200,7 @@ export default function DroidToolCard({ }; return ( - +
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js index 1aa5af56..ac08e747 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js @@ -213,7 +213,7 @@ export default function OpenClawToolCard({ }; return ( - +
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js new file mode 100644 index 00000000..4ab16783 --- /dev/null +++ b/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js @@ -0,0 +1,308 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components"; +import Image from "next/image"; + +export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, apiKeys, activeProviders, cloudEnabled, initialStatus }) { + const [status, setStatus] = useState(initialStatus || null); + const [checking, setChecking] = useState(false); + const [applying, setApplying] = useState(false); + const [restoring, setRestoring] = useState(false); + const [message, setMessage] = useState(null); + const [showInstallGuide, setShowInstallGuide] = useState(false); + const [selectedApiKey, setSelectedApiKey] = useState(""); + const [selectedModel, setSelectedModel] = useState(""); + const [modalOpen, setModalOpen] = useState(false); + const [modelAliases, setModelAliases] = useState({}); + const [showManualConfigModal, setShowManualConfigModal] = useState(false); + const [customBaseUrl, setCustomBaseUrl] = useState(""); + + useEffect(() => { + if (apiKeys?.length > 0 && !selectedApiKey) { + setSelectedApiKey(apiKeys[0].key); + } + }, [apiKeys, selectedApiKey]); + + useEffect(() => { + if (initialStatus) setStatus(initialStatus); + }, [initialStatus]); + + useEffect(() => { + if (isExpanded && !status) { + checkStatus(); + fetchModelAliases(); + } + if (isExpanded) fetchModelAliases(); + }, [isExpanded]); + + // Sync model from existing config + useEffect(() => { + if (status?.config?.model?.startsWith("9router/")) { + setSelectedModel(status.config.model.replace("9router/", "")); + } + }, [status]); + + const fetchModelAliases = async () => { + try { + const res = await fetch("/api/models/alias"); + const data = await res.json(); + if (res.ok) setModelAliases(data.aliases || {}); + } catch (error) { + console.log("Error fetching model aliases:", error); + } + }; + + const getConfigStatus = () => { + if (!status?.installed) return null; + if (!status.config) return "not_configured"; + const url = status.config?.provider?.["9router"]?.options?.baseURL || ""; + const isLocal = url.includes("localhost") || url.includes("127.0.0.1"); + return status.has9Router && (isLocal || url.includes(baseUrl)) ? "configured" : status.has9Router ? "other" : "not_configured"; + }; + + const configStatus = getConfigStatus(); + + const getEffectiveBaseUrl = () => { + const url = customBaseUrl || baseUrl; + return url.endsWith("/v1") ? url : `${url}/v1`; + }; + + const getDisplayUrl = () => customBaseUrl || `${baseUrl}/v1`; + + const checkStatus = async () => { + setChecking(true); + try { + const res = await fetch("/api/cli-tools/opencode-settings"); + const data = await res.json(); + setStatus(data); + } catch (error) { + setStatus({ installed: false, error: error.message }); + } finally { + setChecking(false); + } + }; + + const handleApply = async () => { + setApplying(true); + setMessage(null); + try { + const keyToUse = (selectedApiKey && selectedApiKey.trim()) + ? selectedApiKey + : (!cloudEnabled ? "sk_9router" : selectedApiKey); + + const res = await fetch("/api/cli-tools/opencode-settings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel }), + }); + const data = await res.json(); + if (res.ok) { + setMessage({ type: "success", text: "Settings applied successfully!" }); + checkStatus(); + } else { + setMessage({ type: "error", text: data.error || "Failed to apply settings" }); + } + } catch (error) { + setMessage({ type: "error", text: error.message }); + } finally { + setApplying(false); + } + }; + + const handleReset = async () => { + setRestoring(true); + setMessage(null); + try { + const res = await fetch("/api/cli-tools/opencode-settings", { method: "DELETE" }); + const data = await res.json(); + if (res.ok) { + setMessage({ type: "success", text: "Settings reset successfully!" }); + setSelectedModel(""); + checkStatus(); + } else { + setMessage({ type: "error", text: data.error || "Failed to reset settings" }); + } + } catch (error) { + setMessage({ type: "error", text: error.message }); + } finally { + setRestoring(false); + } + }; + + const getManualConfigs = () => { + const keyToUse = (selectedApiKey && selectedApiKey.trim()) + ? selectedApiKey + : (!cloudEnabled ? "sk_9router" : ""); + + return [{ + filename: "~/.config/opencode/opencode.json", + content: JSON.stringify({ + provider: { + "9router": { + npm: "@ai-sdk/openai-compatible", + options: { baseURL: getEffectiveBaseUrl(), apiKey: keyToUse }, + models: { [selectedModel || "provider/model-id"]: { name: selectedModel || "provider/model-id" } }, + }, + }, + model: `9router/${selectedModel || "provider/model-id"}`, + }, null, 2), + }]; + }; + + return ( + +
+
+
+ {tool.name} { e.target.style.display = "none"; }} /> +
+
+
+

{tool.name}

+ {configStatus === "configured" && Connected} + {configStatus === "not_configured" && Not configured} + {configStatus === "other" && Other} +
+

{tool.description}

+
+
+ expand_more +
+ + {isExpanded && ( +
+ {checking && ( +
+ progress_activity + Checking OpenCode CLI... +
+ )} + + {!checking && status && !status.installed && ( +
+
+ warning +
+

OpenCode CLI not installed

+

Please install OpenCode CLI to use auto-apply feature.

+
+ +
+ {showInstallGuide && ( +
+

Installation Guide

+
+
+

macOS / Linux:

+ npm install -g opencode-ai +
+

After installation, run opencode to verify.

+
+
+ )} +
+ )} + + {!checking && status?.installed && ( + <> +
+ {/* Current base URL */} + {status?.config?.provider?.["9router"]?.options?.baseURL && ( +
+ Current + arrow_forward + + {status.config.provider["9router"].options.baseURL} + +
+ )} + + {/* Base URL */} +
+ Base URL + arrow_forward + setCustomBaseUrl(e.target.value)} + placeholder="https://.../v1" + className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + /> + {customBaseUrl && customBaseUrl !== `${baseUrl}/v1` && ( + + )} +
+ + {/* API Key */} +
+ API Key + arrow_forward + {apiKeys.length > 0 ? ( + + ) : ( + + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} + + )} +
+ + {/* Model */} +
+ Model + arrow_forward + setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> + + {selectedModel && } +
+
+ + {message && ( +
+ {message.type === "success" ? "check_circle" : "error"} + {message.text} +
+ )} + +
+ + + +
+ + )} +
+ )} + + setModalOpen(false)} + onSelect={(model) => { setSelectedModel(model.value); setModalOpen(false); }} + selectedModel={selectedModel} + activeProviders={activeProviders} + modelAliases={modelAliases} + title="Select Model for OpenCode" + /> + + setShowManualConfigModal(false)} + title="OpenCode - Manual Configuration" + configs={getManualConfigs()} + /> +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/index.js b/src/app/(dashboard)/dashboard/cli-tools/components/index.js index 5aed48d8..078970f4 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/index.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/index.js @@ -4,4 +4,5 @@ export { default as DroidToolCard } from "./DroidToolCard"; export { default as OpenClawToolCard } from "./OpenClawToolCard"; export { default as DefaultToolCard } from "./DefaultToolCard"; export { default as AntigravityToolCard } from "./AntigravityToolCard"; +export { default as OpenCodeToolCard } from "./OpenCodeToolCard"; diff --git a/src/app/api/cli-tools/opencode-settings/route.js b/src/app/api/cli-tools/opencode-settings/route.js new file mode 100644 index 00000000..bf140df0 --- /dev/null +++ b/src/app/api/cli-tools/opencode-settings/route.js @@ -0,0 +1,153 @@ +"use server"; + +import { NextResponse } from "next/server"; +import { exec } from "child_process"; +import { promisify } from "util"; +import fs from "fs/promises"; +import path from "path"; +import os from "os"; + +const execAsync = promisify(exec); + +const getConfigDir = () => path.join(os.homedir(), ".config", "opencode"); +const getConfigPath = () => path.join(getConfigDir(), "opencode.json"); + +const checkOpenCodeInstalled = async () => { + try { + const isWindows = os.platform() === "win32"; + const command = isWindows ? "where opencode" : "command -v opencode"; + await execAsync(command, { windowsHide: true }); + return true; + } catch { + return false; + } +}; + +const readConfig = async () => { + try { + const content = await fs.readFile(getConfigPath(), "utf-8"); + return JSON.parse(content); + } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +}; + +const has9RouterConfig = (config) => { + if (!config?.provider) return false; + return !!config.provider["9router"]; +}; + +// GET - Check opencode CLI and read current settings +export async function GET() { + try { + const isInstalled = await checkOpenCodeInstalled(); + + if (!isInstalled) { + return NextResponse.json({ + installed: false, + config: null, + message: "OpenCode CLI is not installed", + }); + } + + const config = await readConfig(); + + return NextResponse.json({ + installed: true, + config, + has9Router: has9RouterConfig(config), + configPath: getConfigPath(), + }); + } catch (error) { + console.log("Error checking opencode settings:", error); + return NextResponse.json({ error: "Failed to check opencode settings" }, { status: 500 }); + } +} + +// POST - Apply 9Router as openai-compatible provider +export async function POST(request) { + try { + const { baseUrl, apiKey, model } = await request.json(); + + if (!baseUrl || !model) { + return NextResponse.json({ error: "baseUrl and model are required" }, { status: 400 }); + } + + const configDir = getConfigDir(); + const configPath = getConfigPath(); + + await fs.mkdir(configDir, { recursive: true }); + + // Read existing config or start fresh + let config = {}; + try { + const existing = await fs.readFile(configPath, "utf-8"); + config = JSON.parse(existing); + } catch { /* No existing config */ } + + const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`; + const keyToUse = apiKey || "sk_9router"; + + // Merge 9router provider + if (!config.provider) config.provider = {}; + config.provider["9router"] = { + npm: "@ai-sdk/openai-compatible", + options: { + baseURL: normalizedBaseUrl, + apiKey: keyToUse, + }, + models: { + [model]: { name: model }, + }, + }; + + // Set as active model + config.model = `9router/${model}`; + + await fs.writeFile(configPath, JSON.stringify(config, null, 2)); + + return NextResponse.json({ + success: true, + message: "OpenCode settings applied successfully!", + configPath, + }); + } catch (error) { + console.log("Error updating opencode settings:", error); + return NextResponse.json({ error: "Failed to update opencode settings" }, { status: 500 }); + } +} + +// DELETE - Remove 9Router provider from config +export async function DELETE() { + try { + const configPath = getConfigPath(); + + let config = {}; + try { + const existing = await fs.readFile(configPath, "utf-8"); + config = JSON.parse(existing); + } catch (error) { + if (error.code === "ENOENT") { + return NextResponse.json({ success: true, message: "No config file to reset" }); + } + throw error; + } + + // Remove 9router provider + if (config.provider) delete config.provider["9router"]; + + // Reset model if it was pointing to 9router + if (config.model?.startsWith("9router/")) delete config.model; + + await fs.writeFile(configPath, JSON.stringify(config, null, 2)); + + return NextResponse.json({ + success: true, + message: "9Router settings removed from OpenCode", + }); + } catch (error) { + console.log("Error resetting opencode settings:", error); + return NextResponse.json({ error: "Failed to reset opencode settings" }, { status: 500 }); + } +} diff --git a/src/shared/constants/cliTools.js b/src/shared/constants/cliTools.js index da5bf28d..ae8a8d43 100644 --- a/src/shared/constants/cliTools.js +++ b/src/shared/constants/cliTools.js @@ -22,6 +22,14 @@ export const CLI_TOOLS = { { id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" }, ], }, + openclaw: { + id: "openclaw", + name: "Open Claw", + image: "/providers/openclaw.png", + color: "#FF6B35", + description: "Open Claw AI Assistant", + configType: "custom", + }, codex: { id: "codex", name: "OpenAI Codex CLI", @@ -30,6 +38,14 @@ export const CLI_TOOLS = { description: "OpenAI Codex CLI", configType: "custom", }, + opencode: { + id: "opencode", + name: "OpenCode", + image: "/providers/opencode.png", + color: "#E87040", + description: "OpenCode AI Terminal Assistant", + configType: "custom", + }, antigravity: { id: "antigravity", name: "Antigravity", @@ -55,14 +71,6 @@ export const CLI_TOOLS = { description: "Factory Droid AI Assistant", configType: "custom", }, - openclaw: { - id: "openclaw", - name: "Open Claw", - image: "/providers/openclaw.png", - color: "#FF6B35", - description: "Open Claw AI Assistant", - configType: "custom", - }, cursor: { id: "cursor", name: "Cursor", @@ -70,10 +78,10 @@ export const CLI_TOOLS = { color: "#000000", description: "Cursor AI Code Editor", configType: "guide", - requiresCloud: true, + requiresExternalUrl: true, notes: [ { type: "warning", text: "Requires Cursor Pro account to use this feature." }, - { type: "cloudCheck", text: "Cursor routes requests through its own server, so local endpoint is not supported. Please enable Cloud Endpoint in Settings." }, + { type: "cloudCheck", text: "Cursor routes requests through its own server, so local endpoint is not supported. Please enable Tunnel or Cloud Endpoint in Settings." }, ], guideSteps: [ { step: 1, title: "Open Settings", desc: "Go to Settings → Models" },