From 01482deeb9f4cdf6cbb0ba7bc3083368ebe81a70 Mon Sep 17 00:00:00 2001 From: Benjamin Shafii Date: Thu, 26 Feb 2026 23:05:23 -0800 Subject: [PATCH] feat(app): simplify artifacts and add worker/plugin quick actions Remove the in-app artifact editor in favor of read-only artifact actions, with reveal and optional Obsidian open for markdown files. Add workspace reveal from worker menus and plugin removal controls so users can manage workers and plugins directly in the UI. Made-with: Cursor --- packages/app/src/app/app.tsx | 2 + .../session/artifact-markdown-editor.tsx | 395 ------------------ .../components/session/artifacts-panel.tsx | 56 ++- .../session/workspace-session-list.tsx | 16 +- packages/app/src/app/context/extensions.ts | 80 ++++ packages/app/src/app/lib/tauri.ts | 12 + packages/app/src/app/pages/dashboard.tsx | 21 + packages/app/src/app/pages/extensions.tsx | 1 + packages/app/src/app/pages/plugins.tsx | 13 +- packages/app/src/app/pages/session.tsx | 182 ++++---- .../desktop/src-tauri/src/commands/misc.rs | 56 +++ packages/desktop/src-tauri/src/lib.rs | 6 +- 12 files changed, 337 insertions(+), 503 deletions(-) delete mode 100644 packages/app/src/app/components/session/artifact-markdown-editor.tsx diff --git a/packages/app/src/app/app.tsx b/packages/app/src/app/app.tsx index 9c793349f..ca20f8e0c 100644 --- a/packages/app/src/app/app.tsx +++ b/packages/app/src/app/app.tsx @@ -2076,6 +2076,7 @@ export default function App() { refreshHubSkills, refreshPlugins, addPlugin, + removePlugin, importLocalSkill, installSkillCreator, installHubSkill, @@ -5636,6 +5637,7 @@ export default function App() { isPluginInstalled: isPluginInstalledByName, suggestedPlugins: SUGGESTED_PLUGINS, addPlugin, + removePlugin, createSessionAndOpen, setPrompt, selectSession: selectSession, diff --git a/packages/app/src/app/components/session/artifact-markdown-editor.tsx b/packages/app/src/app/components/session/artifact-markdown-editor.tsx deleted file mode 100644 index 3f26426f2..000000000 --- a/packages/app/src/app/components/session/artifact-markdown-editor.tsx +++ /dev/null @@ -1,395 +0,0 @@ -import { Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"; -import { FileText, RefreshCcw, Save, X } from "lucide-solid"; - -import Button from "../button"; -import LiveMarkdownEditor from "../live-markdown-editor"; -import type { OpenworkServerClient, OpenworkWorkspaceFileContent, OpenworkWorkspaceFileWriteResult } from "../../lib/openwork-server"; -import { OpenworkServerError } from "../../lib/openwork-server"; - -export type ArtifactMarkdownEditorProps = { - open: boolean; - path: string | null; - workspaceId: string | null; - client: OpenworkServerClient | null; - onClose: () => void; - onToast?: (message: string) => void; -}; - -const isMarkdown = (value: string) => /\.(md|mdx|markdown)$/i.test(value); -const basename = (value: string) => value.split(/[/\\]/).filter(Boolean).pop() ?? value; - -export default function ArtifactMarkdownEditor(props: ArtifactMarkdownEditorProps) { - const [loading, setLoading] = createSignal(false); - const [saving, setSaving] = createSignal(false); - const [error, setError] = createSignal(null); - const [original, setOriginal] = createSignal(""); - const [draft, setDraft] = createSignal(""); - const [loadedPath, setLoadedPath] = createSignal(null); - const [resolvedPath, setResolvedPath] = createSignal(null); - const [baseUpdatedAt, setBaseUpdatedAt] = createSignal(null); - - const [confirmDiscardClose, setConfirmDiscardClose] = createSignal(false); - const [confirmOverwrite, setConfirmOverwrite] = createSignal(false); - - const [pendingPath, setPendingPath] = createSignal(null); - const [pendingReason, setPendingReason] = createSignal<"switch" | null>(null); - - const path = createMemo(() => props.path?.trim() ?? ""); - const title = createMemo(() => (path() ? basename(path()) : "Artifact")); - const dirty = createMemo(() => draft() !== original()); - const canWrite = createMemo(() => Boolean(props.client && props.workspaceId)); - const canSave = createMemo(() => dirty() && !saving() && canWrite()); - const writeDisabledReason = createMemo(() => { - if (canWrite()) return null; - return "Connect to an OpenWork server worker to edit files."; - }); - - const resetState = () => { - setLoading(false); - setSaving(false); - setError(null); - setOriginal(""); - setDraft(""); - setLoadedPath(null); - setResolvedPath(null); - setBaseUpdatedAt(null); - setConfirmDiscardClose(false); - setConfirmOverwrite(false); - setPendingPath(null); - setPendingReason(null); - }; - - const load = async (target: string) => { - const client = props.client; - const workspaceId = props.workspaceId; - - if (!client || !workspaceId) { - setError(writeDisabledReason()); - return; - } - if (!target) { - return; - } - if (!isMarkdown(target)) { - setError("Only markdown files are supported."); - return; - } - - setLoading(true); - setError(null); - setResolvedPath(null); - try { - let result: OpenworkWorkspaceFileContent; - let actualPath = target; - try { - result = (await client.readWorkspaceFile(workspaceId, target)) as OpenworkWorkspaceFileContent; - } catch (err) { - // Artifacts are frequently referenced as workspace-relative paths (e.g. `learned/foo.md`), - // but on disk they may live under the OpenWork outbox dir: `.opencode/openwork/outbox/`. - // If the first lookup fails, retry there. - const candidateOutbox = `.opencode/openwork/outbox/${target}`.replace(/\/+/g, "/"); - const shouldTryOutbox = - !(target.startsWith(".opencode/openwork/outbox/") || target.startsWith("./.opencode/openwork/outbox/")) && - err instanceof OpenworkServerError && - err.status === 404; - - if (!shouldTryOutbox) { - throw err; - } - - actualPath = candidateOutbox; - try { - result = (await client.readWorkspaceFile(workspaceId, actualPath)) as OpenworkWorkspaceFileContent; - } catch (second) { - if (second instanceof OpenworkServerError && second.status === 404) { - throw new OpenworkServerError(404, "file_not_found", "File not found (workspace root or outbox)."); - } - throw second; - } - } - - setOriginal(result.content ?? ""); - setDraft(result.content ?? ""); - setLoadedPath(target); - setResolvedPath(actualPath); - setBaseUpdatedAt(typeof result.updatedAt === "number" ? result.updatedAt : null); - } catch (err) { - const message = err instanceof Error ? err.message : "Failed to load file"; - setError(message); - setLoadedPath(target); - } finally { - setLoading(false); - } - }; - - const save = async (options?: { force?: boolean }) => { - const client = props.client; - const workspaceId = props.workspaceId; - const target = resolvedPath() ?? path(); - if (!client || !workspaceId || !target) { - props.onToast?.("Cannot save: OpenWork server not connected"); - return; - } - if (!isMarkdown(target)) { - props.onToast?.("Only markdown files are supported"); - return; - } - if (!dirty()) return; - - setSaving(true); - setError(null); - try { - const result = (await client.writeWorkspaceFile(workspaceId, { - path: target, - content: draft(), - baseUpdatedAt: baseUpdatedAt(), - force: options?.force ?? false, - })) as OpenworkWorkspaceFileWriteResult; - - setOriginal(draft()); - setBaseUpdatedAt(typeof result.updatedAt === "number" ? result.updatedAt : null); - - if (pendingPath() && pendingReason() === "switch") { - const next = pendingPath(); - setPendingPath(null); - setPendingReason(null); - if (next) void load(next); - } - } catch (err) { - if (err instanceof OpenworkServerError && err.status === 409) { - setConfirmOverwrite(true); - return; - } - const message = err instanceof Error ? err.message : "Failed to save"; - setError(message); - props.onToast?.(message); - } finally { - setSaving(false); - } - }; - - const requestClose = () => { - if (!dirty()) { - resetState(); - props.onClose(); - return; - } - setConfirmDiscardClose(true); - }; - - const requestReload = () => { - const target = path(); - if (!target) return; - if (!dirty()) { - void load(target); - return; - } - // Reload is destructive; reuse the close-discard banner semantics. - setError("Discard changes to reload from disk (close and reopen), or save first."); - }; - - createEffect(() => { - if (!props.open) { - resetState(); - return; - } - - const target = path(); - if (!target || loading() || pendingReason() === "switch") return; - - const active = loadedPath(); - if (!active) { - void load(target); - return; - } - if (target === active) return; - - if (!dirty()) { - void load(target); - return; - } - - setPendingPath(target); - setPendingReason("switch"); - }); - - createEffect(() => { - if (!props.open) return; - const onKeyDown = (event: KeyboardEvent) => { - if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") { - event.preventDefault(); - if (canSave()) void save(); - return; - } - if (event.key === "Escape") { - event.preventDefault(); - requestClose(); - } - }; - window.addEventListener("keydown", onKeyDown); - onCleanup(() => window.removeEventListener("keydown", onKeyDown)); - }); - - return ( - -
-
-
- -
-
-
{title()}
- - - Unsaved - - -
-
- {path()} -
-
-
- -
- - - -
-
- - - {(reason) => ( -
- {reason()} -
- )} -
- - - {(message) => ( -
- {message()} -
- )} -
- - -
-
File changed since load. Overwrite anyway?
-
- - -
-
-
- - -
-
Discard unsaved changes and close?
-
- - -
-
-
- - -
-
- Switch to {pendingPath()} -
-
- - - -
-
-
- -
- -
-
-
- ); -} diff --git a/packages/app/src/app/components/session/artifacts-panel.tsx b/packages/app/src/app/components/session/artifacts-panel.tsx index 1502a69c8..99409bf79 100644 --- a/packages/app/src/app/components/session/artifacts-panel.tsx +++ b/packages/app/src/app/components/session/artifacts-panel.tsx @@ -4,8 +4,9 @@ import { Paperclip } from "lucide-solid"; export type ArtifactsPanelProps = { files: string[]; workspaceRoot?: string; - onOpenMarkdown?: (path: string) => void; - onOpenImage?: (path: string) => void; + onRevealArtifact?: (path: string) => void; + onOpenInObsidian?: (path: string) => void; + obsidianAvailable?: boolean; maxPreview?: number; id?: string; }; @@ -88,8 +89,10 @@ export default function ArtifactsPanel(props: ArtifactsPanelProps) { return Math.max(0, total - shown); }); - const canOpenMarkdown = createMemo(() => typeof props.onOpenMarkdown === "function"); - const canOpenImage = createMemo(() => typeof props.onOpenImage === "function"); + const canRevealArtifact = createMemo(() => typeof props.onRevealArtifact === "function"); + const canOpenObsidian = createMemo( + () => Boolean(props.obsidianAvailable) && typeof props.onOpenInObsidian === "function", + ); const prettyPath = (file: string) => toWorkspaceRelative(file, props.workspaceRoot); return ( @@ -115,25 +118,10 @@ export default function ArtifactsPanel(props: ArtifactsPanelProps) { const dir = () => getDirname(display()); const md = () => artifact.kind === "markdown"; const img = () => artifact.kind === "image"; - const openable = () => (md() ? canOpenMarkdown() : img() ? canOpenImage() : false); - const tooltip = () => { - if (md()) return display(); - if (img() && !canOpenImage()) return `${display()} (image preview coming soon)`; - return display(); - }; return ( - +
+ + + + + + +
+ ); }} diff --git a/packages/app/src/app/components/session/workspace-session-list.tsx b/packages/app/src/app/components/session/workspace-session-list.tsx index d444a0b93..81be13ad5 100644 --- a/packages/app/src/app/components/session/workspace-session-list.tsx +++ b/packages/app/src/app/components/session/workspace-session-list.tsx @@ -4,7 +4,7 @@ import { ChevronDown, ChevronRight, HeartPulse, Loader2, MoreHorizontal, Plus } import type { OpenworkSoulStatus } from "../../lib/openwork-server"; import type { WorkspaceInfo } from "../../lib/tauri"; import type { WorkspaceSessionGroup } from "../../types"; -import { formatRelativeTime, getWorkspaceTaskLoadErrorDisplay } from "../../utils"; +import { formatRelativeTime, getWorkspaceTaskLoadErrorDisplay, isWindowsPlatform } from "../../utils"; type Props = { workspaceSessionGroups: WorkspaceSessionGroup[]; @@ -20,6 +20,7 @@ type Props = { onOpenRenameWorkspace: (workspaceId: string) => void; onShareWorkspace: (workspaceId: string) => void; onOpenSoul: (workspaceId: string) => void; + onRevealWorkspace: (workspaceId: string) => void; onTestWorkspaceConnection: (workspaceId: string) => Promise | boolean | void; onEditWorkspaceConnection: (workspaceId: string) => void; onForgetWorkspace: (workspaceId: string) => void; @@ -48,6 +49,7 @@ const workspaceKindLabel = (workspace: WorkspaceInfo) => : "Local"; export default function WorkspaceSessionList(props: Props) { + const revealLabel = isWindowsPlatform() ? "Reveal in Explorer" : "Reveal in Finder"; const [expandedWorkspaceIds, setExpandedWorkspaceIds] = createSignal>(new Set()); const [previewCountByWorkspaceId, setPreviewCountByWorkspaceId] = createSignal>({}); const [workspaceMenuId, setWorkspaceMenuId] = createSignal(null); @@ -287,6 +289,18 @@ export default function WorkspaceSessionList(props: Props) { > {soulEnabled() ? "Soul settings" : "Enable soul"} + + + + )} diff --git a/packages/app/src/app/pages/session.tsx b/packages/app/src/app/pages/session.tsx index d6a5e1a16..ae1111138 100644 --- a/packages/app/src/app/pages/session.tsx +++ b/packages/app/src/app/pages/session.tsx @@ -20,7 +20,13 @@ import type { WorkspaceSessionGroup, } from "../types"; -import type { EngineInfo, OpenworkServerInfo, WorkspaceInfo } from "../lib/tauri"; +import { + obsidianIsAvailable, + openInObsidian, + type EngineInfo, + type OpenworkServerInfo, + type WorkspaceInfo, +} from "../lib/tauri"; import { Box, @@ -69,6 +75,7 @@ import { DEFAULT_OPENWORK_PUBLISHER_BASE_URL, publishOpenworkBundleJson } from " import { join } from "@tauri-apps/api/path"; import { isTauriRuntime, + isWindowsPlatform, normalizeDirectoryPath, parseTemplateFrontmatter, } from "../utils"; @@ -85,7 +92,6 @@ import FlyoutItem from "../components/flyout-item"; import QuestionModal from "../components/question-modal"; import ArtifactsPanel from "../components/session/artifacts-panel"; import InboxPanel from "../components/session/inbox-panel"; -import ArtifactMarkdownEditor from "../components/session/artifact-markdown-editor"; export type SessionViewProps = { selectedSessionId: string | null; @@ -318,8 +324,7 @@ export default function SessionView(props: SessionViewProps) { const [messageWindowSessionId, setMessageWindowSessionId] = createSignal(null); const [messageWindowExpanded, setMessageWindowExpanded] = createSignal(false); - const [markdownEditorOpen, setMarkdownEditorOpen] = createSignal(false); - const [markdownEditorPath, setMarkdownEditorPath] = createSignal(null); + const [obsidianAvailable, setObsidianAvailable] = createSignal(false); // When a session is selected (i.e. we are in SessionView), the right sidebar is // navigation-only. Avoid showing any tab as "selected" to reduce confusion. @@ -327,6 +332,25 @@ export default function SessionView(props: SessionViewProps) { let commandPaletteInputEl: HTMLInputElement | undefined; const commandPaletteOptionRefs: HTMLButtonElement[] = []; + createEffect(() => { + if (!isTauriRuntime()) { + setObsidianAvailable(false); + return; + } + let cancelled = false; + void (async () => { + try { + const available = await obsidianIsAvailable(); + if (!cancelled) setObsidianAvailable(available); + } catch { + if (!cancelled) setObsidianAvailable(false); + } + })(); + onCleanup(() => { + cancelled = true; + }); + }); + const agentLabel = createMemo(() => props.selectedSessionAgent ?? "Default agent"); const workspaceLabel = (workspace: WorkspaceInfo) => workspace.displayName?.trim() || @@ -730,86 +754,91 @@ export default function SessionView(props: SessionViewProps) { return out; }); - const normalizeSidebarPath = (value: string) => String(value ?? "").trim().replace(/[\\/]+/g, "/"); + const resolveArtifactLocalPath = async (file: string) => { + const trimmed = file.trim(); + if (!trimmed) return null; + const root = props.activeWorkspaceRoot.trim(); + if (!isAbsolutePath(trimmed) && !root) return null; + return !isAbsolutePath(trimmed) && root ? await join(root, trimmed) : trimmed; + }; - const toWorkspaceRelativeForApi = (file: string) => { - const normalized = normalizeSidebarPath(file).replace(/^file:\/\//i, ""); - if (!normalized) return ""; - - const root = normalizeSidebarPath(props.activeWorkspaceRoot).replace(/\/+$/, ""); - const rootKey = root.toLowerCase(); - const fileKey = normalized.toLowerCase(); - - if (root && fileKey.startsWith(`${rootKey}/`)) { - return normalized.slice(root.length + 1); + const revealArtifact = async (file: string) => { + if (props.activeWorkspaceDisplay.workspaceType === "remote") { + setToastMessage("Reveal is unavailable for remote workers."); + return; } - if (root && fileKey === rootKey) { - return ""; + if (!isTauriRuntime()) { + setToastMessage("Reveal is available in the desktop app."); + return; } - - if (root) { - const rootSegments = root.split("/").filter(Boolean); - const workspaceFolderName = rootSegments[rootSegments.length - 1]?.toLowerCase(); - if (workspaceFolderName) { - const workspaceMarker = `workspaces/${workspaceFolderName}/`; - const markerIndex = fileKey.indexOf(workspaceMarker); - if (markerIndex >= 0) return normalized.slice(markerIndex + workspaceMarker.length); - if (fileKey.endsWith(`workspaces/${workspaceFolderName}`)) return ""; + try { + const target = await resolveArtifactLocalPath(file); + if (!target) { + setToastMessage("Pick a worker to reveal files."); + return; } + const { openPath, revealItemInDir } = await import("@tauri-apps/plugin-opener"); + if (isWindowsPlatform()) { + await openPath(target); + } else { + await revealItemInDir(target); + } + } catch (error) { + const message = error instanceof Error ? error.message : "Unable to reveal file"; + setToastMessage(message); } - - let relative = normalized.replace(/^\.\/+/, ""); - if (!relative) return ""; - - // Tool output paths sometimes carry git-style prefixes (a/ or b/). - if (/^[ab]\/.+\.(md|mdx|markdown)$/i.test(relative)) { - relative = relative.slice(2); - } - - // Some tool outputs include a leading "workspace/" prefix. - if (/^workspace\//i.test(relative)) { - relative = relative.replace(/^workspace\//i, ""); - } - - // Other surfaces include an absolute-style "/workspace/" prefix. - if (/^\/+workspace\//i.test(relative)) { - relative = relative.replace(/^\/+workspace\//i, ""); - } - - if (relative.startsWith("/") || relative.startsWith("~") || /^[a-zA-Z]:\//.test(relative)) return ""; - if (relative.split("/").some((part) => part === "." || part === "..")) return ""; - - if (/com\.[^/]+\.(openwork|opencode)/i.test(relative)) return ""; - - return relative; }; - const openMarkdownEditor = (file: string) => { - if (!props.openworkServerClient) { - setToastMessage("Cannot open file: not connected to OpenWork server."); + const openArtifactInObsidian = async (file: string) => { + if (!/\.(md|mdx|markdown)$/i.test(file)) return; + if (!obsidianAvailable()) { + setToastMessage("Obsidian is not available on this system."); return; } - if (!props.openworkServerWorkspaceId) { - setToastMessage("Cannot open file: no workspace selected."); + if (props.activeWorkspaceDisplay.workspaceType === "remote") { + setToastMessage("Open in Obsidian is unavailable for remote workers."); return; } - - const relative = toWorkspaceRelativeForApi(file); - if (!relative) { - setToastMessage(`Cannot open file: path "${file}" is not within the workspace.`); + if (!isTauriRuntime()) { + setToastMessage("Open in Obsidian is available in the desktop app."); return; } - if (!/\.(md|mdx|markdown)$/i.test(relative)) { - setToastMessage("Only markdown files can be edited here right now."); - return; + try { + const target = await resolveArtifactLocalPath(file); + if (!target) { + setToastMessage("Pick a worker to open files."); + return; + } + await openInObsidian(target); + } catch (error) { + const message = error instanceof Error ? error.message : "Unable to open file in Obsidian"; + setToastMessage(message); } - setMarkdownEditorPath(relative); - setMarkdownEditorOpen(true); }; - const closeMarkdownEditor = () => { - setMarkdownEditorOpen(false); - setMarkdownEditorPath(null); + const revealWorkspaceInFinder = async (workspaceId: string) => { + const workspace = props.workspaces.find((entry) => entry.id === workspaceId) ?? null; + if (!workspace || workspace.workspaceType !== "local") return; + const target = workspace.path?.trim() ?? ""; + if (!target) { + setToastMessage("Workspace path is unavailable."); + return; + } + if (!isTauriRuntime()) { + setToastMessage("Reveal is available in the desktop app."); + return; + } + try { + const { openPath, revealItemInDir } = await import("@tauri-apps/plugin-opener"); + if (isWindowsPlatform()) { + await openPath(target); + } else { + await revealItemInDir(target); + } + } catch (error) { + const message = error instanceof Error ? error.message : "Unable to reveal workspace"; + setToastMessage(message); + } }; const todoLabel = createMemo(() => { const total = todoCount(); @@ -2687,6 +2716,7 @@ export default function SessionView(props: SessionViewProps) { onOpenRenameWorkspace={props.openRenameWorkspace} onShareWorkspace={(workspaceId) => setShareWorkspaceId(workspaceId)} onOpenSoul={openSoul} + onRevealWorkspace={revealWorkspaceInFinder} onTestWorkspaceConnection={props.testWorkspaceConnection} onEditWorkspaceConnection={props.editWorkspaceConnection} onForgetWorkspace={props.forgetWorkspace} @@ -3049,18 +3079,6 @@ export default function SessionView(props: SessionViewProps) { - - - 0}> @@ -3284,7 +3302,9 @@ export default function SessionView(props: SessionViewProps) { id="sidebar-artifacts" files={touchedFiles()} workspaceRoot={props.activeWorkspaceRoot} - onOpenMarkdown={openMarkdownEditor} + onRevealArtifact={revealArtifact} + onOpenInObsidian={openArtifactInObsidian} + obsidianAvailable={obsidianAvailable()} /> diff --git a/packages/desktop/src-tauri/src/commands/misc.rs b/packages/desktop/src-tauri/src/commands/misc.rs index bc7891d3a..6e6e3f69d 100644 --- a/packages/desktop/src-tauri/src/commands/misc.rs +++ b/packages/desktop/src-tauri/src/commands/misc.rs @@ -271,6 +271,62 @@ pub fn app_build_info(app: AppHandle) -> AppBuildInfo { } } +#[tauri::command] +pub fn obsidian_is_available() -> bool { + #[cfg(target_os = "macos")] + { + let mut candidates = vec![PathBuf::from("/Applications/Obsidian.app")]; + if let Some(home) = home_dir() { + candidates.push(home.join("Applications").join("Obsidian.app")); + } + return candidates.into_iter().any(|path| path.exists()); + } + + #[cfg(not(target_os = "macos"))] + { + false + } +} + +#[tauri::command] +pub fn open_in_obsidian(file_path: String) -> Result<(), String> { + let trimmed = file_path.trim(); + if trimmed.is_empty() { + return Err("file_path is required".to_string()); + } + + let path = PathBuf::from(trimmed); + if !path.is_absolute() { + return Err("file_path must be an absolute path".to_string()); + } + if !path.exists() { + return Err(format!("File does not exist: {}", path.display())); + } + + #[cfg(target_os = "macos")] + { + if !obsidian_is_available() { + return Err("Obsidian is not installed.".to_string()); + } + + let status = std::process::Command::new("open") + .arg("-a") + .arg("Obsidian") + .arg(&path) + .status() + .map_err(|e| format!("Failed to launch Obsidian: {e}"))?; + if status.success() { + return Ok(()); + } + return Err(format!("Failed to launch Obsidian (exit status: {status}).")); + } + + #[cfg(not(target_os = "macos"))] + { + Err("Open in Obsidian is currently supported on macOS only.".to_string()) + } +} + #[tauri::command] pub fn opencode_db_migrate( app: AppHandle, diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index 97b36c237..10e5aabc5 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -22,8 +22,8 @@ use commands::command_files::{ use commands::config::{read_opencode_config, write_opencode_config}; use commands::engine::{engine_doctor, engine_info, engine_install, engine_start, engine_stop}; use commands::misc::{ - app_build_info, opencode_db_migrate, opencode_mcp_auth, reset_opencode_cache, - reset_openwork_state, + app_build_info, obsidian_is_available, open_in_obsidian, opencode_db_migrate, opencode_mcp_auth, + reset_opencode_cache, reset_openwork_state, }; use commands::opencode_router::{ opencodeRouter_config_set, opencodeRouter_info, opencodeRouter_start, opencodeRouter_status, @@ -134,6 +134,8 @@ pub fn run() { write_opencode_config, updater_environment, app_build_info, + obsidian_is_available, + open_in_obsidian, reset_openwork_state, reset_opencode_cache, opencode_db_migrate,