diff --git a/extensions/cloudflare-warp/.gitignore b/extensions/cloudflare-warp/.gitignore new file mode 100644 index 00000000..eb3ae205 --- /dev/null +++ b/extensions/cloudflare-warp/.gitignore @@ -0,0 +1,2 @@ +node_modules +vicinae-env.d.ts diff --git a/extensions/cloudflare-warp/.oxfmtrc.json b/extensions/cloudflare-warp/.oxfmtrc.json new file mode 100644 index 00000000..55c15df3 --- /dev/null +++ b/extensions/cloudflare-warp/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": [] +} diff --git a/extensions/cloudflare-warp/.oxlintrc.json b/extensions/cloudflare-warp/.oxlintrc.json new file mode 100644 index 00000000..b99210f2 --- /dev/null +++ b/extensions/cloudflare-warp/.oxlintrc.json @@ -0,0 +1,40 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": null, + "categories": {}, + "rules": {}, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {}, + "attributes": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [], + "version": null, + "componentWrapperFunctions": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "vitest": { + "typecheck": false + } + }, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [] +} diff --git a/extensions/cloudflare-warp/README.md b/extensions/cloudflare-warp/README.md new file mode 100644 index 00000000..e2cb5930 --- /dev/null +++ b/extensions/cloudflare-warp/README.md @@ -0,0 +1,16 @@ +# Vicinae Extension + +Congratulations on generating your new Vicinae extension! + +You can install the required dependencies and run your extension in development mode like so: + +```bash +npm install +npm run dev +``` + +If you want to build the production bundle, simply run: + +```bash +npm run build +``` diff --git a/extensions/cloudflare-warp/assets/extension_icon.png b/extensions/cloudflare-warp/assets/extension_icon.png new file mode 100644 index 00000000..e00e1ed2 Binary files /dev/null and b/extensions/cloudflare-warp/assets/extension_icon.png differ diff --git a/extensions/cloudflare-warp/package.json b/extensions/cloudflare-warp/package.json new file mode 100644 index 00000000..20ab7d67 --- /dev/null +++ b/extensions/cloudflare-warp/package.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://raw.githubusercontent.com/vicinaehq/vicinae/refs/heads/main/extra/schemas/extension.json", + "name": "cloudflare-warp", + "title": "Cloudflare Warp", + "description": "Manage your Cloudflare Warp connection — toggle, check status, and stay in control from your launcher.", + "categories": ["System"], + "license": "MIT", + "author": "anuraglol", + "contributors": [], + "pastContributors": [], + "preferences": [], + "icon": "extension_icon.png", + "scripts": { + "build": "vici build", + "dev": "vici develop", + "format": "oxfmt", + "lint": "vici lint" + }, + "dependencies": { + "@vicinae/api": "^0.20.7" + }, + "devDependencies": { + "oxfmt": "^0.41.0", + "oxlint": "^1.56.0", + "typescript": "^5.9.2" + }, + "commands": [ + { + "name": "cloudflare-warp-tool", + "title": "Cloudflare Warp", + "subtitle": "Cloudflare", + "description": "Toggle your Warp connection and check its current status.", + "mode": "view" + } + ] +} diff --git a/extensions/cloudflare-warp/src/cloudflare-warp-tool.tsx b/extensions/cloudflare-warp/src/cloudflare-warp-tool.tsx new file mode 100644 index 00000000..f0bbc548 --- /dev/null +++ b/extensions/cloudflare-warp/src/cloudflare-warp-tool.tsx @@ -0,0 +1,113 @@ +import { Action, ActionPanel, Icon, List, showToast, Toast } from "@vicinae/api"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { useEffect, useState } from "react"; + +const execAsync = promisify(exec); + +async function checkWarpCli(): Promise { + try { + await execAsync("which warp-cli"); + return true; + } catch { + return false; + } +} + +async function getStatus(): Promise<{ connected: boolean }> { + const { stdout } = await execAsync("warp-cli status"); + const connected = !stdout.includes("Disconnected"); + return { connected }; +} + +export default function WarpHelper() { + const [cliAvailable, setCliAvailable] = useState(null); + + useEffect(() => { + checkWarpCli().then(setCliAvailable); + }, []); + + if (cliAvailable === null) { + return ; + } + + if (!cliAvailable) { + return ( + + + + ); + } + + return ( + + + + { + const toast = await showToast(Toast.Style.Animated, "Warp", "Checking status..."); + try { + const { connected } = await getStatus(); + if (connected) { + await execAsync("warp-cli disconnect"); + toast.style = Toast.Style.Success; + toast.title = "Disconnected"; + toast.message = "Warp disconnected"; + } else { + await execAsync("warp-cli connect"); + toast.style = Toast.Style.Success; + toast.title = "Connected"; + toast.message = "Warp connected"; + } + } catch (e) { + toast.style = Toast.Style.Failure; + toast.title = "Error"; + toast.message = String(e); + } + }} + /> + + } + /> + + { + const toast = await showToast(Toast.Style.Animated, "Warp", "Fetching status..."); + try { + const { connected } = await getStatus(); + toast.style = Toast.Style.Success; + toast.title = connected ? "Connected" : "Disconnected"; + toast.message = connected ? "Warp is active" : "Warp is inactive"; + } catch (e) { + toast.style = Toast.Style.Failure; + toast.title = "Error"; + toast.message = String(e); + } + }} + /> + + } + /> + + + ); +} diff --git a/extensions/cloudflare-warp/tsconfig.json b/extensions/cloudflare-warp/tsconfig.json new file mode 100644 index 00000000..38fb14b2 --- /dev/null +++ b/extensions/cloudflare-warp/tsconfig.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node 16", + "include": ["src/**/*"], + "compilerOptions": { + //"lib": ["es2020"], + "module": "commonjs", + "target": "es2020", + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "jsx": "react-jsx" + } +}