chore(raycast-extension): Add initial project files and configurations

This commit is contained in:
Fatih Kadir Akın
2026-01-07 23:09:30 +03:00
parent 04768b20e2
commit 4dc1d5be3d
19 changed files with 5663 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# Dependencies
node_modules/
# Build output
dist/
# Raycast
.raycast/
# OS
.DS_Store
# IDE
.idea/
.vscode/
*.swp
*.swo
# Logs
*.log
npm-debug.log*
+52
View File
@@ -0,0 +1,52 @@
# Prompts.chat
Search, browse, and run AI prompts from [prompts.chat](https://prompts.chat) — the open-source community platform for ChatGPT and AI prompts.
## Features
- **Search Prompts** — Quickly search through thousands of community AI prompts
- **Browse Prompts** — Browse latest prompts with filtering by type and sorting options
- **Random Prompt** — Get a random prompt for inspiration
- **Run Prompts** — Run prompts directly in ChatGPT, Claude, Cursor, and 25+ other AI platforms
- **Variable Support** — Fill in prompt variables before running
## Commands
| Command | Description |
|---------|-------------|
| Search Prompts | Search for AI prompts from the community |
| Browse Prompts | Browse and filter latest AI prompts |
| Random Prompt | Get a random AI prompt for inspiration |
## Running Prompts
Press **Enter** on any prompt to run it. You can choose from:
**Chat Platforms:** ChatGPT, Claude, Microsoft Copilot, DeepSeek, Gemini, Grok, Perplexity, and more
**Code Platforms:** Cursor, Windsurf, VS Code, GitHub Copilot, Bolt, Lovable, v0, and more
If a prompt contains variables (e.g., `${topic}`), you'll be prompted to fill them in first.
## Keyboard Shortcuts
| Action | Shortcut |
|--------|----------|
| Run Prompt | ↵ Enter |
| Copy Prompt | ⌘ C |
| Open in Browser | ⌘ O |
| Copy URL | ⌘ ⇧ C |
| Refresh | ⌘ R |
## Configuration
| Preference | Description | Default |
|------------|-------------|---------|
| Base URL | The base URL of the prompts.chat instance | `https://prompts.chat` |
You can use this extension with self-hosted instances of prompts.chat by changing the Base URL.
## Links
- [prompts.chat](https://prompts.chat) — Browse prompts on the web
- [GitHub Repository](https://github.com/f/awesome-chatgpt-prompts) — Source code and contributions
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@@ -0,0 +1,8 @@
import raycastConfig from "@raycast/eslint-config";
export default [
{
ignores: ["node_modules/**", "dist/**"],
},
...raycastConfig,
];
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "prompts-chat",
"title": "Prompts.chat",
"description": "Search, browse, and copy AI prompts from prompts.chat - the community platform for ChatGPT and AI prompts",
"icon": "icon.png",
"author": "fka",
"categories": ["Productivity", "Developer Tools"],
"license": "MIT",
"platforms": ["macOS"],
"commands": [
{
"name": "search-prompts",
"title": "Search Prompts",
"subtitle": "prompts.chat",
"description": "Search for AI prompts from the community",
"mode": "view"
},
{
"name": "browse-prompts",
"title": "Browse Prompts",
"subtitle": "prompts.chat",
"description": "Browse and filter latest AI prompts",
"mode": "view"
},
{
"name": "random-prompt",
"title": "Random Prompt",
"subtitle": "prompts.chat",
"description": "Get a random AI prompt for inspiration",
"mode": "view"
},
{
"name": "download-prompts",
"title": "Download All Prompts",
"subtitle": "prompts.chat",
"description": "Download and cache all prompts for offline use",
"mode": "view"
},
{
"name": "browse-categories",
"title": "Browse Categories",
"subtitle": "prompts.chat",
"description": "Browse prompts by category",
"mode": "view"
}
],
"preferences": [
{
"name": "baseUrl",
"title": "Base URL",
"description": "The base URL of the prompts.chat instance",
"type": "textfield",
"default": "https://prompts.chat",
"required": false
}
],
"dependencies": {
"@raycast/api": "^1.96.1",
"@raycast/utils": "^1.19.1",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "22.13.14",
"@types/react": "19.0.10",
"eslint": "^8.57.0",
"prettier": "^3.5.3",
"typescript": "^5.8.2"
},
"scripts": {
"build": "ray build",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint",
"publish": "npx @raycast/api@latest publish"
}
}
+43
View File
@@ -0,0 +1,43 @@
/// <reference types="@raycast/api">
/* 🚧 🚧 🚧
* This file is auto-generated from the extension's manifest.
* Do not modify manually. Instead, update the `package.json` file.
* 🚧 🚧 🚧 */
/* eslint-disable @typescript-eslint/ban-types */
type ExtensionPreferences = {
/** Base URL - The base URL of the prompts.chat instance */
"baseUrl": string
}
/** Preferences accessible in all the extension's commands */
declare type Preferences = ExtensionPreferences
declare namespace Preferences {
/** Preferences accessible in the `search-prompts` command */
export type SearchPrompts = ExtensionPreferences & {}
/** Preferences accessible in the `browse-prompts` command */
export type BrowsePrompts = ExtensionPreferences & {}
/** Preferences accessible in the `random-prompt` command */
export type RandomPrompt = ExtensionPreferences & {}
/** Preferences accessible in the `download-prompts` command */
export type DownloadPrompts = ExtensionPreferences & {}
/** Preferences accessible in the `browse-categories` command */
export type BrowseCategories = ExtensionPreferences & {}
}
declare namespace Arguments {
/** Arguments passed to the `search-prompts` command */
export type SearchPrompts = {}
/** Arguments passed to the `browse-prompts` command */
export type BrowsePrompts = {}
/** Arguments passed to the `random-prompt` command */
export type RandomPrompt = {}
/** Arguments passed to the `download-prompts` command */
export type DownloadPrompts = {}
/** Arguments passed to the `browse-categories` command */
export type BrowseCategories = {}
}
+98
View File
@@ -0,0 +1,98 @@
import { getPreferenceValues } from "@raycast/api";
import type { Preferences, Prompt, PromptsResponse } from "./types";
function getBaseUrl(): string {
const { baseUrl } = getPreferenceValues<Preferences>();
return baseUrl?.replace(/\/$/, "") || "https://prompts.chat";
}
export async function fetchPrompts(options: {
page?: number;
perPage?: number;
type?: string;
category?: string;
tag?: string;
sort?: string;
q?: string;
}): Promise<PromptsResponse> {
const baseUrl = getBaseUrl();
const params = new URLSearchParams();
if (options.page) params.set("page", options.page.toString());
if (options.perPage) params.set("perPage", options.perPage.toString());
if (options.type) params.set("type", options.type);
if (options.category) params.set("category", options.category);
if (options.tag) params.set("tag", options.tag);
if (options.sort) params.set("sort", options.sort);
if (options.q) params.set("q", options.q);
const url = `${baseUrl}/api/prompts?${params.toString()}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch prompts: ${response.statusText}`);
}
return response.json() as Promise<PromptsResponse>;
}
export async function fetchPromptBySlug(
username: string,
slug: string,
): Promise<Prompt | null> {
const baseUrl = getBaseUrl();
const response = await fetch(`${baseUrl}/api/prompts/${slug}`);
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`Failed to fetch prompt: ${response.statusText}`);
}
return response.json() as Promise<Prompt>;
}
export async function searchPrompts(
query: string,
limit: number = 20,
): Promise<
{ id: string; title: string; slug: string; author: { username: string } }[]
> {
if (query.length < 2) {
return [];
}
const baseUrl = getBaseUrl();
const params = new URLSearchParams({
q: query,
limit: limit.toString(),
});
const url = `${baseUrl}/api/prompts/search?${params.toString()}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to search prompts: ${response.statusText}`);
}
const data = (await response.json()) as {
prompts: {
id: string;
title: string;
slug: string;
author: { username: string };
}[];
};
return data.prompts;
}
export function getPromptUrl(username: string, slug: string): string {
const baseUrl = getBaseUrl();
return `${baseUrl}/${username}/${slug}`;
}
export function getWebsiteUrl(): string {
return getBaseUrl();
}
@@ -0,0 +1,264 @@
import {
Action,
ActionPanel,
Icon,
List,
showToast,
Toast,
Clipboard,
openExtensionPreferences,
} from "@raycast/api";
import { useState, useEffect, useMemo } from "react";
import type { Prompt } from "./types";
import { getPromptUrl } from "./api";
import { RunPromptForm } from "./components/run-prompt";
import { PromptDetailView } from "./components/prompt-detail";
import {
getCachedPrompts,
convertToPrompt,
downloadAllPrompts,
type CachedPrompt,
} from "./cache";
interface Category {
name: string;
slug: string;
count: number;
}
export default function BrowseCategories() {
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [searchText, setSearchText] = useState("");
async function handleDownload() {
setIsLoading(true);
try {
await showToast({ style: Toast.Style.Animated, title: "Downloading prompts..." });
const prompts = await downloadAllPrompts();
setAllPrompts(prompts);
await showToast({ style: Toast.Style.Success, title: `Downloaded ${prompts.length} prompts` });
} catch (err) {
await showToast({ style: Toast.Style.Failure, title: "Download failed", message: String(err) });
} finally {
setIsLoading(false);
}
}
useEffect(() => {
async function loadPrompts() {
const cached = await getCachedPrompts();
setAllPrompts(cached);
setIsLoading(false);
}
loadPrompts();
}, []);
const categories = useMemo(() => {
const categoryMap = new Map<string, Category>();
for (const prompt of allPrompts) {
if (prompt.category) {
const existing = categoryMap.get(prompt.category.slug);
if (existing) {
existing.count++;
} else {
categoryMap.set(prompt.category.slug, {
name: prompt.category.name,
slug: prompt.category.slug,
count: 1,
});
}
}
}
return Array.from(categoryMap.values()).sort((a, b) => b.count - a.count);
}, [allPrompts]);
const categoryPrompts = useMemo(() => {
if (!selectedCategory) return [];
let result = allPrompts.filter((p) => p.category?.slug === selectedCategory);
if (searchText.trim()) {
const query = searchText.toLowerCase();
result = result.filter(
(p) =>
p.title.toLowerCase().includes(query) ||
(p.description && p.description.toLowerCase().includes(query)) ||
p.author.username.toLowerCase().includes(query)
);
}
return result.sort((a, b) => b.voteCount - a.voteCount);
}, [allPrompts, selectedCategory, searchText]);
if (allPrompts.length === 0 && !isLoading) {
return (
<List>
<List.EmptyView
icon={Icon.Download}
title="No prompts cached"
description="Press Enter to download all prompts"
actions={
<ActionPanel>
<Action
title="Download All Prompts"
icon={Icon.Download}
onAction={handleDownload}
/>
</ActionPanel>
}
/>
</List>
);
}
if (selectedCategory) {
const categoryName = categories.find((c) => c.slug === selectedCategory)?.name || selectedCategory;
return (
<List
isLoading={isLoading}
navigationTitle={categoryName}
searchBarPlaceholder={`Search in ${categoryName}...`}
onSearchTextChange={setSearchText}
throttle
>
{categoryPrompts.length === 0 ? (
<List.EmptyView
icon={Icon.XMarkCircle}
title="No prompts found"
description={searchText ? `No prompts match "${searchText}"` : "No prompts in this category"}
actions={
<ActionPanel>
<Action
title="Back to Categories"
icon={Icon.ArrowLeft}
onAction={() => setSelectedCategory(null)}
/>
</ActionPanel>
}
/>
) : (
categoryPrompts.map((cached) => (
<PromptListItem
key={cached.id}
prompt={convertToPrompt(cached)}
onBack={() => setSelectedCategory(null)}
/>
))
)}
</List>
);
}
return (
<List
isLoading={isLoading}
searchBarPlaceholder="Search categories..."
onSearchTextChange={setSearchText}
throttle
>
{categories.length === 0 ? (
<List.EmptyView
icon={Icon.Folder}
title="No categories found"
description="Prompts don't have categories assigned"
/>
) : (
categories
.filter((c) => !searchText || c.name.toLowerCase().includes(searchText.toLowerCase()))
.map((category) => (
<List.Item
key={category.slug}
title={category.name}
subtitle={`${category.count} prompts`}
icon={Icon.Folder}
actions={
<ActionPanel>
<Action
title="View Prompts"
icon={Icon.List}
onAction={() => {
setSelectedCategory(category.slug);
setSearchText("");
}}
/>
</ActionPanel>
}
/>
))
)}
</List>
);
}
function PromptListItem({ prompt, onBack }: { prompt: Prompt; onBack: () => void }) {
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
return (
<List.Item
title={prompt.title}
subtitle={`@${prompt.author.username} • ${prompt.voteCount} upvotes`}
accessories={[{ tag: prompt.type }]}
actions={
<ActionPanel>
<ActionPanel.Section title="Run">
<Action.Push
title="Run Prompt"
icon={Icon.Play}
target={<RunPromptForm prompt={prompt} />}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.CopyToClipboard
title="Copy Prompt"
content={prompt.content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="Open in Browser"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
<Action
title="Copy URL"
icon={Icon.Link}
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
onAction={async () => {
await Clipboard.copy(promptUrl);
await showToast({
style: Toast.Style.Success,
title: "URL copied to clipboard",
});
}}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.Push
title="View Details"
icon={Icon.Eye}
target={<PromptDetailView prompt={prompt} />}
shortcut={{ modifiers: ["cmd"], key: "d" }}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action
title="Back to Categories"
icon={Icon.ArrowLeft}
onAction={onBack}
shortcut={{ modifiers: ["cmd"], key: "b" }}
/>
<Action
title="Open Extension Preferences"
icon={Icon.Gear}
onAction={openExtensionPreferences}
/>
</ActionPanel.Section>
</ActionPanel>
}
/>
);
}
@@ -0,0 +1,212 @@
import {
Action,
ActionPanel,
Icon,
List,
showToast,
Toast,
Clipboard,
openExtensionPreferences,
} from "@raycast/api";
import { useState, useEffect, useMemo } from "react";
import type { Prompt } from "./types";
import { getPromptUrl } from "./api";
import { RunPromptForm } from "./components/run-prompt";
import { PromptDetailView } from "./components/prompt-detail";
import {
getCachedPrompts,
convertToPrompt,
type CachedPrompt,
} from "./cache";
const TYPE_OPTIONS = [
{ title: "All Types", value: "" },
{ title: "Text", value: "TEXT" },
{ title: "Image", value: "IMAGE" },
{ title: "Video", value: "VIDEO" },
{ title: "Audio", value: "AUDIO" },
{ title: "Skill", value: "SKILL" },
];
const SORT_OPTIONS = [
{ title: "Most Upvoted", value: "upvotes" },
{ title: "Most Recent", value: "recent" },
{ title: "By Author", value: "author" },
];
export default function BrowsePrompts() {
const [searchText, setSearchText] = useState("");
const [type, setType] = useState("");
const [sort, setSort] = useState("upvotes");
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function loadPrompts() {
const cached = await getCachedPrompts();
setAllPrompts(cached);
setIsLoading(false);
if (cached.length === 0) {
showToast({
style: Toast.Style.Animated,
title: "No prompts cached",
message: "Run 'Download All Prompts' first",
});
}
}
loadPrompts();
}, []);
const filteredPrompts = useMemo(() => {
let result = [...allPrompts];
if (type) {
result = result.filter((p) => p.type === type);
}
if (searchText.trim()) {
const query = searchText.toLowerCase();
result = result.filter(
(p) =>
p.title.toLowerCase().includes(query) ||
(p.description && p.description.toLowerCase().includes(query)) ||
p.author.username.toLowerCase().includes(query) ||
(p.author.name && p.author.name.toLowerCase().includes(query)) ||
p.tags.some((tag) => tag.toLowerCase().includes(query)) ||
(p.category && p.category.name.toLowerCase().includes(query))
);
}
// Sort
if (sort === "upvotes") {
result.sort((a, b) => b.voteCount - a.voteCount);
} else if (sort === "recent") {
// Keep original order (already sorted by newest from API)
} else if (sort === "author") {
result.sort((a, b) => a.author.username.localeCompare(b.author.username));
}
return result;
}, [allPrompts, type, searchText, sort]);
return (
<List
isLoading={isLoading}
searchBarPlaceholder="Filter prompts..."
onSearchTextChange={setSearchText}
searchBarAccessory={
<List.Dropdown
tooltip="Filter & Sort"
storeValue
onChange={(value) => {
if (value.startsWith("sort:")) {
setSort(value.replace("sort:", ""));
} else if (value.startsWith("type:")) {
setType(value.replace("type:", ""));
}
}}
>
<List.Dropdown.Section title="Sort">
{SORT_OPTIONS.map((option) => (
<List.Dropdown.Item
key={`sort:${option.value}`}
title={option.title}
value={`sort:${option.value}`}
/>
))}
</List.Dropdown.Section>
<List.Dropdown.Section title="Type">
{TYPE_OPTIONS.map((option) => (
<List.Dropdown.Item
key={`type:${option.value}`}
title={option.title}
value={`type:${option.value}`}
/>
))}
</List.Dropdown.Section>
</List.Dropdown>
}
>
{allPrompts.length === 0 ? (
<List.EmptyView
icon={Icon.Download}
title="No prompts cached"
description="Run 'Download All Prompts' command first"
/>
) : filteredPrompts.length === 0 ? (
<List.EmptyView
icon={Icon.Document}
title="No prompts found"
description="Try changing the filters"
/>
) : (
filteredPrompts.map((cached) => (
<PromptListItem key={cached.id} prompt={convertToPrompt(cached)} />
))
)}
</List>
);
}
function PromptListItem({ prompt }: { prompt: Prompt }) {
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
return (
<List.Item
title={prompt.title}
subtitle={`@${prompt.author.username} • ${prompt.voteCount} upvotes`}
accessories={[{ tag: prompt.type }]}
actions={
<ActionPanel>
<ActionPanel.Section title="Run">
<Action.Push
title="Run Prompt"
icon={Icon.Play}
target={<RunPromptForm prompt={prompt} />}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.CopyToClipboard
title="Copy Prompt"
content={prompt.content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="Open in Browser"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
<Action
title="Copy URL"
icon={Icon.Link}
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
onAction={async () => {
await Clipboard.copy(promptUrl);
await showToast({
style: Toast.Style.Success,
title: "URL copied to clipboard",
});
}}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.Push
title="View Details"
icon={Icon.Eye}
target={<PromptDetailView prompt={prompt} />}
shortcut={{ modifiers: ["cmd"], key: "d" }}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action
title="Open Extension Preferences"
icon={Icon.Gear}
onAction={openExtensionPreferences}
/>
</ActionPanel.Section>
</ActionPanel>
}
/>
);
}
+158
View File
@@ -0,0 +1,158 @@
import { LocalStorage } from "@raycast/api";
import type { Prompt } from "./types";
import { getWebsiteUrl } from "./api";
const PROMPTS_CACHE_KEY = "cached_prompts";
const CACHE_TIMESTAMP_KEY = "cached_prompts_timestamp";
export interface CachedPrompt {
id: string;
title: string;
content: string;
description?: string;
type: string;
author: {
username: string;
name?: string;
};
slug: string;
category?: {
name: string;
slug: string;
};
tags: string[];
voteCount: number;
}
export async function getCachedPrompts(): Promise<CachedPrompt[]> {
const cached = await LocalStorage.getItem<string>(PROMPTS_CACHE_KEY);
if (!cached) return [];
try {
return JSON.parse(cached) as CachedPrompt[];
} catch {
return [];
}
}
export async function setCachedPrompts(prompts: CachedPrompt[]): Promise<void> {
await LocalStorage.setItem(PROMPTS_CACHE_KEY, JSON.stringify(prompts));
await LocalStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
}
export async function getCacheTimestamp(): Promise<number | null> {
const timestamp = await LocalStorage.getItem<string>(CACHE_TIMESTAMP_KEY);
return timestamp ? parseInt(timestamp, 10) : null;
}
export async function clearCache(): Promise<void> {
await LocalStorage.removeItem(PROMPTS_CACHE_KEY);
await LocalStorage.removeItem(CACHE_TIMESTAMP_KEY);
}
interface PromptsJsonResponse {
count: number;
prompts: Array<{
id: string;
title: string;
slug: string;
description: string | null;
content: string;
type: string;
voteCount: number;
category: {
id: string;
name: string;
slug: string;
} | null;
author: {
username: string;
name: string | null;
};
tags: Array<{
id: string;
name: string;
slug: string;
}>;
}>;
}
export async function downloadAllPrompts(): Promise<CachedPrompt[]> {
const url = `${getWebsiteUrl()}/prompts.json`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch prompts: ${response.statusText}`);
}
const data = (await response.json()) as PromptsJsonResponse;
const prompts: CachedPrompt[] = data.prompts.map((item) => ({
id: item.id,
title: item.title,
content: item.content,
description: item.description || undefined,
type: item.type,
author: {
username: item.author.username,
name: item.author.name || undefined,
},
slug: item.slug,
category: item.category
? { name: item.category.name, slug: item.category.slug }
: undefined,
tags: item.tags.map((t) => t.name),
voteCount: item.voteCount,
}));
await setCachedPrompts(prompts);
return prompts;
}
export function searchPrompts(prompts: CachedPrompt[], query: string): CachedPrompt[] {
if (!query.trim()) return prompts;
const lowerQuery = query.toLowerCase();
return prompts.filter(
(p) =>
p.title.toLowerCase().includes(lowerQuery) ||
p.content.toLowerCase().includes(lowerQuery) ||
(p.description && p.description.toLowerCase().includes(lowerQuery)) ||
p.author.username.toLowerCase().includes(lowerQuery) ||
(p.author.name && p.author.name.toLowerCase().includes(lowerQuery)) ||
(p.category && p.category.name.toLowerCase().includes(lowerQuery)) ||
p.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))
);
}
export function convertToPrompt(cached: CachedPrompt): Prompt {
return {
id: cached.id,
title: cached.title,
content: cached.content,
description: cached.description || null,
type: cached.type as Prompt["type"],
mediaUrl: null,
author: {
id: "local",
username: cached.author.username,
name: cached.author.name || null,
avatar: null,
verified: false,
},
slug: cached.slug,
category: cached.category
? {
id: "local",
name: cached.category.name,
slug: cached.category.slug,
parent: null,
}
: null,
tags: cached.tags.map((tag, i) => ({
tag: { id: `tag-${i}`, name: tag, slug: tag.toLowerCase() },
})),
voteCount: cached.voteCount,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
}
@@ -0,0 +1,127 @@
import {
Action,
ActionPanel,
Detail,
Icon,
showToast,
Toast,
Clipboard,
} from "@raycast/api";
import type { Prompt } from "../types";
import { getPromptUrl } from "../api";
import { RunPromptForm } from "./run-prompt";
interface PromptDetailViewProps {
prompt: Prompt;
onRefresh?: () => void;
}
export function PromptDetailView({ prompt, onRefresh }: PromptDetailViewProps) {
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
const markdown = `# ${prompt.title}
${prompt.description ? `*${prompt.description}*\n\n---\n\n` : ""}
## Prompt
\`\`\`
${prompt.content}
\`\`\`
---
**Author:** ${prompt.author.name || prompt.author.username} (@${prompt.author.username})
**Type:** ${prompt.type}
**Votes:** ${prompt.voteCount}
${prompt.category ? `**Category:** ${prompt.category.name} \n` : ""}
${prompt.tags.length > 0 ? `**Tags:** ${prompt.tags.map((t) => t.tag.name).join(", ")}` : ""}
`;
return (
<Detail
markdown={markdown}
actions={
<ActionPanel>
<ActionPanel.Section title="Run">
<Action.Push
title="Run Prompt"
icon={Icon.Play}
target={<RunPromptForm prompt={prompt} />}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.CopyToClipboard
title="Copy Prompt"
content={prompt.content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="Open in Browser"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
<Action
title="Copy URL"
icon={Icon.Link}
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
onAction={async () => {
await Clipboard.copy(promptUrl);
await showToast({
style: Toast.Style.Success,
title: "URL copied to clipboard",
});
}}
/>
</ActionPanel.Section>
{onRefresh && (
<ActionPanel.Section>
<Action
title="Refresh"
icon={Icon.ArrowClockwise}
onAction={onRefresh}
shortcut={{ modifiers: ["cmd"], key: "r" }}
/>
</ActionPanel.Section>
)}
</ActionPanel>
}
metadata={
<Detail.Metadata>
<Detail.Metadata.Label title="Title" text={prompt.title} />
<Detail.Metadata.Label
title="Author"
text={`@${prompt.author.username}`}
/>
<Detail.Metadata.TagList title="Type">
<Detail.Metadata.TagList.Item text={prompt.type} color="#007AFF" />
</Detail.Metadata.TagList>
<Detail.Metadata.Label
title="Votes"
text={prompt.voteCount.toString()}
icon={Icon.Heart}
/>
{prompt.category && (
<Detail.Metadata.Label
title="Category"
text={prompt.category.name}
/>
)}
{prompt.tags.length > 0 && (
<Detail.Metadata.TagList title="Tags">
{prompt.tags.map((t) => (
<Detail.Metadata.TagList.Item key={t.tag.id} text={t.tag.name} />
))}
</Detail.Metadata.TagList>
)}
<Detail.Metadata.Separator />
<Detail.Metadata.Link
title="View on prompts.chat"
text="Open"
target={promptUrl}
/>
</Detail.Metadata>
}
/>
);
}
@@ -0,0 +1,244 @@
import {
Action,
ActionPanel,
Form,
Icon,
List,
showToast,
Toast,
Clipboard,
open,
popToRoot,
useNavigation,
} from "@raycast/api";
import { useState } from "react";
import type { Prompt } from "../types";
import {
extractVariables,
compilePrompt,
chatPlatforms,
codePlatforms,
imagePlatforms,
videoPlatforms,
buildUrl,
type Platform,
} from "../utils";
import { getPromptUrl } from "../api";
interface RunPromptProps {
prompt: Prompt;
}
export function RunPromptForm({ prompt }: RunPromptProps) {
const variables = extractVariables(prompt.content);
const [values, setValues] = useState<Record<string, string>>(() => {
const initial: Record<string, string> = {};
for (const v of variables) {
initial[v.name] = v.defaultValue;
}
return initial;
});
const { push } = useNavigation();
const compiledContent = compilePrompt(prompt.content, values);
function handleSubmit() {
push(<PlatformList prompt={prompt} content={compiledContent} />);
}
if (variables.length === 0) {
return <PlatformList prompt={prompt} content={compiledContent} />;
}
return (
<Form
navigationTitle={`Fill Variables: ${prompt.title}`}
actions={
<ActionPanel>
<Action.SubmitForm
title="Run Prompt"
icon={Icon.Play}
onSubmit={handleSubmit}
/>
<Action.CopyToClipboard
title="Copy Compiled Prompt"
content={compiledContent}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
</ActionPanel>
}
>
<Form.Description title="Prompt" text={prompt.title} />
{prompt.description && (
<Form.Description title="" text={prompt.description} />
)}
<Form.Separator />
{variables.map((variable) => (
<Form.TextField
key={variable.name}
id={variable.name}
title={variable.name}
placeholder={variable.defaultValue || `Enter ${variable.name}`}
value={values[variable.name] || ""}
onChange={(value) =>
setValues((prev) => ({ ...prev, [variable.name]: value }))
}
/>
))}
<Form.Separator />
<Form.Description
title="Preview"
text={compiledContent}
/>
</Form>
);
}
interface PlatformListProps {
prompt: Prompt;
content: string;
}
function PlatformList({ prompt, content }: PlatformListProps) {
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
async function handleRun(platform: Platform) {
if (platform.supportsQuerystring) {
const url = buildUrl(
platform.id,
platform.baseUrl,
content,
prompt.title,
prompt.description || undefined,
);
await open(url);
await showToast({
style: Toast.Style.Success,
title: `Opening in ${platform.name}`,
});
} else {
await Clipboard.copy(content);
await open(platform.baseUrl);
await showToast({
style: Toast.Style.Success,
title: "Prompt copied!",
message: `Opening ${platform.name}... Paste with ⌘V`,
});
}
await popToRoot();
}
const isImagePrompt = prompt.type === "IMAGE";
const isVideoPrompt = prompt.type === "VIDEO";
const mediaPlatforms = isImagePrompt
? imagePlatforms
: isVideoPrompt
? videoPlatforms
: [];
return (
<List navigationTitle={`Run: ${prompt.title}`}>
<List.Section title="Chat Platforms">
{chatPlatforms.map((platform) => (
<List.Item
key={platform.id}
title={platform.name}
icon={Icon.Message}
accessories={[
platform.supportsQuerystring
? { icon: Icon.Play, tooltip: "Auto-fill supported" }
: { icon: Icon.Clipboard, tooltip: "Will copy to clipboard" },
]}
actions={
<ActionPanel>
<Action
title={`Run with ${platform.name}`}
icon={Icon.Play}
onAction={() => handleRun(platform)}
/>
<Action.CopyToClipboard
title="Copy Prompt"
content={content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="View on prompts.chat"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
</ActionPanel>
}
/>
))}
</List.Section>
<List.Section title="Code Platforms">
{codePlatforms.map((platform) => (
<List.Item
key={platform.id}
title={platform.name}
icon={Icon.Terminal}
accessories={[
platform.supportsQuerystring
? { icon: Icon.Play, tooltip: "Auto-fill supported" }
: { icon: Icon.Clipboard, tooltip: "Will copy to clipboard" },
]}
actions={
<ActionPanel>
<Action
title={`Run with ${platform.name}`}
icon={Icon.Play}
onAction={() => handleRun(platform)}
/>
<Action.CopyToClipboard
title="Copy Prompt"
content={content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="View on prompts.chat"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
</ActionPanel>
}
/>
))}
</List.Section>
{mediaPlatforms.length > 0 && (
<List.Section title="Media Generation">
{mediaPlatforms.map((platform) => (
<List.Item
key={platform.id}
title={platform.name}
icon={Icon.Image}
accessories={[
platform.supportsQuerystring
? { icon: Icon.Play, tooltip: "Auto-fill supported" }
: { icon: Icon.Clipboard, tooltip: "Will copy to clipboard" },
]}
actions={
<ActionPanel>
<Action
title={`Run with ${platform.name}`}
icon={Icon.Play}
onAction={() => handleRun(platform)}
/>
<Action.CopyToClipboard
title="Copy Prompt"
content={content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="View on prompts.chat"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
</ActionPanel>
}
/>
))}
</List.Section>
)}
</List>
);
}
@@ -0,0 +1,127 @@
import { Action, ActionPanel, Detail, Icon, showToast, Toast } from "@raycast/api";
import { useState, useEffect } from "react";
import { downloadAllPrompts, getCacheTimestamp, getCachedPrompts, clearCache } from "./cache";
export default function DownloadPrompts() {
const [isLoading, setIsLoading] = useState(false);
const [promptCount, setPromptCount] = useState<number | null>(null);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadCacheInfo();
}, []);
async function loadCacheInfo() {
const prompts = await getCachedPrompts();
setPromptCount(prompts.length);
const timestamp = await getCacheTimestamp();
if (timestamp) {
setLastUpdated(new Date(timestamp));
}
}
async function handleDownload() {
setIsLoading(true);
setError(null);
try {
await showToast({
style: Toast.Style.Animated,
title: "Downloading prompts...",
});
const prompts = await downloadAllPrompts();
await showToast({
style: Toast.Style.Success,
title: "Download complete!",
message: `${prompts.length} prompts cached`,
});
setPromptCount(prompts.length);
setLastUpdated(new Date());
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
setError(message);
await showToast({
style: Toast.Style.Failure,
title: "Download failed",
message,
});
} finally {
setIsLoading(false);
}
}
async function handleClear() {
await clearCache();
setPromptCount(0);
setLastUpdated(null);
await showToast({
style: Toast.Style.Success,
title: "Cache cleared",
});
}
const markdown = `# Download Prompts
Download all prompts from prompts.chat for offline access and faster searching.
## Cache Status
${promptCount !== null ? `**Cached Prompts:** ${promptCount}` : "**Cached Prompts:** Not loaded yet"}
${lastUpdated ? `**Last Updated:** ${lastUpdated.toLocaleString()}` : "**Last Updated:** Never"}
${error ? `\n\n## Error\n\n\`${error}\`` : ""}
---
Press **Enter** to download all prompts. This will fetch the latest prompts from prompts.chat and cache them locally for offline use.
`;
return (
<Detail
isLoading={isLoading}
markdown={markdown}
actions={
<ActionPanel>
<Action
title="Download All Prompts"
icon={Icon.Download}
onAction={handleDownload}
/>
{promptCount && promptCount > 0 && (
<Action
title="Clear Cache"
icon={Icon.Trash}
style={Action.Style.Destructive}
onAction={handleClear}
/>
)}
</ActionPanel>
}
metadata={
<Detail.Metadata>
<Detail.Metadata.Label
title="Cached Prompts"
text={promptCount !== null ? promptCount.toString() : "—"}
icon={Icon.Document}
/>
<Detail.Metadata.Label
title="Last Updated"
text={lastUpdated ? lastUpdated.toLocaleString() : "Never"}
icon={Icon.Clock}
/>
<Detail.Metadata.Separator />
<Detail.Metadata.Label
title="Source"
text="prompts.chat/prompts.json"
/>
</Detail.Metadata>
}
/>
);
}
@@ -0,0 +1,65 @@
import { Action, ActionPanel, Detail, Icon, showToast, Toast } from "@raycast/api";
import { useState, useEffect, useCallback } from "react";
import type { Prompt } from "./types";
import { PromptDetailView } from "./components/prompt-detail";
import { getCachedPrompts, convertToPrompt, type CachedPrompt } from "./cache";
export default function RandomPrompt() {
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
const [randomPrompt, setRandomPrompt] = useState<Prompt | null>(null);
const [isLoading, setIsLoading] = useState(true);
const pickRandom = useCallback((prompts: CachedPrompt[]) => {
if (prompts.length === 0) return null;
const randomIndex = Math.floor(Math.random() * prompts.length);
return convertToPrompt(prompts[randomIndex]);
}, []);
useEffect(() => {
async function loadPrompts() {
const cached = await getCachedPrompts();
setAllPrompts(cached);
setRandomPrompt(pickRandom(cached));
setIsLoading(false);
if (cached.length === 0) {
showToast({
style: Toast.Style.Animated,
title: "No prompts cached",
message: "Run 'Download All Prompts' first",
});
}
}
loadPrompts();
}, [pickRandom]);
function handleRefresh() {
setRandomPrompt(pickRandom(allPrompts));
}
if (isLoading) {
return <Detail isLoading={true} markdown="Loading random prompt..." />;
}
if (allPrompts.length === 0) {
return (
<Detail
markdown="# No Prompts Cached\n\nRun the **Download All Prompts** command first to cache prompts for offline use."
actions={
<ActionPanel>
<Action.OpenInBrowser
title="Open prompts.chat"
url="https://prompts.chat"
/>
</ActionPanel>
}
/>
);
}
if (!randomPrompt) {
return <Detail isLoading={true} markdown="Picking a random prompt..." />;
}
return <PromptDetailView prompt={randomPrompt} onRefresh={handleRefresh} />;
}
@@ -0,0 +1,158 @@
import {
Action,
ActionPanel,
Icon,
List,
showToast,
Toast,
Clipboard,
openExtensionPreferences,
} from "@raycast/api";
import { useState, useEffect } from "react";
import type { Prompt } from "./types";
import { getPromptUrl } from "./api";
import { RunPromptForm } from "./components/run-prompt";
import { PromptDetailView } from "./components/prompt-detail";
import {
getCachedPrompts,
searchPrompts,
convertToPrompt,
downloadAllPrompts,
type CachedPrompt,
} from "./cache";
export default function SearchPrompts() {
const [searchText, setSearchText] = useState("");
const [allPrompts, setAllPrompts] = useState<CachedPrompt[]>([]);
const [isLoading, setIsLoading] = useState(true);
async function handleDownload() {
setIsLoading(true);
try {
await showToast({ style: Toast.Style.Animated, title: "Downloading prompts..." });
const prompts = await downloadAllPrompts();
setAllPrompts(prompts);
await showToast({ style: Toast.Style.Success, title: `Downloaded ${prompts.length} prompts` });
} catch (err) {
await showToast({ style: Toast.Style.Failure, title: "Download failed", message: String(err) });
} finally {
setIsLoading(false);
}
}
useEffect(() => {
async function loadPrompts() {
const cached = await getCachedPrompts();
setAllPrompts(cached);
setIsLoading(false);
}
loadPrompts();
}, []);
const filteredPrompts = searchPrompts(allPrompts, searchText);
return (
<List
isLoading={isLoading}
searchBarPlaceholder="Search prompts..."
onSearchTextChange={setSearchText}
throttle
>
{allPrompts.length === 0 ? (
<List.EmptyView
icon={Icon.Download}
title="No prompts cached"
description="Press Enter to download all prompts"
actions={
<ActionPanel>
<Action
title="Download All Prompts"
icon={Icon.Download}
onAction={handleDownload}
/>
</ActionPanel>
}
/>
) : searchText.length < 2 ? (
<List.EmptyView
icon={Icon.MagnifyingGlass}
title="Start typing to search"
description={`${allPrompts.length} prompts available. Enter at least 2 characters to search.`}
/>
) : filteredPrompts.length === 0 ? (
<List.EmptyView
icon={Icon.XMarkCircle}
title="No prompts found"
description={`No prompts match "${searchText}"`}
/>
) : (
filteredPrompts.map((cached) => (
<PromptListItem key={cached.id} prompt={convertToPrompt(cached)} />
))
)}
</List>
);
}
function PromptListItem({ prompt }: { prompt: Prompt }) {
const promptUrl = getPromptUrl(prompt.author.username, prompt.slug);
return (
<List.Item
title={prompt.title}
subtitle={`@${prompt.author.username} • ${prompt.voteCount} upvotes`}
accessories={[{ tag: prompt.type }]}
actions={
<ActionPanel>
<ActionPanel.Section title="Run">
<Action.Push
title="Run Prompt"
icon={Icon.Play}
target={<RunPromptForm prompt={prompt} />}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.CopyToClipboard
title="Copy Prompt"
content={prompt.content}
shortcut={{ modifiers: ["cmd"], key: "c" }}
/>
<Action.OpenInBrowser
title="Open in Browser"
url={promptUrl}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
<Action
title="Copy URL"
icon={Icon.Link}
shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
onAction={async () => {
await Clipboard.copy(promptUrl);
await showToast({
style: Toast.Style.Success,
title: "URL copied to clipboard",
});
}}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action.Push
title="View Details"
icon={Icon.Eye}
target={<PromptDetailView prompt={prompt} />}
shortcut={{ modifiers: ["cmd"], key: "d" }}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<Action
title="Open Extension Preferences"
icon={Icon.Gear}
onAction={openExtensionPreferences}
shortcut={{ modifiers: ["cmd"], key: "," }}
/>
</ActionPanel.Section>
</ActionPanel>
}
/>
);
}
+69
View File
@@ -0,0 +1,69 @@
export interface Author {
id: string;
name: string | null;
username: string;
avatar: string | null;
verified: boolean;
}
export interface Category {
id: string;
name: string;
slug: string;
parent?: {
id: string;
name: string;
slug: string;
} | null;
}
export interface Tag {
id: string;
name: string;
slug: string;
}
export interface PromptTag {
tag: Tag;
}
export interface Prompt {
id: string;
title: string;
slug: string;
description: string | null;
content: string;
type: "TEXT" | "IMAGE" | "VIDEO" | "AUDIO" | "SKILL";
mediaUrl: string | null;
createdAt: string;
updatedAt: string;
author: Author;
category: Category | null;
tags: PromptTag[];
voteCount: number;
viewCount?: number;
isFeatured?: boolean;
}
export interface PromptsResponse {
prompts: Prompt[];
total: number;
page: number;
perPage: number;
totalPages: number;
}
export interface SearchPromptsResponse {
prompts: {
id: string;
title: string;
slug: string;
author: {
username: string;
};
}[];
}
export interface Preferences {
baseUrl: string;
}
+320
View File
@@ -0,0 +1,320 @@
export interface Variable {
name: string;
defaultValue: string;
}
/**
* Extract variables from prompt content in ${variableName} or ${variableName:default} format
*/
export function extractVariables(content: string): Variable[] {
const regex = /\$\{([^}:]+)(?::([^}]*))?\}/g;
const variables: Variable[] = [];
const seen = new Set<string>();
let match;
while ((match = regex.exec(content)) !== null) {
const name = match[1].trim();
const defaultValue = match[2]?.trim() || "";
if (!seen.has(name)) {
seen.add(name);
variables.push({ name, defaultValue });
}
}
return variables;
}
/**
* Compile a prompt template with variable values
*/
export function compilePrompt(
template: string,
values: Record<string, string>,
): string {
return template.replace(
/\$\{([^}:]+)(?::([^}]*))?\}/g,
(match, name, defaultValue) => {
const trimmedName = name.trim();
return values[trimmedName] ?? defaultValue?.trim() ?? match;
},
);
}
export interface Platform {
id: string;
name: string;
baseUrl: string;
supportsQuerystring: boolean;
isDeeplink?: boolean;
}
// Chat platforms (AI assistants)
export const chatPlatforms: Platform[] = [
{
id: "chatgpt",
name: "ChatGPT",
baseUrl: "https://chatgpt.com",
supportsQuerystring: true,
},
{
id: "claude",
name: "Claude",
baseUrl: "https://claude.ai/new",
supportsQuerystring: true,
},
{
id: "copilot",
name: "Microsoft Copilot",
baseUrl: "https://copilot.microsoft.com",
supportsQuerystring: false,
},
{
id: "deepseek",
name: "DeepSeek",
baseUrl: "https://chat.deepseek.com",
supportsQuerystring: false,
},
{
id: "fal",
name: "fal.ai Sandbox",
baseUrl: "https://fal.ai/sandbox",
supportsQuerystring: true,
},
{
id: "gemini",
name: "Gemini",
baseUrl: "https://gemini.google.com/app",
supportsQuerystring: false,
},
{
id: "goose-chat",
name: "Goose",
baseUrl: "goose://recipe",
supportsQuerystring: true,
isDeeplink: true,
},
{
id: "grok",
name: "Grok",
baseUrl: "https://grok.com/chat?reasoningMode=none",
supportsQuerystring: true,
},
{
id: "huggingface",
name: "HuggingChat",
baseUrl: "https://huggingface.co/chat",
supportsQuerystring: true,
},
{
id: "llama",
name: "Meta AI",
baseUrl: "https://www.meta.ai",
supportsQuerystring: false,
},
{
id: "manus",
name: "Manus",
baseUrl: "https://manus.im/app",
supportsQuerystring: false,
},
{
id: "mistral",
name: "Le Chat",
baseUrl: "https://chat.mistral.ai/chat",
supportsQuerystring: true,
},
{
id: "perplexity",
name: "Perplexity",
baseUrl: "https://www.perplexity.ai",
supportsQuerystring: true,
},
{
id: "phind",
name: "Phind",
baseUrl: "https://www.phind.com",
supportsQuerystring: true,
},
{
id: "pi",
name: "Pi",
baseUrl: "https://pi.ai",
supportsQuerystring: false,
},
{
id: "poe",
name: "Poe",
baseUrl: "https://poe.com",
supportsQuerystring: false,
},
{
id: "you",
name: "You.com",
baseUrl: "https://you.com",
supportsQuerystring: true,
},
];
// Code platforms (IDEs + code generation tools)
export const codePlatforms: Platform[] = [
{
id: "windsurf",
name: "Windsurf",
baseUrl: "windsurf://",
supportsQuerystring: false,
isDeeplink: true,
},
{
id: "cursor",
name: "Cursor",
baseUrl: "cursor://anysphere.cursor-deeplink/prompt",
supportsQuerystring: true,
isDeeplink: true,
},
{
id: "vscode",
name: "VS Code",
baseUrl: "vscode://",
supportsQuerystring: false,
isDeeplink: true,
},
{
id: "vscode-insiders",
name: "VS Code Insiders",
baseUrl: "vscode-insiders://",
supportsQuerystring: false,
isDeeplink: true,
},
{
id: "goose",
name: "Goose",
baseUrl: "goose://recipe",
supportsQuerystring: true,
isDeeplink: true,
},
{
id: "github-copilot",
name: "GitHub Copilot",
baseUrl: "https://github.com/copilot",
supportsQuerystring: true,
},
{
id: "bolt",
name: "Bolt",
baseUrl: "https://bolt.new",
supportsQuerystring: true,
},
{
id: "lovable",
name: "Lovable",
baseUrl: "https://lovable.dev",
supportsQuerystring: true,
},
{
id: "v0",
name: "v0",
baseUrl: "https://v0.dev/chat",
supportsQuerystring: true,
},
{
id: "ai2sql",
name: "AI2SQL",
baseUrl: "https://builder.ai2sql.io/dashboard/builder-all-lp?tab=generate",
supportsQuerystring: true,
},
];
// Image generation platforms
export const imagePlatforms: Platform[] = [
{
id: "mitte-image",
name: "Mitte.ai (Image)",
baseUrl: "https://mitte.ai",
supportsQuerystring: true,
},
];
// Video generation platforms
export const videoPlatforms: Platform[] = [
{
id: "mitte-video",
name: "Mitte.ai (Video)",
baseUrl: "https://mitte.ai",
supportsQuerystring: true,
},
];
export function buildUrl(
platformId: string,
baseUrl: string,
promptText: string,
promptTitle?: string,
promptDescription?: string,
): string {
const encoded = encodeURIComponent(promptText);
switch (platformId) {
// IDE deeplinks
case "cursor":
return `${baseUrl}?text=${encoded}`;
case "goose":
case "goose-chat": {
const config = JSON.stringify({
version: "1.0.0",
title: promptTitle || "Prompt",
description: promptDescription || "",
instructions:
"This is a prompt imported from prompts.chat. Follow the instructions below to complete the task.",
prompt: promptText,
activities: [
"message:This prompt was imported from prompts.chat. Follow the instructions below to complete the task.",
"Do it now",
"Learn more about the instructions",
],
});
const base64Config = Buffer.from(config).toString("base64");
return `${baseUrl}?config=${base64Config}`;
}
// Web platforms
case "ai2sql":
return `${baseUrl}&prompt=${encoded}`;
case "bolt":
return `${baseUrl}?prompt=${encoded}`;
case "chatgpt":
return `${baseUrl}/?q=${encoded}`;
case "claude":
return `${baseUrl}?q=${encoded}`;
case "copilot":
return `${baseUrl}/?q=${encoded}`;
case "deepseek":
return `${baseUrl}/?q=${encoded}`;
case "github-copilot":
return `${baseUrl}?prompt=${encoded}`;
case "grok":
return `${baseUrl}&q=${encoded}`;
case "fal":
return `${baseUrl}?prompt=${encoded}`;
case "huggingface":
return `${baseUrl}/?prompt=${encoded}`;
case "lovable":
return `${baseUrl}/?autosubmit=true#prompt=${encoded}`;
case "mistral":
return `${baseUrl}?q=${encoded}`;
case "perplexity":
return `${baseUrl}/search?q=${encoded}`;
case "phind":
return `${baseUrl}/search?q=${encoded}`;
case "poe":
return `${baseUrl}/?q=${encoded}`;
case "v0":
return `${baseUrl}?q=${encoded}`;
case "you":
return `${baseUrl}/search?q=${encoded}`;
case "mitte-image":
case "mitte-video":
return `${baseUrl}?prompt=${encoded}`;
default:
return `${baseUrl}?q=${encoded}`;
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Raycast Extension",
"compilerOptions": {
"strict": true,
"lib": ["ES2022"],
"module": "Node16",
"target": "ES2022",
"moduleResolution": "Node16",
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"outDir": "dist"
},
"include": ["src/**/*"]
}