- Write your own logic to handle infinite edge cases. Allow research on youtube,
- block specific subreddits, or give yourself a 15 minute break every 2 hours.
-
+
+
+ Restorable draft found from a previous session.
+
+
+
+
+ Discard
+
+
+ Restore Draft
+
)}
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/settings/extensions-settings.tsx b/frontend/src/components/settings/extensions-settings.tsx
index 9aa5d40..257fd55 100644
--- a/frontend/src/components/settings/extensions-settings.tsx
+++ b/frontend/src/components/settings/extensions-settings.tsx
@@ -1,33 +1,87 @@
import {
Card,
CardContent,
- CardHeader,
- CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
+import { ScrollArea } from "@/components/ui/scroll-area";
import { useState } from "react";
import {
IconTerminal,
IconCopy,
IconCheck,
IconRobot,
- IconPlug,
- IconPlus,
IconBook,
- IconActivity,
IconLock,
- IconStar
+ IconCode
} from "@tabler/icons-react";
-import { Browser } from "@wailsio/runtime";
+import { Browser, Clipboard } from "@wailsio/runtime";
import { useAccountStore } from "@/stores/account-store";
import { useQuery } from "@tanstack/react-query";
import { DeviceHandshakeResponse_AccountTier } from "../../../bindings/github.com/focusd-so/focusd/gen/api/v1/models";
const PORT = 50533;
+const API_EXAMPLES = [
+ {
+ id: "whitelist",
+ title: "Whitelist a site",
+ method: "POST",
+ path: "/whitelist",
+ description: "Temporarily allow access to a specific domain while in focus mode. Useful for giving agents access to docs.",
+ code: `curl -X POST http://localhost:50533/whitelist \\
+ -H "Content-Type: application/json" \\
+ -d '{
+ "hostname": "example.com",
+ "duration_seconds": 3600
+ }'`
+ },
+ {
+ id: "unwhitelist",
+ title: "Remove from whitelist",
+ method: "POST",
+ path: "/unwhitelist",
+ description: "Remove a previously whitelisted domain from the allowed list to re-enable blocking.",
+ code: `curl -X POST http://localhost:50533/unwhitelist \\
+ -H "Content-Type: application/json" \\
+ -d '{
+ "id": 1
+ }'`
+ },
+ {
+ id: "pause",
+ title: "Pause Focus",
+ method: "POST",
+ path: "/pause",
+ description: "Temporarily pause your current focus session for a specific duration.",
+ code: `curl -X POST http://localhost:50533/pause \\
+ -H "Content-Type: application/json" \\
+ -d '{
+ "duration_seconds": 300
+ }'`
+ },
+ {
+ id: "unpause",
+ title: "Unpause Focus",
+ method: "POST",
+ path: "/unpause",
+ description: "Resume your focus session immediately, canceling any active pause.",
+ code: `curl -X POST http://localhost:50533/unpause`
+ },
+ {
+ id: "status",
+ title: "Get Status",
+ method: "GET",
+ path: "/status",
+ description: "Retrieve the current status of Focusd, including active sessions, pauses, and whitelists.",
+ code: `curl -X GET http://localhost:50533/status`
+ }
+];
+
export function ExtensionsSettings() {
const [copied, setCopied] = useState(null);
+ const [selectedExampleId, setSelectedExampleId] = useState(API_EXAMPLES[0].id);
+
const { checkoutLink, fetchAccountTier } = useAccountStore();
const { data: accountTier } = useQuery({
@@ -40,16 +94,18 @@ export function ExtensionsSettings() {
const baseUrl = `http://localhost:${PORT}`;
const copyToClipboard = (text: string, label: string) => {
- navigator.clipboard.writeText(text);
+ Clipboard.SetText(text);
setCopied(label);
setTimeout(() => setCopied(null), 2000);
};
+ const activeEx = API_EXAMPLES.find(ex => ex.id === selectedExampleId) || API_EXAMPLES[0];
+
return (
-
- {/* Locked Banner Replacement */}
+
+ {/* Locked Banner */}
{isLocked && (
-
+
@@ -66,201 +122,124 @@ export function ExtensionsSettings() {
)}
- {/* Hero Section - More Compact */}
-
-
-
-
-
- Plus Feature
-
-
-
- Extend Focusd with Local API
-
-
- Automate your focus workflow and integrate Focusd with third-party tools, coding agents, and custom scripts. Our Local API provides the building blocks for a customized productivity environment.
-
- The Local API allows external tools to query and modify your focus state. This is perfect for the AGENT ERA.
-
-
- {[
- { title: "Agents", desc: "can pause blocking while they research on your behalf." },
- { title: "Scripts", desc: "can automate blocking based on your specific dev environment state." },
- { title: "Dashboards", desc: "can pull your focus stats for custom displays." },
- ].map((item, idx) => (
-
-
-
{item.title} {item.desc}
-
- ))}
-
-
-
- API Documentation
-
-
-
-
-
-
+
);
}
diff --git a/internal/usage/http_handler.go b/internal/usage/http_handler.go
index f730d72..45da7a1 100644
--- a/internal/usage/http_handler.go
+++ b/internal/usage/http_handler.go
@@ -5,6 +5,9 @@ import (
"net/http"
"time"
+ apiv1 "github.com/focusd-so/focusd/gen/api/v1"
+ "github.com/focusd-so/focusd/internal/identity"
+
"github.com/go-chi/chi/v5"
)
@@ -29,6 +32,16 @@ type UnwhitelistRequest struct {
func (s *Service) RegisterHTTPHandlers(r *chi.Mux) {
r.Group(func(r chi.Router) {
+ r.Use(func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if identity.GetAccountTier() == apiv1.DeviceHandshakeResponse_ACCOUNT_TIER_FREE {
+ http.Error(w, `{"error": "This local API feature requires a Focusd Plus or Pro plan."}`, http.StatusForbidden)
+ return
+ }
+ next.ServeHTTP(w, r)
+ })
+ })
+
r.Post("/pause", func(w http.ResponseWriter, r *http.Request) {
var pauseRequest PauseRequets