From 2a20523e166300c407c03393d61b13caaa64ea7e Mon Sep 17 00:00:00 2001 From: celestial-vault <58194240+celestial-vault@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:37:47 -0600 Subject: [PATCH] View remote rules and workflows in the editor (#7702) * allow the user to view remote rules and workflows in the editor by creating a temp file * add await --- src/core/controller/file/openFile.ts | 52 ++++++++++++++++++- src/shared/remote-config/constants.ts | 5 ++ src/shared/remote-config/index.ts | 1 - .../src/components/cline-rules/RuleRow.tsx | 20 ++++--- webview-ui/src/components/ui/button.tsx | 2 +- 5 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 src/shared/remote-config/constants.ts delete mode 100644 src/shared/remote-config/index.ts diff --git a/src/core/controller/file/openFile.ts b/src/core/controller/file/openFile.ts index e86cfab5ec..d028df571c 100644 --- a/src/core/controller/file/openFile.ts +++ b/src/core/controller/file/openFile.ts @@ -1,16 +1,64 @@ +import { StateManager } from "@core/storage/StateManager" import { openFile as openFileIntegration } from "@integrations/misc/open-file" import { Empty, StringRequest } from "@shared/proto/cline/common" +import { REMOTE_URI_SCHEME } from "@shared/remote-config/constants" +import { writeFile } from "@utils/fs" +import * as os from "os" +import * as path from "path" import { Controller } from ".." /** * Opens a file in the editor * @param controller The controller instance - * @param request The request message containing the file path in the 'value' field + * @param request The request message containing the file path in the 'value' field. + * Supports special URI format for remote rules/workflows: + * - remote://rule/{ruleName} + * - remote://workflow/{workflowName} * @returns Empty response */ export async function openFile(_controller: Controller, request: StringRequest): Promise { if (request.value) { - openFileIntegration(request.value) + // Check for remote:// prefix for remote rules/workflows + if (request.value.startsWith(REMOTE_URI_SCHEME)) { + await openRemoteFile(request.value) + } else { + await openFileIntegration(request.value) + } } return Empty.create() } + +/** + * Opens a remote rule or workflow file by creating a temp file with its contents + * @param uri The remote URI in format: remote://rule/{name} or remote://workflow/{name} + */ +async function openRemoteFile(uri: string): Promise { + // Parse: remote://rule/{name} or remote://workflow/{name} + const match = uri.match(/^remote:\/\/(rule|workflow)\/(.+)$/) + if (!match) { + throw new Error(`Invalid remote file URI: ${uri}`) + } + + const [, type, name] = match + const remoteConfig = StateManager.get().getRemoteConfigSettings() + + // Look up content based on type + const items = type === "rule" ? remoteConfig.remoteGlobalRules : remoteConfig.remoteGlobalWorkflows + const item = items?.find((r) => r.name === name) + + if (!item?.contents) { + throw new Error(`Remote ${type} not found: ${name}`) + } + + // Create temp file with read-only header comment + const typeLabel = type === "rule" ? "rule" : "workflow" + const header = `# ⚠️ READ-ONLY: This ${typeLabel} is managed by your organization.\n# Changes made here will not be saved.\n\n` + const content = header + item.contents + + // Sanitize the name for use in filename (replace invalid characters) + const sanitizedName = name.replace(/[<>:"/\\|?*]/g, "_") + const tempPath = path.join(os.tmpdir(), `cline-remote-${type}-${sanitizedName}.md`) + + await writeFile(tempPath, content) + await openFileIntegration(tempPath) +} diff --git a/src/shared/remote-config/constants.ts b/src/shared/remote-config/constants.ts new file mode 100644 index 0000000000..09df75e120 --- /dev/null +++ b/src/shared/remote-config/constants.ts @@ -0,0 +1,5 @@ +/** + * URI scheme for opening remote rules/workflows in the editor. + * Used to construct URIs like: remote://rule/{name} or remote://workflow/{name} + */ +export const REMOTE_URI_SCHEME = "remote://" diff --git a/src/shared/remote-config/index.ts b/src/shared/remote-config/index.ts deleted file mode 100644 index 06861aae91..0000000000 --- a/src/shared/remote-config/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./schema" diff --git a/webview-ui/src/components/cline-rules/RuleRow.tsx b/webview-ui/src/components/cline-rules/RuleRow.tsx index fe26d77cd8..a52f1c7347 100644 --- a/webview-ui/src/components/cline-rules/RuleRow.tsx +++ b/webview-ui/src/components/cline-rules/RuleRow.tsx @@ -1,10 +1,10 @@ import { StringRequest } from "@shared/proto/cline/common" import { RuleFileRequest } from "@shared/proto/index.cline" -import { InfoIcon, PenIcon, Trash2Icon } from "lucide-react" +import { REMOTE_URI_SCHEME } from "@shared/remote-config/constants" +import { EyeIcon, InfoIcon, PenIcon, Trash2Icon } from "lucide-react" import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" -import { cn } from "@/lib/utils" import { FileServiceClient } from "@/services/grpc-client" const RuleRow: React.FC<{ @@ -81,7 +81,9 @@ const RuleRow: React.FC<{ } const handleEditClick = () => { - FileServiceClient.openFile(StringRequest.create({ value: rulePath })).catch((err) => + // For remote rules, use the special remote:// URI format + const filePath = isRemote ? `${REMOTE_URI_SCHEME}${ruleType === "workflow" ? "workflow" : "rule"}/${rulePath}` : rulePath + FileServiceClient.openFile(StringRequest.create({ value: filePath })).catch((err) => console.error("Failed to open file:", err), ) } @@ -98,10 +100,7 @@ const RuleRow: React.FC<{ return (
-
+
{getRuleTypeIcon() && {getRuleTypeIcon()}} {finalDisplayName} @@ -128,13 +127,12 @@ const RuleRow: React.FC<{ title={isDisabled ? "This rule is required and cannot be disabled" : undefined} />