- {language.t("dialog.project.edit.color")}
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/packages/app/src/components/project-settings-extensions.tsx b/packages/app/src/components/project-settings-extensions.tsx
new file mode 100644
index 0000000000..9505c71d6a
--- /dev/null
+++ b/packages/app/src/components/project-settings-extensions.tsx
@@ -0,0 +1,240 @@
+import { Icon } from "@opencode-ai/ui/icon"
+import { Switch } from "@opencode-ai/ui/v2/switch-v2"
+import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
+import { type Component, For, Show, createMemo, createResource, createSignal } from "solid-js"
+import { useLanguage } from "@/context/language"
+import { useMcpToggle } from "@/context/mcp"
+import { useSDK } from "@/context/sdk"
+import { useServerSDK } from "@/context/server-sdk"
+import { useServerSync } from "@/context/server-sync"
+import { useSync } from "@/context/sync"
+import { ExternalLink } from "./external-link"
+
+type SkillItem = {
+ name: string
+ location: string
+}
+
+const pluginName = (item: string | [string, Record
]) => (typeof item === "string" ? item : item[0])
+const skillKey = (item: SkillItem) => `${item.name}\n${item.location}`
+
+const ExtensionCard: Component<{ children: unknown }> = (props) => (
+ {props.children as any}
+)
+
+const ExtensionRow: Component<{
+ icon: "mcp" | "cube" | "post-skill" | "code"
+ name: string
+ status?: string
+ children?: unknown
+}> = (props) => (
+
+
+
+ {props.name}
+
+
+ {(status) => (
+
+
+ {status()}
+
+ )}
+
+ {props.children as any}
+
+)
+
+const SharedSection: Component<{
+ count: number
+ children: unknown
+}> = (props) => {
+ const language = useLanguage()
+ const [open, setOpen] = createSignal(false)
+ return (
+ 0}>
+
+
+
+ {props.children}
+
+
+
+ )
+}
+
+export const ProjectSettingsExtensions: Component = () => {
+ const language = useLanguage()
+ const serverSDK = useServerSDK()
+ const directorySDK = useSDK()
+ const serverSync = useServerSync()
+ const sync = useSync()
+ const toggleMcp = useMcpToggle()
+
+ const [serverMcp] = createResource(
+ serverSDK,
+ (sdk) =>
+ sdk.api.mcp
+ .list()
+ .then((result) => Object.fromEntries(result.data.map((server) => [server.name, server.status])))
+ .catch(() => ({})),
+ { initialValue: {} },
+ )
+ const globalMcpNames = createMemo(() =>
+ [...new Set([...Object.keys(serverSync().data.config.mcp ?? {}), ...Object.keys(serverMcp.latest)])].sort(),
+ )
+ const projectMcpNames = createMemo(() => {
+ const shared = new Set(globalMcpNames())
+ const configured = Object.keys(sync().data.config.mcp ?? {}).filter((name) => !shared.has(name))
+ if (configured.length > 0) return configured.sort()
+ return Object.keys(sync().data.mcp ?? {})
+ .filter((name) => !shared.has(name))
+ .sort()
+ })
+ const mcpEnabled = (name: string) => sync().data.mcp?.[name]?.status === "connected"
+
+ const globalPlugins = createMemo(() => (serverSync().data.config.plugin ?? []).map(pluginName))
+ const projectPlugins = createMemo(() => {
+ const shared = new Set(globalPlugins())
+ return (sync().data.config.plugin ?? []).map(pluginName).filter((name) => !shared.has(name))
+ })
+
+ const [serverSkills] = createResource(
+ serverSDK,
+ (sdk): Promise =>
+ sdk.api.skill
+ .list()
+ .then((result) => result.data.map((item) => ({ name: item.name, location: item.location }))),
+ { initialValue: [] },
+ )
+ const [directorySkills] = createResource(
+ directorySDK,
+ (sdk): Promise =>
+ sdk.api.skill
+ .list({ location: { directory: sdk.directory } })
+ .then((result) => result.data.map((item) => ({ name: item.name, location: item.location }))),
+ { initialValue: [] },
+ )
+ const projectSkills = createMemo(() => {
+ const shared = new Set(serverSkills.latest.map(skillKey))
+ return directorySkills.latest.filter((item) => !shared.has(skillKey(item)))
+ })
+
+ const mcpRows = (items: string[]) => (
+
+ {(name) => (
+
+ {
+ if (toggleMcp.isPending) return
+ toggleMcp.mutate(name)
+ }}
+ >
+ {name}
+
+
+ )}
+
+ )
+
+ const pluginRows = (items: string[]) => {(name) => }
+
+ const skillRows = (items: SkillItem[]) => (
+ {(item) => }
+ )
+
+ return (
+
+
+
+
+
+ {language.t("settings.extensions.tab.mcps")}
+ {language.t("status.popover.tab.plugins")}
+ {language.t("settings.extensions.tab.skills")}
+ {language.t("project.settings.extensions.tab.lsps")}
+
+
+
+
+
+ 0}>
+ {mcpRows(projectMcpNames())}
+
+ {mcpRows(globalMcpNames())}
+
+
+
+
+
+
+ 0}>
+ {pluginRows(projectPlugins())}
+
+ {pluginRows(globalPlugins())}
+
+
+
+
+
+
+ 0}>
+ {skillRows(projectSkills())}
+
+ {skillRows(serverSkills.latest)}
+
+
+
+
+
+
+ 0}>
+
+
+ {(item) => (
+
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/app/src/components/server/server-row-menu.tsx b/packages/app/src/components/server/server-row-menu.tsx
index 619c8a50be..aedcae8537 100644
--- a/packages/app/src/components/server/server-row-menu.tsx
+++ b/packages/app/src/components/server/server-row-menu.tsx
@@ -9,7 +9,7 @@ import { ServerConnection } from "@/context/server"
export const ServerRowMenu: Component<{
server: ServerConnection.Any
domain: ServerActionsController
- onEdit: (server: ServerConnection.Http) => void
+ onEdit: (server: ServerConnection.Any) => void
open?: boolean
onOpenChange?: (open: boolean) => void
}> = (props) => {
@@ -49,7 +49,7 @@ export const ServerRowMenuView: Component<{
canDefault: boolean
isDefault: boolean
canRemove: boolean
- onEdit: (server: ServerConnection.Http) => void
+ onEdit: (server: ServerConnection.Any) => void
onSetDefault: () => void
onRemoveDefault: () => void
onRemove: () => void
@@ -57,7 +57,6 @@ export const ServerRowMenuView: Component<{
onOpenChange?: (open: boolean) => void
}> = (props) => {
const builtin = () => ServerConnection.builtin(props.server)
- const httpServer = () => (props.server.type === "http" ? props.server : undefined)
return (
{props.labels.server}
- {
- const server = httpServer()
- if (server) props.onEdit(server)
- }}
- >
- {props.labels.edit}
-
+ props.onEdit(props.server)}>{props.labels.edit}
{props.labels.default}
diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx
index ce74be4586..57d71c0d6d 100644
--- a/packages/app/src/components/settings-keybinds.tsx
+++ b/packages/app/src/components/settings-keybinds.tsx
@@ -451,7 +451,10 @@ function SettingsKeybindsV2View(props: {
<>
>
)
diff --git a/packages/app/src/components/settings-v2/extensions.tsx b/packages/app/src/components/settings-v2/extensions.tsx
index baa0448442..bbe4e26453 100644
--- a/packages/app/src/components/settings-v2/extensions.tsx
+++ b/packages/app/src/components/settings-v2/extensions.tsx
@@ -50,11 +50,8 @@ export const SettingsExtensionsV2: Component = () => {
})
const [skills] = createResource(
- () => serverSdk(),
- async (sdk) => {
- const result = await sdk.client.v2.skill.list(undefined, { throwOnError: true })
- return result.data.data
- },
+ serverSdk,
+ (sdk) => sdk.api.skill.list().then((result) => result.data),
{ initialValue: [] },
)
@@ -64,7 +61,7 @@ export const SettingsExtensionsV2: Component = () => {
@@ -73,9 +70,9 @@ export const SettingsExtensionsV2: Component = () => {
- {language.t("status.popover.tab.mcp")}
+ {language.t("settings.extensions.tab.mcps")}
{language.t("status.popover.tab.plugins")}
- {language.t("settings.permissions.tool.skill.title")}
+ {language.t("settings.extensions.tab.skills")}
diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx
index 66b8ccd69c..2c51252857 100644
--- a/packages/app/src/components/settings-v2/general.tsx
+++ b/packages/app/src/components/settings-v2/general.tsx
@@ -279,8 +279,6 @@ export const SettingsGeneralV2: Component<{
const updater = useUpdaterAction()
const permissionScope = createPermissionScopeController(() => props.sessionID)
const shell = createShellSettingsController()
- const appearance = createAppearanceSettingsController()
- const sounds = createSoundSettingsController()
const desktop = createMemo(() => platform.platform === "desktop")
const [pinchZoom, { mutate: setPinchZoom }] = createResource(
@@ -298,6 +296,7 @@ export const SettingsGeneralV2: Component<{
const GeneralSection = () => (
+
{language.t("settings.general.section.general")}
@@ -510,18 +509,19 @@ export const SettingsGeneralV2: Component<{
return (
<>
-
-
-
-
-
-
diff --git a/packages/app/src/components/settings-v2/models.tsx b/packages/app/src/components/settings-v2/models.tsx
index deb5b97c12..738d282d72 100644
--- a/packages/app/src/components/settings-v2/models.tsx
+++ b/packages/app/src/components/settings-v2/models.tsx
@@ -55,7 +55,10 @@ export const SettingsModelsV2: Component = () => {
<>