(hostEl = el)}
- />
- );
-}
diff --git a/apps/app/src/app/components/mobile-sidebar-drawer.tsx b/apps/app/src/app/components/mobile-sidebar-drawer.tsx
deleted file mode 100644
index 74d4b5c77..000000000
--- a/apps/app/src/app/components/mobile-sidebar-drawer.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Show, createEffect, onCleanup } from "solid-js";
-import type { JSX } from "solid-js";
-
-type MobileSidebarDrawerProps = {
- open: boolean;
- onClose: () => void;
- children: JSX.Element;
-};
-
-export default function MobileSidebarDrawer(props: MobileSidebarDrawerProps) {
- createEffect(() => {
- if (!props.open || typeof window === "undefined" || typeof document === "undefined") return;
-
- const previousOverflow = document.body.style.overflow;
- document.body.style.overflow = "hidden";
-
- const handleKeyDown = (event: KeyboardEvent) => {
- if (event.key === "Escape") {
- props.onClose();
- }
- };
-
- window.addEventListener("keydown", handleKeyDown);
-
- onCleanup(() => {
- window.removeEventListener("keydown", handleKeyDown);
- document.body.style.overflow = previousOverflow;
- });
- });
-
- return (
-
-
-
-
- {props.children}
-
-
-
- );
-}
diff --git a/apps/app/src/app/components/openwork-logo.tsx b/apps/app/src/app/components/openwork-logo.tsx
deleted file mode 100644
index 4b35c0b9b..000000000
--- a/apps/app/src/app/components/openwork-logo.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { JSX } from "solid-js";
-
-type Props = {
- size?: number;
- class?: string;
-};
-
-export default function OpenWorkLogo(props: Props): JSX.Element {
- const size = props.size ?? 24;
- return (
-
- );
-}
diff --git a/apps/app/src/app/components/session/minimap.tsx b/apps/app/src/app/components/session/minimap.tsx
deleted file mode 100644
index b39db53e8..000000000
--- a/apps/app/src/app/components/session/minimap.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { For, createEffect, createSignal, onCleanup, onMount } from "solid-js";
-import type { MessageWithParts } from "../../types";
-
-export type MinimapProps = {
- containerRef: () => HTMLDivElement | undefined;
- messages: MessageWithParts[];
-};
-
-export default function Minimap(props: MinimapProps) {
- const [lines, setLines] = createSignal<{ id: string; role: "user" | "assistant"; top: number; height: number }[]>([]);
- const [activeId, setActiveId] = createSignal
(null);
-
- let rafId: number | null = null;
-
- const update = () => {
- const container = props.containerRef();
- if (!container) return;
-
- const containerRect = container.getBoundingClientRect();
- const scrollTop = container.scrollTop;
-
- // Find all message groups (bubbles)
- // We assume MessageList renders them with data-message-id
- const elements = Array.from(container.querySelectorAll('[data-message-role]'));
-
- const nextLines = elements.map(el => {
- const rect = el.getBoundingClientRect();
- const relativeTop = rect.top - containerRect.top + scrollTop;
- const scrollHeight = container.scrollHeight;
- const clientHeight = container.clientHeight;
-
- // Map content position (0 to scrollHeight) to viewport position (0 to clientHeight)
- const mapTop = (relativeTop / scrollHeight) * clientHeight;
-
- return {
- id: el.getAttribute('data-message-id') || "",
- role: el.getAttribute('data-message-role') as "user" | "assistant",
- top: mapTop,
- height: 2
- };
- });
-
- setLines(nextLines);
-
- // Update active message based on center
- const center = containerRect.top + containerRect.height / 2;
- let closestId = null;
- let minDist = Infinity;
-
- elements.forEach(el => {
- const rect = el.getBoundingClientRect();
- const dist = Math.abs((rect.top + rect.height / 2) - center);
- if (dist < minDist) {
- minDist = dist;
- closestId = el.getAttribute('data-message-id');
- }
- });
- setActiveId(closestId);
- };
-
- const scheduleUpdate = () => {
- if (rafId !== null) cancelAnimationFrame(rafId);
- rafId = requestAnimationFrame(() => {
- update();
- rafId = null;
- });
- };
-
- createEffect(() => {
- props.messages.length;
- scheduleUpdate();
- });
-
- createEffect(() => {
- const container = props.containerRef();
- if (!container) return;
-
- container.addEventListener("scroll", scheduleUpdate);
- window.addEventListener("resize", scheduleUpdate);
-
- onCleanup(() => {
- container.removeEventListener("scroll", scheduleUpdate);
- window.removeEventListener("resize", scheduleUpdate);
- if (rafId !== null) cancelAnimationFrame(rafId);
- });
- });
-
- return (
-
-
- {(line, idx) => {
- const isActive = () => line.id === activeId();
- const isUser = line.role === "user";
-
- return (
- {
- e.stopPropagation();
- const container = props.containerRef();
- const el = container?.querySelector(`[data-message-id="${line.id}"]`);
- el?.scrollIntoView({ behavior: "smooth", block: "center" });
- }}
- >
-
-
- );
- }}
-
-
- );
-}
diff --git a/apps/app/src/app/components/thinking-block.tsx b/apps/app/src/app/components/thinking-block.tsx
deleted file mode 100644
index 9b54f3474..000000000
--- a/apps/app/src/app/components/thinking-block.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { For, Show, createMemo, createSignal } from "solid-js";
-
-import { CheckCircle2, ChevronRight, Circle, RefreshCcw, X, Zap } from "lucide-solid";
-
-export type ThinkingStep = {
- status: "pending" | "running" | "completed" | "error";
- text: string;
-};
-
-export default function ThinkingBlock(props: {
- steps: ThinkingStep[];
- maxWidthClass?: string;
-}) {
- const [expanded, setExpanded] = createSignal(false);
-
- const activeStep = createMemo(() => {
- const steps = props.steps;
- return steps.find((s) => s.status === "running") ?? steps[steps.length - 1] ?? null;
- });
-
- return (
- 0}>
-
-
setExpanded((v) => !v)}
- class="flex items-center gap-2 text-xs font-medium text-gray-10 hover:text-gray-12 transition-colors py-1 px-2 rounded-lg hover:bg-gray-2/40"
- >
-
-
-
- {activeStep()?.text ?? "Working…"}
-
-
-
-
-
-
- {(step) => (
-
-
- }
- >
-
-
- }
- >
-
-
- }
- >
-
-
-
-
{step.text}
-
- )}
-
-
-
-
-
- );
-}
diff --git a/apps/app/src/app/components/workspace-switch-overlay.tsx b/apps/app/src/app/components/workspace-switch-overlay.tsx
deleted file mode 100644
index 510bd7a10..000000000
--- a/apps/app/src/app/components/workspace-switch-overlay.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { Show, createMemo } from "solid-js";
-import { t, currentLocale } from "../../i18n";
-import OpenWorkLogo from "./openwork-logo";
-
-import type { WorkspaceInfo } from "../lib/tauri";
-
-export default function WorkspaceSwitchOverlay(props: {
- open: boolean;
- workspace: WorkspaceInfo | null;
- statusKey: string;
-}) {
- const translate = (key: string) => t(key, currentLocale());
-
- const workspaceName = createMemo(() => {
- if (!props.workspace) return "";
- if (props.workspace.workspaceType === "remote" && props.workspace.remoteType === "openwork") {
- return (
- props.workspace.openworkWorkspaceName?.trim() ||
- props.workspace.displayName?.trim() ||
- props.workspace.name?.trim() ||
- props.workspace.openworkHostUrl?.trim() ||
- props.workspace.baseUrl?.trim() ||
- props.workspace.path?.trim() ||
- ""
- );
- }
- return (
- props.workspace.displayName?.trim() ||
- props.workspace.name?.trim() ||
- props.workspace.baseUrl?.trim() ||
- props.workspace.path?.trim() ||
- ""
- );
- });
-
- const title = createMemo(() => {
- const name = workspaceName();
- if (!name) return translate("workspace.switching_title_unknown");
- return translate("workspace.switching_title").replace("{name}", name);
- });
-
- const subtitle = createMemo(() => translate("workspace.switching_subtitle"));
-
- const statusLine = createMemo(() => {
- if (props.statusKey) return translate(props.statusKey);
- return translate("workspace.switching_status_loading");
- });
-
- const metaPrimary = createMemo(() => {
- if (!props.workspace) return "";
- if (props.workspace.workspaceType === "remote") {
- if (props.workspace.remoteType === "openwork") {
- return props.workspace.openworkHostUrl?.trim() ?? props.workspace.baseUrl?.trim() ?? "";
- }
- return props.workspace.baseUrl?.trim() ?? "";
- }
- return props.workspace.path?.trim() ?? "";
- });
-
- const metaSecondary = createMemo(() => {
- if (!props.workspace || props.workspace.workspaceType !== "remote") return "";
- return (
- props.workspace.directory?.trim() ||
- props.workspace.openworkWorkspaceName?.trim() ||
- ""
- );
- });
-
- return (
-
-
-
-
-
-
-
-
-
-
-
{title()}
-
-
- {translate("dashboard.remote")}
-
-
- {props.workspace?.remoteType === "openwork"
- ? translate("dashboard.remote_connection_openwork")
- : translate("dashboard.remote_connection_direct")}
-
-
-
-
{subtitle()}
-
-
-
-
-
-
-
-
- {statusLine()}
-
-
-
-
-
-
- {metaPrimary()}
-
-
- {metaSecondary()}
-
-
-
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-activity-panel.tsx b/ee/apps/landing/components/den-activity-panel.tsx
deleted file mode 100644
index 1638349f7..000000000
--- a/ee/apps/landing/components/den-activity-panel.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-"use client";
-
-import { motion, useReducedMotion } from "framer-motion";
-import { useEffect, useRef, useState } from "react";
-
-const baseActivityEntries = [
- {
- time: "9:41 AM",
- source: "GitHub",
- tone: "success" as const,
- lines: ["Reviewed PR #247, approved"],
- },
- {
- time: "10:12 AM",
- source: "Slack",
- tone: "warning" as const,
- lines: ["Flagged invoice #1092,", "duplicate"],
- },
- {
- time: "1:30 PM",
- source: "GitHub",
- tone: "critical" as const,
- lines: ["Triaged 8 issues, 2 critical"],
- },
- {
- time: "3:15 PM",
- source: "Slack",
- tone: "success" as const,
- lines: ["Weekly digest sent to #ops"],
- },
- {
- time: "5:44 PM",
- source: "Slack",
- tone: "success" as const,
- lines: ["6 follow-up emails queued", "for review"],
- },
- {
- time: "6:05 PM",
- source: "Linear",
- tone: "warning" as const,
- lines: ["Moved 3 stale tickets to backlog"],
- },
- {
- time: "8:20 AM",
- source: "Slack",
- tone: "success" as const,
- lines: ["Morning sync summary posted"],
- },
- {
- time: "11:45 AM",
- source: "GitHub",
- tone: "success" as const,
- lines: ["Merged dependabot PRs"],
- },
-];
-
-const toneStyles = {
- success: { bg: "bg-[#22c55e]", shadow: "0 0 0 7px rgba(34,197,94,0.18)" },
- warning: { bg: "bg-[#f59e0b]", shadow: "0 0 0 7px rgba(245,158,11,0.18)" },
- critical: { bg: "bg-[#ef4444]", shadow: "0 0 0 7px rgba(239,68,68,0.16)" },
-} as const;
-
-export function DenActivityPanel() {
- const reduceMotion = useReducedMotion();
- const [items, setItems] = useState(() =>
- baseActivityEntries.slice(0, 5).map((entry, i) => ({ ...entry, id: `initial-${i}` }))
- );
- const feedIndexRef = useRef(5);
-
- useEffect(() => {
- if (reduceMotion) return;
-
- const interval = setInterval(() => {
- const idx = feedIndexRef.current;
- feedIndexRef.current = idx + 1;
-
- const nextEntry = baseActivityEntries[idx % baseActivityEntries.length];
- const now = new Date();
- let hours = now.getHours();
- const minutes = now.getMinutes().toString().padStart(2, "0");
- const ampm = hours >= 12 ? "PM" : "AM";
- hours = hours % 12;
- hours = hours ? hours : 12;
- const timeString = `${hours}:${minutes} ${ampm}`;
-
- setItems(currentItems => {
- const newItems = [
- ...currentItems,
- {
- ...nextEntry,
- time: timeString,
- id: `item-${Date.now()}`,
- },
- ];
-
- return newItems.length > 5 ? newItems.slice(1) : newItems;
- });
- }, 3000);
-
- return () => clearInterval(interval);
- }, [reduceMotion]);
-
- return (
-
-
-
-
-
-
-
-
- ops-worker-01
-
-
-
-
-
-
- RUNNING
-
-
-
-
- {items.map(entry => (
-
-
-
- {entry.time}
- {entry.source}
-
-
- {entry.lines.map(line => (
-
{line}
- ))}
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-capability-carousel.tsx b/ee/apps/landing/components/den-capability-carousel.tsx
deleted file mode 100644
index 02e138172..000000000
--- a/ee/apps/landing/components/den-capability-carousel.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-"use client";
-
-import { motion, useReducedMotion } from "framer-motion";
-import {
- Box,
- Cpu,
- GitPullRequest,
- MessageSquare,
- ShieldCheck,
- Wrench,
-} from "lucide-react";
-
-const capabilityItems = [
- { label: "Secure isolation", icon: ShieldCheck },
- { label: "Slack + Telegram", icon: MessageSquare },
- { label: "Custom MCP tools", icon: Wrench },
- { label: "Any LLM (BYOK)", icon: Cpu },
- { label: "Open source", icon: GitPullRequest },
- { label: "Persistent state", icon: Box },
-];
-
-const premiumBadgeClassName =
- "relative flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full border border-white/80 bg-[radial-gradient(circle_at_28%_24%,rgba(255,255,255,0.96),rgba(255,255,255,0.52)_34%,transparent_35%),linear-gradient(180deg,#f6f8fb_0%,#e0e6ee_100%)] text-[#111827] shadow-[inset_0_1px_0_rgba(255,255,255,0.88),0_14px_26px_-20px_rgba(15,23,42,0.26)] ring-1 ring-[#d9e0e8]";
-
-export function DenCapabilityCarousel() {
- const reduceMotion = useReducedMotion();
- const repeatedItems = [...capabilityItems, ...capabilityItems];
-
- return (
-
-
- What you get
-
-
-
-
-
-
- {repeatedItems.map((item, index) => {
- const Icon = item.icon;
-
- return (
-
-
-
-
-
-
- {item.label}
-
-
- );
- })}
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-comparison-animation.tsx b/ee/apps/landing/components/den-comparison-animation.tsx
deleted file mode 100644
index d01e5938a..000000000
--- a/ee/apps/landing/components/den-comparison-animation.tsx
+++ /dev/null
@@ -1,311 +0,0 @@
-"use client";
-
-import { useEffect, useState, type CSSProperties } from "react";
-import { motion, useReducedMotion } from "framer-motion";
-import { CheckCheck, CircleAlert, Layers3, MessageSquareMore, Sparkles } from "lucide-react";
-
-const comparisonTheme = {
- ink: "#0f172a",
- muted: "#475569",
- caption: "#64748b",
- border: "rgba(148, 163, 184, 0.18)",
- card: "rgba(255, 255, 255, 0.94)",
- agentGrad: "linear-gradient(135deg, #1d4ed8, #60a5fa)",
- danger: "#ffe4e6",
- dangerText: "#9f1239",
- attention: "#fef3c7",
- attentionText: "#92400e",
- success: "#dcfce7",
- successText: "#166534",
- ease: "cubic-bezier(0.31, 0.325, 0, 0.92)",
-} as const;
-
-const comparisonTasks = [
- "PR #247",
- "INV #1092",
- "ISSUE #8",
- "QA notes",
- "Refund queue",
- "SLA digest",
- "Follow-ups",
-];
-
-const totalTicks = 110;
-
-export function DenComparisonAnimation() {
- const [tick, setTick] = useState(0);
- const reduceMotion = useReducedMotion();
-
- useEffect(() => {
- if (reduceMotion) {
- setTick(65);
- return;
- }
-
- const timer = window.setInterval(() => {
- setTick(current => (current >= totalTicks ? 0 : current + 1));
- }, 100);
-
- return () => window.clearInterval(timer);
- }, [reduceMotion]);
-
- const getDenTaskStyle = (index: number): CSSProperties => {
- const start = 8 + index * 14;
- const processing = start + 5;
- const done = start + 11;
-
- if (tick < start) return { left: "15%", top: `${20 + index * 10}%`, opacity: 0, transition: "none" };
- if (tick >= start && tick < processing) {
- return { left: "48%", top: "50%", opacity: 1, transition: `all 0.45s ${comparisonTheme.ease}` };
- }
- if (tick >= processing && tick < done) {
- return {
- left: "48%",
- top: "50%",
- opacity: 1,
- transform: "translate(-50%, -50%) scale(1.04)",
- transition: "all 0.8s linear",
- };
- }
-
- return {
- left: "82%",
- top: `${28 + Math.min(index, 3) * 6}%`,
- opacity: 0,
- transform: "translate(-50%, -50%) scale(0.88)",
- transition: `all 0.45s ${comparisonTheme.ease}, opacity 0.3s ease`,
- };
- };
-
- const getLocalTaskStyle = (index: number): CSSProperties => {
- if (index === 0) {
- if (tick < 6) return { left: "15%", top: "24%", opacity: 0, transition: "none" };
- if (tick >= 6 && tick < 12) {
- return { left: "48%", top: "50%", opacity: 1, transition: `all 0.45s ${comparisonTheme.ease}` };
- }
- if (tick >= 12 && tick < 46) return { left: "48%", top: "50%", opacity: 1, transition: "none" };
- if (tick >= 46 && tick < 56) {
- return {
- left: "82%",
- top: "28%",
- opacity: 0,
- transition: `all 0.45s ${comparisonTheme.ease}, opacity 0.35s ease`,
- };
- }
-
- return { left: "82%", top: "28%", opacity: 0, transition: "none" };
- }
-
- if (index === 1) {
- if (tick < 52) return { left: "15%", top: "34%", opacity: 0, transition: "none" };
- if (tick >= 52 && tick < 58) {
- return { left: "48%", top: "50%", opacity: 1, transition: `all 0.45s ${comparisonTheme.ease}` };
- }
- if (tick >= 58 && tick < totalTicks - 8) {
- return {
- left: "48%",
- top: "50%",
- opacity: 1,
- transform: tick >= 68 && tick < 74 ? "translate(-50%, -50%) translateX(3px)" : "translate(-50%, -50%)",
- transition: "all 0.12s ease",
- };
- }
-
- return { left: "48%", top: "50%", opacity: 0, transition: "none" };
- }
-
- return { left: "15%", top: `${24 + index * 10}%`, opacity: 0, transition: "none" };
- };
-
- const manualSolvedCount = tick < 50 ? 0 : 1;
- const denSolvedCount = reduceMotion ? 5 : tick < 20 ? 0 : tick < 34 ? 1 : tick < 48 ? 2 : tick < 62 ? 3 : tick < 76 ? 4 : 5;
- const isDenProcessing = tick >= 8 && tick < 86;
- const manualBacklogCount = tick < 50 ? 6 : 5;
- const denBacklogCount = Math.max(0, 6 - denSolvedCount);
- const manualFailed = tick >= 68;
-
- const getLocalStatus = () => {
- if (tick < 10) return { text: "IDLE", bg: "#f8fafc", color: comparisonTheme.caption };
- if (tick >= 10 && tick < 40) {
- return { text: "NEEDS APPROVAL", bg: comparisonTheme.attention, color: comparisonTheme.attentionText };
- }
- if (tick >= 40 && tick < 50) {
- return { text: "PROCESSING", bg: comparisonTheme.success, color: comparisonTheme.successText };
- }
- if (tick >= 50 && tick < 65) return { text: "GENERATING...", bg: "#e0e7ff", color: "#3730a3" };
- return { text: "CONTEXT FAILED", bg: comparisonTheme.danger, color: comparisonTheme.dangerText };
- };
-
- const localStatus = getLocalStatus();
-
- const renderBacklogStack = (items: string[], tone: "manual" | "den", activeCount: number) =>
- items.slice(0, activeCount).map((task, index) => (
-
-
- {tone === "manual" ? "Queued" : "Incoming"}
-
-
- {task}
-
-
- ));
-
- const renderSolvedStack = (items: string[], tone: "manual" | "den", statusLabel: string) =>
- items.map((task, index) => (
-
-
- {tone === "manual" ? : }
-
- {statusLabel}
-
-
-
- {task}
-
-
- ));
-
- return (
-
-
-
-
-
-
- Chat-based / Local AI
-
-
You are the bottleneck.
-
-
- {localStatus.text}
-
-
-
-
-
Backlog
-
Resolved
- {renderBacklogStack(comparisonTasks, "manual", manualBacklogCount)}
-
= 65 ? comparisonTheme.dangerText : "#cbd5e1" }}>
-
-
-
= 30 && tick < 45 ? "10px, 10px" : "40px, 40px"})`, opacity: tick >= 25 && tick < 45 ? 1 : 0, transition: `all 0.6s ${comparisonTheme.ease}` }}>
-
-
-
- {tick >= 35 && tick < 40 ? (
-
- Click to Approve
-
- ) : null}
-
- {comparisonTasks.slice(0, 3).map((task, index) => (
-
- {task}
-
- ))}
- {renderSolvedStack(comparisonTasks.slice(0, manualSolvedCount), "manual", "done")}
- {manualFailed ? (
-
-
- Context failed
-
- ) : null}
-
-
-
-
-
-
- Always-on Cloud Worker
-
-
Autonomous, sandboxed execution.
-
-
- {isDenProcessing ? "PROCESSING QUEUE" : "LISTENING"}
-
-
-
-
-
Queue
-
Solved stack
- {renderBacklogStack(comparisonTasks.slice(denSolvedCount, denSolvedCount + denBacklogCount), "den", denBacklogCount)}
-
- {comparisonTasks.slice(0, 5).map((task, index) => (
-
5 + index * 25 + 5 ? "#1b29ff" : "#cbd5e1", color: tick > 5 + index * 25 + 5 ? "#1b29ff" : "#334155" }}
- >
- {task}
-
- ))}
- {renderSolvedStack(comparisonTasks.slice(0, denSolvedCount), "den", "sent")}
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-how-it-works.tsx b/ee/apps/landing/components/den-how-it-works.tsx
deleted file mode 100644
index 6e79d44af..000000000
--- a/ee/apps/landing/components/den-how-it-works.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-"use client";
-
-import { CheckCircle2, Lock, Workflow, Zap } from "lucide-react";
-
-const steps = [
- {
- title: "1. Context Setup",
- body: "Define .opencode/skills and attach data sources via MCP.",
- icon: Workflow,
- accent: "bg-[#1b29ff]/10 text-[#1b29ff]",
- },
- {
- title: "2. Event Trigger",
- body: "Cloud workers wake up on webhooks or scheduled polling intervals.",
- icon: Zap,
- accent: "bg-orange-500/10 text-orange-600",
- },
- {
- title: "3. Isolated Compute",
- body: "A sandboxed runtime spins up automatically to process the workload securely.",
- icon: Lock,
- accent: "bg-teal-500/10 text-teal-600",
- },
- {
- title: "4. Review & Merge",
- body: "The worker proposes changes via PRs or messaging platforms.",
- icon: CheckCircle2,
- accent: "bg-[linear-gradient(180deg,#eceff3,#dfe4ea)] text-[#111827] ring-1 ring-[#d7dde5]",
- },
-];
-
-export function DenHowItWorks() {
- return (
-
-
-
- How it works
-
-
- From trigger to completion.
-
-
- We turn your defined skills into an automated workflow. Cloud workers operate independently in the cloud, unblocking your team.
-
-
-
-
- {steps.map(step => {
- const Icon = step.icon;
-
- return (
-
-
-
-
-
- {step.title}
-
-
- {step.body}
-
-
- );
- })}
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-icons.tsx b/ee/apps/landing/components/den-icons.tsx
deleted file mode 100644
index 85efaf1f1..000000000
--- a/ee/apps/landing/components/den-icons.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-"use client";
-
-export function SlackGlyph(props: { className?: string }) {
- return (
-
-
-
-
-
-
- );
-}
-
-export function TelegramGlyph(props: { className?: string }) {
- return (
-
-
-
-
- );
-}
-
-export function AppleGlyph(props: { className?: string }) {
- return (
-
-
-
- );
-}
-
-export function WindowsGlyph(props: { className?: string }) {
- return (
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-support-grid.tsx b/ee/apps/landing/components/den-support-grid.tsx
deleted file mode 100644
index 1b1be9fd1..000000000
--- a/ee/apps/landing/components/den-support-grid.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-"use client";
-
-import { motion } from "framer-motion";
-import { Blocks, Box, MessageSquare, Shield } from "lucide-react";
-import { AppleGlyph, SlackGlyph, TelegramGlyph, WindowsGlyph } from "./den-icons";
-
-export function DenSupportGrid() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Isolated & Secure
-
-
-
-
-
Hosted sandboxed workers
-
- Every worker runs in an isolated environment so your team can automate safely without managing infrastructure.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Linux
-
-
- Desktop,
-
- Slack, and
-
- Telegram access
-
-
- Run and monitor the same workers from the OpenWork desktop app or directly inside your team chats.
-
-
-
-
-
-
-
-
Skills, agents, and MCP included
-
- Bring your existing OpenWork setup and everything is available immediately in each hosted worker.
-
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/den-value-section.tsx b/ee/apps/landing/components/den-value-section.tsx
deleted file mode 100644
index 7be317e79..000000000
--- a/ee/apps/landing/components/den-value-section.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-"use client";
-
-import { CheckCircle2 } from "lucide-react";
-
-type DenValueSectionProps = {
- getStartedHref: string;
-};
-
-export function DenValueSection(props: DenValueSectionProps) {
- const hireHumanSubject =
- "Please come automate this {TASK} at {LOCATION} - SF for {BUDGET}";
- const hireHumanBody = `Hey Ben,
-
-I want to automate this {TASK} because {REASON}. I don't trust AI to do this because of the following {AI_CONCERN}. I'm willing to pay you {BUDGET} for {HOURS} of your time.
-
-Best`;
- const hireHumanHref = `mailto:ben@openworklabs.com?subject=${encodeURIComponent(hireHumanSubject)}&body=${encodeURIComponent(hireHumanBody)}`;
- const getStartedExternal = /^https?:\/\//.test(props.getStartedHref);
-
- return (
-
-
-
-
- Pricing
-
-
- Replace repetitive work with a $50 worker.
-
-
- Cloud is priced like a utility, not a headcount bet. Keep your team on
- the critical decisions and let the worker own the repetitive queue.
-
-
-
-
-
-
-
-
- Human repetitive work
-
-
-
- Recommended
-
-
-
-
-
-
- Best when the work needs constant human judgment.
-
-
-
- Expensive for follow-through and reminders.
-
-
-
-
-
-
-
-
-
- Cloud worker
-
-
-
- Recommended
-
-
-
-
-
-
- Handles repetitive work continuously instead of in bursts.
-
-
-
- Keeps humans focused on approvals and exceptions.
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/ee/apps/landing/components/og-image-svg.ts b/ee/apps/landing/components/og-image-svg.ts
deleted file mode 100644
index 525ea014a..000000000
--- a/ee/apps/landing/components/og-image-svg.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-const logoPaths = `
-
-
-
-
-
-
-`;
-
-export function getOgImageDataUrl() {
- const svg = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${logoPaths}
- OpenWork
-
-
-
- The team layer for your
- existing
- agent
- setup.
-
-
-
-
- Backed by
-
- Y
- Combinator
-
-
-
-
-
-
-
-
-
-
-
-
-
- OpenWork
-
-
-
-
- Digital Twin
- Extended digital you
-
- Sales Inbound
- Qualifies leads
-
-
- Twitter replies...
- 1s ago
- Q3 outliers
- 15m
-
-
-
- Like all the replies to this tweet and save the users to a CSV.
- ›
- Navigates to tweet URL
- ›
- Extracts bio data to tweet_replies.csv
- I liked 42 replies and saved the data to
- "tweet_replies.csv" on your desktop.
-
-
- Describe your task
-
- Like Twitter replies and extract users to CSV.
-
-
- `;
-
- return `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`;
-}
diff --git a/scripts/find-unused.README.md b/scripts/find-unused.README.md
new file mode 100644
index 000000000..b6c48b20c
--- /dev/null
+++ b/scripts/find-unused.README.md
@@ -0,0 +1,48 @@
+# find-unused.sh
+
+Wrapper around [knip](https://knip.dev) that detects unused files and cross-references them against CI configs, package.json scripts, convention-based usage, and file-based routing directories to reduce false positives.
+
+## Usage
+
+```bash
+bash scripts/find-unused.sh
+```
+
+Requires `npx` (knip is fetched automatically). A fake `DATABASE_URL` is injected so config resolution doesn't fail.
+
+## What it does
+
+1. **Runs `knip --include files`** to get a list of unused files across the monorepo.
+2. **Cross-references** each file against:
+ - **CI / infra configs** — GitHub workflows, Dockerfiles, Vercel configs, Turbo config, Tauri config
+ - **package.json scripts** — all workspace `package.json` files
+ - **Convention patterns** — filenames like `postinstall`, `drizzle.config`, Tauri hooks
+ - **File-based routing dirs** — Nuxt/Next server routes and API routes that are entry points by convention
+3. **Outputs a sorted list** (oldest first) with two categories:
+ - `✗` **Likely safe to remove** — no references found anywhere
+ - `⚠` **Review before removing** — referenced in CI, infra, convention, or routing
+
+Certain paths are ignored entirely (scripts, dev tools) — see the `IGNORE_PREFIXES` array in the script.
+
+## Using knip directly
+
+The script only checks for unused **files**. Knip can detect much more — run it directly for deeper analysis:
+
+```bash
+# Unused exports (functions, types, constants)
+npx knip --include exports
+
+# Unused dependencies in package.json
+npx knip --include dependencies
+
+# Everything at once
+npx knip
+
+# Scope to a single workspace
+npx knip --workspace apps/app
+
+# Auto-fix removable issues (careful — modifies files)
+npx knip --fix
+```
+
+See the [knip docs](https://knip.dev) for the full set of options.
diff --git a/scripts/find-unused.sh b/scripts/find-unused.sh
new file mode 100755
index 000000000..5b68aa1ad
--- /dev/null
+++ b/scripts/find-unused.sh
@@ -0,0 +1,192 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# ── Config ──────────────────────────────────────────────────────────────────
+INFRA_PATHS=(
+ .github/workflows/
+ packaging/docker/
+ turbo.json
+ apps/app/vercel.json
+ apps/share/vercel.json
+ ee/apps/den-web/vercel.json
+ apps/desktop/src-tauri/tauri.conf.json
+)
+
+# Globs for package.json scripts across all workspaces
+PACKAGE_JSONS=$(find . -name package.json -not -path '*/node_modules/*' -not -path '*/.git/*')
+
+# Files that are used by convention (framework/tool magic), not imports
+CONVENTION_PATTERNS=(
+ "postinstall"
+ "drizzle.config"
+ "tauri-before-build"
+ "tauri-before-dev"
+)
+
+# File-based routing directories — files here are entry points by convention
+ROUTING_DIRS=(
+ "apps/share/server/"
+ "ee/apps/den-web/app/"
+ "ee/apps/landing/app/api/"
+)
+
+# Paths to ignore entirely (scripts, dev tools, etc.)
+IGNORE_PREFIXES=(
+ "apps/app/scripts/"
+ "apps/desktop/scripts/"
+ "apps/orchestrator/scripts/"
+ "scripts/stats"
+)
+
+# ── Colors ──────────────────────────────────────────────────────────────────
+RED='\033[0;31m'
+YELLOW='\033[0;33m'
+GREEN='\033[0;32m'
+DIM='\033[2m'
+BOLD='\033[1m'
+RESET='\033[0m'
+
+# ── Step 1: Run knip ───────────────────────────────────────────────────────
+echo -e "${BOLD}Running knip to detect unused files...${RESET}"
+KNIP_OUTPUT=$(DATABASE_URL=mysql://fake:fake@localhost/fake npx knip --include files --no-progress --no-config-hints 2>&1 || true)
+
+UNUSED_FILES=()
+while IFS= read -r line; do
+ trimmed=$(echo "$line" | sed 's/[[:space:]]*$//')
+ [ -z "$trimmed" ] && continue
+ [[ "$trimmed" == Unused* ]] && continue
+ [[ "$trimmed" == npm* ]] && continue
+ [ -f "$trimmed" ] || continue
+ skip=false
+ for prefix in "${IGNORE_PREFIXES[@]}"; do
+ if [[ "$trimmed" == "$prefix"* ]]; then
+ skip=true
+ break
+ fi
+ done
+ $skip || UNUSED_FILES+=("$trimmed")
+done <<< "$KNIP_OUTPUT"
+
+if [ ${#UNUSED_FILES[@]} -eq 0 ]; then
+ echo -e "${GREEN}No unused files detected by knip.${RESET}"
+ exit 0
+fi
+
+echo -e "Found ${BOLD}${#UNUSED_FILES[@]}${RESET} unused files. Cross-referencing...\n"
+
+# ── Step 2: Cross-reference each file ──────────────────────────────────────
+declare -A FILE_STATUS # "safe" | "ci" | "convention" | "routing"
+declare -A FILE_REFS
+declare -A FILE_DATES
+
+for filepath in "${UNUSED_FILES[@]}"; do
+ name=$(basename "$filepath")
+ status="safe"
+ refs=""
+
+ # Check CI/infra configs
+ existing_paths=()
+ for p in "${INFRA_PATHS[@]}"; do
+ [ -e "$p" ] && existing_paths+=("$p")
+ done
+ if [ ${#existing_paths[@]} -gt 0 ]; then
+ ci_hits=$(grep -rl "$name" "${existing_paths[@]}" 2>/dev/null || true)
+ if [ -n "$ci_hits" ]; then
+ status="ci"
+ refs="$ci_hits"
+ fi
+ fi
+
+ # Check package.json scripts
+ if [ "$status" = "safe" ]; then
+ pkg_hits=$(echo "$PACKAGE_JSONS" | xargs grep -l "$name" 2>/dev/null || true)
+ if [ -n "$pkg_hits" ]; then
+ status="ci"
+ refs="$pkg_hits"
+ fi
+ fi
+
+ # Check convention-based usage
+ if [ "$status" = "safe" ]; then
+ for pat in "${CONVENTION_PATTERNS[@]}"; do
+ if [[ "$name" == *"$pat"* ]]; then
+ status="convention"
+ refs="used by convention ($pat)"
+ break
+ fi
+ done
+ fi
+
+ # Check file-based routing dirs
+ if [ "$status" = "safe" ]; then
+ for dir in "${ROUTING_DIRS[@]}"; do
+ if [[ "$filepath" == "$dir"* ]]; then
+ status="routing"
+ refs="file-based route ($dir)"
+ break
+ fi
+ done
+ fi
+
+ # Get last commit date
+ last_date=$(git log -1 --format="%aI" -- "$filepath" 2>/dev/null || echo "unknown")
+
+ FILE_STATUS["$filepath"]="$status"
+ FILE_REFS["$filepath"]="$refs"
+ FILE_DATES["$filepath"]="$last_date"
+done
+
+# ── Step 3: Sort by date and display ───────────────────────────────────────
+sorted_files=()
+while IFS= read -r line; do
+ sorted_files+=("$line")
+done < <(
+ for filepath in "${UNUSED_FILES[@]}"; do
+ echo "${FILE_DATES[$filepath]}|$filepath"
+ done | sort
+)
+
+safe_count=0
+flagged_count=0
+
+echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
+echo -e "${BOLD} UNUSED FILES (oldest first)${RESET}"
+echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
+
+for entry in "${sorted_files[@]}"; do
+ date="${entry%%|*}"
+ filepath="${entry#*|}"
+ status="${FILE_STATUS[$filepath]}"
+ refs="${FILE_REFS[$filepath]}"
+ short_date="${date%%T*}"
+
+ case "$status" in
+ safe)
+ echo -e "${RED} ✗ ${RESET}${DIM}${short_date}${RESET} ./$filepath:1"
+ safe_count=$((safe_count + 1))
+ ;;
+ ci)
+ echo -e "${YELLOW} ⚠ ${RESET}${DIM}${short_date}${RESET} ./$filepath:1"
+ echo -e " ${DIM}→ referenced in: $(echo "$refs" | tr '\n' ', ' | sed 's/,$//')${RESET}"
+ flagged_count=$((flagged_count + 1))
+ ;;
+ convention)
+ echo -e "${YELLOW} ⚠ ${RESET}${DIM}${short_date}${RESET} ./$filepath:1"
+ echo -e " ${DIM}→ $refs${RESET}"
+ flagged_count=$((flagged_count + 1))
+ ;;
+ routing)
+ echo -e "${YELLOW} ⚠ ${RESET}${DIM}${short_date}${RESET} ./$filepath:1"
+ echo -e " ${DIM}→ $refs${RESET}"
+ flagged_count=$((flagged_count + 1))
+ ;;
+ esac
+done
+
+echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
+echo -e ""
+echo -e "${BOLD}Legend:${RESET}"
+echo -e " ${RED}✗${RESET} Likely safe to remove (no references found)"
+echo -e " ${YELLOW}⚠${RESET} Review before removing (referenced in CI/infra/convention/routing)"
+echo -e ""
+echo -e "${BOLD}Summary:${RESET} ${RED}${safe_count} likely removable${RESET} │ ${YELLOW}${flagged_count} need review${RESET} │ ${#UNUSED_FILES[@]} total"